mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:25:20 +00:00
Move new Noise test vectors into XCTests (WIP)
This commit is contained in:
Generated
+34
@@ -95,6 +95,24 @@
|
|||||||
);
|
);
|
||||||
target = 57CA17A36A2532A6CFF367BB /* bitchatShareExtension */;
|
target = 57CA17A36A2532A6CFF367BB /* bitchatShareExtension */;
|
||||||
};
|
};
|
||||||
|
C5E027A52ECCDFD700BD6012 /* Exceptions for "bitchatTests" folder in "bitchatTests_macOS" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
Localization/PrimaryLocalizationKeys.json,
|
||||||
|
README.md,
|
||||||
|
);
|
||||||
|
target = 47FF23248747DD7CB666CB91 /* bitchatTests_macOS */;
|
||||||
|
};
|
||||||
|
C5E027A82ECCDFE200BD6012 /* Exceptions for "bitchatTests" folder in "bitchatTests_iOS" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
Localization/PrimaryLocalizationKeys.json,
|
||||||
|
README.md,
|
||||||
|
);
|
||||||
|
target = 6CB97DF2EA57234CB3E563B8 /* bitchatTests_iOS */;
|
||||||
|
};
|
||||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
@@ -118,6 +136,10 @@
|
|||||||
};
|
};
|
||||||
A6E32D412E762EAE0032EA8A /* bitchatTests */ = {
|
A6E32D412E762EAE0032EA8A /* bitchatTests */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
C5E027A82ECCDFE200BD6012 /* Exceptions for "bitchatTests" folder in "bitchatTests_iOS" target */,
|
||||||
|
C5E027A52ECCDFD700BD6012 /* Exceptions for "bitchatTests" folder in "bitchatTests_macOS" target */,
|
||||||
|
);
|
||||||
path = bitchatTests;
|
path = bitchatTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -213,6 +235,7 @@
|
|||||||
buildConfigurationList = 1C27B5BA3DB46DDF0DBFEF62 /* Build configuration list for PBXNativeTarget "bitchatTests_macOS" */;
|
buildConfigurationList = 1C27B5BA3DB46DDF0DBFEF62 /* Build configuration list for PBXNativeTarget "bitchatTests_macOS" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
5C22AA7B9ACC5A861445C769 /* Sources */,
|
5C22AA7B9ACC5A861445C769 /* Sources */,
|
||||||
|
C5E027A42ECCDFD700BD6012 /* Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -245,6 +268,7 @@
|
|||||||
buildConfigurationList = 38C4AF6313E5037F25CEF30B /* Build configuration list for PBXNativeTarget "bitchatTests_iOS" */;
|
buildConfigurationList = 38C4AF6313E5037F25CEF30B /* Build configuration list for PBXNativeTarget "bitchatTests_iOS" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
865C8403EF02C089369A9FCB /* Sources */,
|
865C8403EF02C089369A9FCB /* Sources */,
|
||||||
|
C5E027A72ECCDFE200BD6012 /* Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -343,6 +367,16 @@
|
|||||||
E0A1B2C3D4E5F6012345678D /* relays/online_relays_gps.csv in Resources */,
|
E0A1B2C3D4E5F6012345678D /* relays/online_relays_gps.csv in Resources */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
C5E027A42ECCDFD700BD6012 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
};
|
||||||
|
C5E027A72ECCDFE200BD6012 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
};
|
||||||
CD6E8F32BC38357473954F97 /* Resources */ = {
|
CD6E8F32BC38357473954F97 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
files = (
|
files = (
|
||||||
|
|||||||
@@ -77,9 +77,7 @@
|
|||||||
/// - Noise Specification: http://www.noiseprotocol.org/noise.html
|
/// - Noise Specification: http://www.noiseprotocol.org/noise.html
|
||||||
///
|
///
|
||||||
|
|
||||||
#if !NOISE_TESTS
|
|
||||||
import BitLogger
|
import BitLogger
|
||||||
#endif
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
import CryptoKit
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
// MARK: - Minimal Mocks for Dependencies
|
|
||||||
|
|
||||||
protocol KeychainManagerProtocol {
|
|
||||||
func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool
|
|
||||||
func getIdentityKey(forKey key: String) -> Data?
|
|
||||||
func deleteIdentityKey(forKey key: String) -> Bool
|
|
||||||
func deleteAllKeychainData() -> Bool
|
|
||||||
func secureClear(_ data: inout Data)
|
|
||||||
func secureClear(_ string: inout String)
|
|
||||||
func verifyIdentityKeyExists() -> Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MockKeychain: KeychainManagerProtocol {
|
|
||||||
func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool { true }
|
|
||||||
func getIdentityKey(forKey key: String) -> Data? { nil }
|
|
||||||
func deleteIdentityKey(forKey key: String) -> Bool { true }
|
|
||||||
func deleteAllKeychainData() -> Bool { true }
|
|
||||||
func secureClear(_ data: inout Data) { data.resetBytes(in: 0..<data.count) }
|
|
||||||
func secureClear(_ string: inout String) { string = "" }
|
|
||||||
func verifyIdentityKeyExists() -> Bool { true }
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SecureLogCategory { case encryption, security }
|
|
||||||
|
|
||||||
struct SecureLogger {
|
|
||||||
static func debug(_ message: String, category: SecureLogCategory? = nil) {}
|
|
||||||
static func info(_ message: Any) {}
|
|
||||||
static func warning(_ message: String, category: SecureLogCategory) {}
|
|
||||||
static func error(_ message: Any, category: SecureLogCategory? = nil) {}
|
|
||||||
static func logKeyOperation(_ op: String, keyType: String, success: Bool) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SecureLogEvent {
|
|
||||||
case handshakeCompleted(peerID: String)
|
|
||||||
case sessionExpired(peerID: String)
|
|
||||||
case authenticationFailed(peerID: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SecureLogger {
|
|
||||||
static func error(_ event: SecureLogEvent) {}
|
|
||||||
static func info(_ event: SecureLogEvent) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Test Vector Structure
|
|
||||||
|
|
||||||
struct NoiseTestVector: Codable {
|
|
||||||
let protocol_name: String
|
|
||||||
let init_prologue: String
|
|
||||||
let init_static: String
|
|
||||||
let init_ephemeral: String
|
|
||||||
let init_psks: [String]?
|
|
||||||
let resp_prologue: String
|
|
||||||
let resp_static: String
|
|
||||||
let resp_ephemeral: String
|
|
||||||
let resp_psks: [String]?
|
|
||||||
let handshake_hash: String?
|
|
||||||
let messages: [TestMessage]
|
|
||||||
|
|
||||||
struct TestMessage: Codable {
|
|
||||||
let payload: String
|
|
||||||
let ciphertext: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Extensions
|
|
||||||
|
|
||||||
extension Data {
|
|
||||||
init?(hex: String) {
|
|
||||||
let cleaned = hex.replacingOccurrences(of: " ", with: "")
|
|
||||||
guard cleaned.count % 2 == 0 else { return nil }
|
|
||||||
var data = Data(capacity: cleaned.count / 2)
|
|
||||||
var index = cleaned.startIndex
|
|
||||||
while index < cleaned.endIndex {
|
|
||||||
let nextIndex = cleaned.index(index, offsetBy: 2)
|
|
||||||
guard let byte = UInt8(cleaned[index..<nextIndex], radix: 16) else { return nil }
|
|
||||||
data.append(byte)
|
|
||||||
index = nextIndex
|
|
||||||
}
|
|
||||||
self = data
|
|
||||||
}
|
|
||||||
|
|
||||||
func hexString() -> String {
|
|
||||||
map { String(format: "%02x", $0) }.joined()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Test Runner
|
|
||||||
|
|
||||||
func runNoiseTests() {
|
|
||||||
print("=== Noise Protocol Test Vector Runner ===\n")
|
|
||||||
|
|
||||||
// Load test vectors
|
|
||||||
guard let testData = try? Data(contentsOf: URL(fileURLWithPath: "NoiseTestVectors.json")),
|
|
||||||
let testVectors = try? JSONDecoder().decode([NoiseTestVector].self, from: testData)
|
|
||||||
else {
|
|
||||||
print("❌ Failed to load test vectors")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Found \(testVectors.count) test vector(s)\n")
|
|
||||||
|
|
||||||
for (index, testVector) in testVectors.enumerated() {
|
|
||||||
print("=== Test Vector \(index + 1) ===")
|
|
||||||
print("Protocol: \(testVector.protocol_name)")
|
|
||||||
runSingleTest(testVector)
|
|
||||||
print("")
|
|
||||||
}
|
|
||||||
|
|
||||||
print("=== All Test Vectors Passed! ===")
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSingleTest(_ testVector: NoiseTestVector) {
|
|
||||||
|
|
||||||
// Parse test inputs
|
|
||||||
guard let initStatic = Data(hex: testVector.init_static),
|
|
||||||
let initEphemeral = Data(hex: testVector.init_ephemeral),
|
|
||||||
let respStatic = Data(hex: testVector.resp_static),
|
|
||||||
let respEphemeral = Data(hex: testVector.resp_ephemeral),
|
|
||||||
let prologue = Data(hex: testVector.init_prologue)
|
|
||||||
else {
|
|
||||||
print("❌ Failed to parse test vector hex strings")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
let expectedHash = testVector.handshake_hash.flatMap { Data(hex: $0) }
|
|
||||||
|
|
||||||
// Create keys
|
|
||||||
guard
|
|
||||||
let initStaticKey = try? Curve25519.KeyAgreement.PrivateKey(rawRepresentation: initStatic),
|
|
||||||
let initEphemeralKey = try? Curve25519.KeyAgreement.PrivateKey(
|
|
||||||
rawRepresentation: initEphemeral),
|
|
||||||
let respStaticKey = try? Curve25519.KeyAgreement.PrivateKey(rawRepresentation: respStatic),
|
|
||||||
let respEphemeralKey = try? Curve25519.KeyAgreement.PrivateKey(
|
|
||||||
rawRepresentation: respEphemeral)
|
|
||||||
else {
|
|
||||||
print("❌ Failed to create keys from test vectors")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
let keychain = MockKeychain()
|
|
||||||
|
|
||||||
// Create handshake states
|
|
||||||
let initiatorHandshake = NoiseHandshakeState(
|
|
||||||
role: .initiator,
|
|
||||||
pattern: .XX,
|
|
||||||
keychain: keychain,
|
|
||||||
localStaticKey: initStaticKey,
|
|
||||||
prologue: prologue,
|
|
||||||
predeterminedEphemeralKey: initEphemeralKey
|
|
||||||
)
|
|
||||||
|
|
||||||
let responderHandshake = NoiseHandshakeState(
|
|
||||||
role: .responder,
|
|
||||||
pattern: .XX,
|
|
||||||
keychain: keychain,
|
|
||||||
localStaticKey: respStaticKey,
|
|
||||||
prologue: prologue,
|
|
||||||
predeterminedEphemeralKey: respEphemeralKey
|
|
||||||
)
|
|
||||||
|
|
||||||
print("\n--- Handshake Phase ---")
|
|
||||||
|
|
||||||
// Message 1: Initiator -> Responder (e)
|
|
||||||
guard let msg1 = try? initiatorHandshake.writeMessage() else {
|
|
||||||
print("❌ Failed to write message 1")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
print("✓ Message 1: Initiator sent ephemeral (\(msg1.count) bytes)")
|
|
||||||
|
|
||||||
guard (try? responderHandshake.readMessage(msg1)) != nil else {
|
|
||||||
print("❌ Failed to read message 1")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
print("✓ Message 1: Responder received")
|
|
||||||
|
|
||||||
// Message 2: Responder -> Initiator (e, ee, s, es)
|
|
||||||
guard let msg2 = try? responderHandshake.writeMessage() else {
|
|
||||||
print("❌ Failed to write message 2")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
print("✓ Message 2: Responder sent (\(msg2.count) bytes)")
|
|
||||||
|
|
||||||
guard (try? initiatorHandshake.readMessage(msg2)) != nil else {
|
|
||||||
print("❌ Failed to read message 2")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
print("✓ Message 2: Initiator received")
|
|
||||||
|
|
||||||
// Message 3: Initiator -> Responder (s, se)
|
|
||||||
guard let msg3 = try? initiatorHandshake.writeMessage() else {
|
|
||||||
print("❌ Failed to write message 3")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
print("✓ Message 3: Initiator sent (\(msg3.count) bytes)")
|
|
||||||
|
|
||||||
guard (try? responderHandshake.readMessage(msg3)) != nil else {
|
|
||||||
print("❌ Failed to read message 3")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
print("✓ Message 3: Responder received")
|
|
||||||
|
|
||||||
// Verify handshake hash
|
|
||||||
let initiatorHash = initiatorHandshake.getHandshakeHash()
|
|
||||||
let responderHash = responderHandshake.getHandshakeHash()
|
|
||||||
|
|
||||||
if initiatorHash != responderHash {
|
|
||||||
print("❌ Initiator and responder hashes don't match!")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let expectedHash = expectedHash {
|
|
||||||
if initiatorHash == expectedHash {
|
|
||||||
print("✓ Handshake hash verified")
|
|
||||||
} else {
|
|
||||||
print("⚠️ Handshake hash differs from test vector (may be implementation-specific)")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✓ Handshake complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get transport ciphers
|
|
||||||
guard let (initSend, initRecv) = try? initiatorHandshake.getTransportCiphers(),
|
|
||||||
let (respSend, respRecv) = try? responderHandshake.getTransportCiphers()
|
|
||||||
else {
|
|
||||||
print("❌ Failed to split to transport ciphers")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n--- Transport Phase ---")
|
|
||||||
|
|
||||||
// Test transport messages
|
|
||||||
var passedMessages = 0
|
|
||||||
for (index, testMsg) in testVector.messages.enumerated() {
|
|
||||||
guard let payload = Data(hex: testMsg.payload) else {
|
|
||||||
print("❌ Message \(index + 1): Failed to parse payload hex")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alternate between initiator and responder sending
|
|
||||||
let (sender, receiver): (NoiseCipherState, NoiseCipherState)
|
|
||||||
let direction: String
|
|
||||||
if index % 2 == 0 {
|
|
||||||
sender = initSend
|
|
||||||
receiver = respRecv
|
|
||||||
direction = "Initiator → Responder"
|
|
||||||
} else {
|
|
||||||
sender = respSend
|
|
||||||
receiver = initRecv
|
|
||||||
direction = "Responder → Initiator"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt
|
|
||||||
guard let ciphertext = try? sender.encrypt(plaintext: payload) else {
|
|
||||||
print("❌ Message \(index + 1): Encryption failed")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt
|
|
||||||
guard let decrypted = try? receiver.decrypt(ciphertext: ciphertext) else {
|
|
||||||
print("❌ Message \(index + 1): Decryption failed")
|
|
||||||
print(" Ciphertext: \(ciphertext.hexString())")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if decrypted == payload {
|
|
||||||
print(
|
|
||||||
"✓ Message \(index + 1) (\(direction)): Encrypt/decrypt successful (\(payload.count) bytes)"
|
|
||||||
)
|
|
||||||
passedMessages += 1
|
|
||||||
} else {
|
|
||||||
print("❌ Message \(index + 1): Decrypted payload mismatch!")
|
|
||||||
print(" Expected: \(payload.hexString())")
|
|
||||||
print(" Got: \(decrypted.hexString())")
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("✓ Test Passed!")
|
|
||||||
print(" Handshake: ✓")
|
|
||||||
print(" Transport Messages: \(passedMessages)/\(testVector.messages.count) ✓")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Main Entry Point
|
|
||||||
|
|
||||||
@main
|
|
||||||
struct NoiseTestRunner {
|
|
||||||
static func main() {
|
|
||||||
runNoiseTests()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"protocol_name": "Noise_XX_25519_ChaChaPoly_SHA256",
|
|
||||||
"init_prologue": "4a6f686e2047616c74",
|
|
||||||
"init_static": "e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1",
|
|
||||||
"init_ephemeral": "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",
|
|
||||||
"resp_prologue": "4a6f686e2047616c74",
|
|
||||||
"resp_static": "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",
|
|
||||||
"resp_ephemeral": "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",
|
|
||||||
"handshake_hash": "c8e5f64e846193be2a834104c2a009868d6c9f3bd3c186299888b488b2f1f58e",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"payload": "4c756477696720766f6e204d69736573",
|
|
||||||
"ciphertext": "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c79444c756477696720766f6e204d69736573"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "4d757272617920526f746862617264",
|
|
||||||
"ciphertext": "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f14480884381cbad1f276e038c48378ffce2b65285e08d6b68aaa3629a5a8639392490e5b9bd5269c2f1e4f488ed8831161f19b7815528f8982ffe09be9b5c412f8a0db50f8814c7194e83f23dbd8d162c9326ad"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "462e20412e20486179656b",
|
|
||||||
"ciphertext": "c7195ffacac1307ff99046f219750fc47693e23c3cb08b89c2af808b444850a80ae475b9df0f169ae80a89be0865b57f58c9fea0d4ec82a286427402f113e4b6ae769a1d95941d49b25030"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "4361726c204d656e676572",
|
|
||||||
"ciphertext": "96763ed773f8e47bb3712f0e29b3060ffc956ffc146cee53d5e1df"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "4a65616e2d426170746973746520536179",
|
|
||||||
"ciphertext": "3e40f15f6f3a46ae446b253bf8b1d9ffb6ed9b174d272328ff91a7e2e5c79c07f5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "457567656e2042f6686d20766f6e2042617765726b",
|
|
||||||
"ciphertext": "eb3f3515110702e047a6c9da4478b6ead94873c11c0f2d710ddb3f09fce024b3a58502ae3f"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"protocol_name": "Noise_XX_25519_ChaChaPoly_SHA256",
|
|
||||||
"init_prologue": "5468657265206973206e6f20726967687420616e642077726f6e672e2054686572652773206f6e6c792066756e20616e6420626f72696e672e",
|
|
||||||
"init_psks": [],
|
|
||||||
"init_static": "7dec208517a3b81a2861d7a71266d5d6dc944c5a8816634a86fe63198a0148ee",
|
|
||||||
"init_ephemeral": "a32daf21e93c0131495ce1d903181fde81cc46937daaeb990bae7c992709421e",
|
|
||||||
"resp_prologue": "5468657265206973206e6f20726967687420616e642077726f6e672e2054686572652773206f6e6c792066756e20616e6420626f72696e672e",
|
|
||||||
"resp_psks": [],
|
|
||||||
"resp_static": "4d0aed5098e3b4ef20357e9f686ce66204c792b358da2e475017d6c485304881",
|
|
||||||
"resp_ephemeral": "4eece0f195d026db035ff987597c429d3ad3bcc2944df37d649528951b2a27c5",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"payload": "d03c489139e645d0711a3c9e810d776b46a84912463fafa87b884eebf242dc34",
|
|
||||||
"ciphertext": "f9fa868ba97ab8a2686deccfaad5a484ee10a5bb85e3d1dce015a84797f92818d03c489139e645d0711a3c9e810d776b46a84912463fafa87b884eebf242dc34"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "d8190a92f7dc0c93dbea9118ba8055751fb7c6590c416ffbd419964132b99a85",
|
|
||||||
"ciphertext": "8c4e6fdb7d09d501a86f7eca5c234522751706ed409182c05cdf5f827d4dae47b81c6c5f43b025692c24391eefee725c17d8cb0fbe3e4abb8aedf42c4fd2592d4ea48ac08989d6ae8b4adae08b2c34087c808c7aa55a63c02b0fab9e930612336bd43eaea04d3c670a0a146691aa9cc9d357872320dc735dbc48580cffb553db"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "77891b19dcb92ef7c055b672c4a5aa7fdf1c84146b8b303459022729473ce254",
|
|
||||||
"ciphertext": "933ca6b5ed60df3df66121f0ab49a09e49efa45c613a86a3cecbf4c535cef2f83f72b42837b18e3572f2fdc2b74c331e2368a545cef54bdca081678ab0e9dd5348122459e0c034c851984d88ce610963d43cde6cfe73a67fbd5a63e8bfca96d0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "d7efdf988072881941db045a42882433817555128fbf5663e56081712ec7d212",
|
|
||||||
"ciphertext": "54ef0ff0629e1aaa7685a2806ab111cba76b52331f2642276736f415868eacb69ab2577f3bda0cbf72f879685f6ed25f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"payload": "dd7bf01a588bafb52c6cfba952e5d8fe35cc2b3f92b4730ae2474615157345ce",
|
|
||||||
"ciphertext": "356be70f110306d5c699bb834bb9d58d909e325924dfbec972e406e6f294dc63e1daebefe8a62a334facc8048ab4ad66"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -6,11 +6,53 @@
|
|||||||
// For more information, see <https://unlicense.org>
|
// For more information, see <https://unlicense.org>
|
||||||
//
|
//
|
||||||
|
|
||||||
import Testing
|
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
|
||||||
@testable import bitchat
|
@testable import bitchat
|
||||||
|
|
||||||
|
// MARK: - Test Vector Support
|
||||||
|
|
||||||
|
struct NoiseTestVector: Codable {
|
||||||
|
let protocol_name: String
|
||||||
|
let init_prologue: String
|
||||||
|
let init_static: String
|
||||||
|
let init_ephemeral: String
|
||||||
|
let init_psks: [String]?
|
||||||
|
let resp_prologue: String
|
||||||
|
let resp_static: String
|
||||||
|
let resp_ephemeral: String
|
||||||
|
let resp_psks: [String]?
|
||||||
|
let handshake_hash: String?
|
||||||
|
let messages: [TestMessage]
|
||||||
|
|
||||||
|
struct TestMessage: Codable {
|
||||||
|
let payload: String
|
||||||
|
let ciphertext: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
init?(hex: String) {
|
||||||
|
let cleaned = hex.replacingOccurrences(of: " ", with: "")
|
||||||
|
guard cleaned.count % 2 == 0 else { return nil }
|
||||||
|
var data = Data(capacity: cleaned.count / 2)
|
||||||
|
var index = cleaned.startIndex
|
||||||
|
while index < cleaned.endIndex {
|
||||||
|
let nextIndex = cleaned.index(index, offsetBy: 2)
|
||||||
|
guard let byte = UInt8(cleaned[index..<nextIndex], radix: 16) else { return nil }
|
||||||
|
data.append(byte)
|
||||||
|
index = nextIndex
|
||||||
|
}
|
||||||
|
self = data
|
||||||
|
}
|
||||||
|
|
||||||
|
func hexString() -> String {
|
||||||
|
map { String(format: "%02x", $0) }.joined()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct NoiseProtocolTests {
|
struct NoiseProtocolTests {
|
||||||
|
|
||||||
private let aliceKey = Curve25519.KeyAgreement.PrivateKey()
|
private let aliceKey = Curve25519.KeyAgreement.PrivateKey()
|
||||||
@@ -69,8 +111,12 @@ struct NoiseProtocolTests {
|
|||||||
#expect(bobSession.isEstablished())
|
#expect(bobSession.isEstablished())
|
||||||
|
|
||||||
// Verify they have each other's static keys
|
// Verify they have each other's static keys
|
||||||
#expect(aliceSession.getRemoteStaticPublicKey()?.rawRepresentation == bobKey.publicKey.rawRepresentation)
|
#expect(
|
||||||
#expect(bobSession.getRemoteStaticPublicKey()?.rawRepresentation == aliceKey.publicKey.rawRepresentation)
|
aliceSession.getRemoteStaticPublicKey()?.rawRepresentation
|
||||||
|
== bobKey.publicKey.rawRepresentation)
|
||||||
|
#expect(
|
||||||
|
bobSession.getRemoteStaticPublicKey()?.rawRepresentation
|
||||||
|
== aliceKey.publicKey.rawRepresentation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handshakeStateValidation() throws {
|
@Test func handshakeStateValidation() throws {
|
||||||
@@ -190,11 +236,13 @@ struct NoiseProtocolTests {
|
|||||||
#expect(message2 != nil)
|
#expect(message2 != nil)
|
||||||
|
|
||||||
// Continue handshake
|
// Continue handshake
|
||||||
let message3 = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: message2!)
|
let message3 = try aliceManager.handleIncomingHandshake(
|
||||||
|
from: alicePeerID, message: message2!)
|
||||||
#expect(message3 != nil)
|
#expect(message3 != nil)
|
||||||
|
|
||||||
// Complete handshake
|
// Complete handshake
|
||||||
let finalMessage = try bobManager.handleIncomingHandshake(from: bobPeerID, message: message3!)
|
let finalMessage = try bobManager.handleIncomingHandshake(
|
||||||
|
from: bobPeerID, message: message3!)
|
||||||
#expect(finalMessage == nil)
|
#expect(finalMessage == nil)
|
||||||
|
|
||||||
// Both should have established sessions
|
// Both should have established sessions
|
||||||
@@ -258,11 +306,19 @@ struct NoiseProtocolTests {
|
|||||||
|
|
||||||
@Test func sessionIsolation() throws {
|
@Test func sessionIsolation() throws {
|
||||||
// Create two separate session pairs
|
// Create two separate session pairs
|
||||||
let aliceSession1 = NoiseSession(peerID: PeerID(str: "peer1"), role: .initiator, keychain: mockKeychain, localStaticKey: aliceKey)
|
let aliceSession1 = NoiseSession(
|
||||||
let bobSession1 = NoiseSession(peerID: PeerID(str: "alice1"), role: .responder, keychain: mockKeychain, localStaticKey: bobKey)
|
peerID: PeerID(str: "peer1"), role: .initiator, keychain: mockKeychain,
|
||||||
|
localStaticKey: aliceKey)
|
||||||
|
let bobSession1 = NoiseSession(
|
||||||
|
peerID: PeerID(str: "alice1"), role: .responder, keychain: mockKeychain,
|
||||||
|
localStaticKey: bobKey)
|
||||||
|
|
||||||
let aliceSession2 = NoiseSession(peerID: PeerID(str: "peer2"), role: .initiator, keychain: mockKeychain, localStaticKey: aliceKey)
|
let aliceSession2 = NoiseSession(
|
||||||
let bobSession2 = NoiseSession(peerID: PeerID(str: "alice2"), role: .responder, keychain: mockKeychain, localStaticKey: bobKey)
|
peerID: PeerID(str: "peer2"), role: .initiator, keychain: mockKeychain,
|
||||||
|
localStaticKey: aliceKey)
|
||||||
|
let bobSession2 = NoiseSession(
|
||||||
|
peerID: PeerID(str: "alice2"), role: .responder, keychain: mockKeychain,
|
||||||
|
localStaticKey: bobKey)
|
||||||
|
|
||||||
// Establish both pairs
|
// Establish both pairs
|
||||||
try performHandshake(initiator: aliceSession1, responder: bobSession1)
|
try performHandshake(initiator: aliceSession1, responder: bobSession1)
|
||||||
@@ -305,17 +361,20 @@ struct NoiseProtocolTests {
|
|||||||
_ = try aliceManager.decrypt(message2, from: alicePeerID)
|
_ = try aliceManager.decrypt(message2, from: alicePeerID)
|
||||||
|
|
||||||
// Simulate Bob restart by creating new manager with same key
|
// Simulate Bob restart by creating new manager with same key
|
||||||
let bobManagerRestarted = NoiseSessionManager(localStaticKey: bobKey, keychain: mockKeychain)
|
let bobManagerRestarted = NoiseSessionManager(
|
||||||
|
localStaticKey: bobKey, keychain: mockKeychain)
|
||||||
|
|
||||||
// Bob initiates new handshake after restart
|
// Bob initiates new handshake after restart
|
||||||
let newHandshake1 = try bobManagerRestarted.initiateHandshake(with: bobPeerID)
|
let newHandshake1 = try bobManagerRestarted.initiateHandshake(with: bobPeerID)
|
||||||
|
|
||||||
// Alice should accept the new handshake (clearing old session)
|
// Alice should accept the new handshake (clearing old session)
|
||||||
let newHandshake2 = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: newHandshake1)
|
let newHandshake2 = try aliceManager.handleIncomingHandshake(
|
||||||
|
from: alicePeerID, message: newHandshake1)
|
||||||
#expect(newHandshake2 != nil)
|
#expect(newHandshake2 != nil)
|
||||||
|
|
||||||
// Complete the new handshake
|
// Complete the new handshake
|
||||||
let newHandshake3 = try bobManagerRestarted.handleIncomingHandshake(from: bobPeerID, message: newHandshake2!)
|
let newHandshake3 = try bobManagerRestarted.handleIncomingHandshake(
|
||||||
|
from: bobPeerID, message: newHandshake2!)
|
||||||
#expect(newHandshake3 != nil)
|
#expect(newHandshake3 != nil)
|
||||||
_ = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: newHandshake3!)
|
_ = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: newHandshake3!)
|
||||||
|
|
||||||
@@ -328,8 +387,10 @@ struct NoiseProtocolTests {
|
|||||||
|
|
||||||
@Test func nonceDesynchronizationRecovery() throws {
|
@Test func nonceDesynchronizationRecovery() throws {
|
||||||
// Create two sessions
|
// Create two sessions
|
||||||
let aliceSession = NoiseSession(peerID: alicePeerID, role: .initiator, keychain: mockKeychain, localStaticKey: aliceKey)
|
let aliceSession = NoiseSession(
|
||||||
let bobSession = NoiseSession(peerID: bobPeerID, role: .responder, keychain: mockKeychain, localStaticKey: bobKey)
|
peerID: alicePeerID, role: .initiator, keychain: mockKeychain, localStaticKey: aliceKey)
|
||||||
|
let bobSession = NoiseSession(
|
||||||
|
peerID: bobPeerID, role: .responder, keychain: mockKeychain, localStaticKey: bobKey)
|
||||||
|
|
||||||
// Establish sessions
|
// Establish sessions
|
||||||
try performHandshake(initiator: aliceSession, responder: bobSession)
|
try performHandshake(initiator: aliceSession, responder: bobSession)
|
||||||
@@ -361,7 +422,8 @@ struct NoiseProtocolTests {
|
|||||||
|
|
||||||
let messageCount = 100
|
let messageCount = 100
|
||||||
|
|
||||||
try await confirmation("All messages encrypted and decrypted", expectedCount: messageCount) { completion in
|
try await confirmation("All messages encrypted and decrypted", expectedCount: messageCount)
|
||||||
|
{ completion in
|
||||||
var encryptedMessages: [Int: Data] = [:]
|
var encryptedMessages: [Int: Data] = [:]
|
||||||
// Encrypt messages sequentially to avoid nonce races in manager
|
// Encrypt messages sequentially to avoid nonce races in manager
|
||||||
for i in 0..<messageCount {
|
for i in 0..<messageCount {
|
||||||
@@ -454,11 +516,13 @@ struct NoiseProtocolTests {
|
|||||||
let newHandshake1 = try aliceManager.initiateHandshake(with: alicePeerID)
|
let newHandshake1 = try aliceManager.initiateHandshake(with: alicePeerID)
|
||||||
|
|
||||||
// Bob should accept the new handshake even though he has a valid session
|
// Bob should accept the new handshake even though he has a valid session
|
||||||
let newHandshake2 = try bobManager.handleIncomingHandshake(from: bobPeerID, message: newHandshake1)
|
let newHandshake2 = try bobManager.handleIncomingHandshake(
|
||||||
|
from: bobPeerID, message: newHandshake1)
|
||||||
#expect(newHandshake2 != nil, "Bob should accept handshake despite having valid session")
|
#expect(newHandshake2 != nil, "Bob should accept handshake despite having valid session")
|
||||||
|
|
||||||
// Complete the handshake
|
// Complete the handshake
|
||||||
let newHandshake3 = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: newHandshake2!)
|
let newHandshake3 = try aliceManager.handleIncomingHandshake(
|
||||||
|
from: alicePeerID, message: newHandshake2!)
|
||||||
#expect(newHandshake3 != nil)
|
#expect(newHandshake3 != nil)
|
||||||
_ = try bobManager.handleIncomingHandshake(from: bobPeerID, message: newHandshake3!)
|
_ = try bobManager.handleIncomingHandshake(from: bobPeerID, message: newHandshake3!)
|
||||||
|
|
||||||
@@ -489,7 +553,8 @@ struct NoiseProtocolTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// With nonce carried in packet, decryption should not throw here
|
// With nonce carried in packet, decryption should not throw here
|
||||||
let desyncMessage = try aliceManager.encrypt("This now succeeds".data(using: .utf8)!, for: alicePeerID)
|
let desyncMessage = try aliceManager.encrypt(
|
||||||
|
"This now succeeds".data(using: .utf8)!, for: alicePeerID)
|
||||||
#expect(throws: Never.self) {
|
#expect(throws: Never.self) {
|
||||||
try bobManager.decrypt(desyncMessage, from: bobPeerID)
|
try bobManager.decrypt(desyncMessage, from: bobPeerID)
|
||||||
}
|
}
|
||||||
@@ -499,11 +564,13 @@ struct NoiseProtocolTests {
|
|||||||
let rehandshake1 = try bobManager.initiateHandshake(with: bobPeerID)
|
let rehandshake1 = try bobManager.initiateHandshake(with: bobPeerID)
|
||||||
|
|
||||||
// Alice should accept despite having a "valid" (but desynced) session
|
// Alice should accept despite having a "valid" (but desynced) session
|
||||||
let rehandshake2 = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: rehandshake1)
|
let rehandshake2 = try aliceManager.handleIncomingHandshake(
|
||||||
|
from: alicePeerID, message: rehandshake1)
|
||||||
#expect(rehandshake2 != nil, "Alice should accept handshake to fix desync")
|
#expect(rehandshake2 != nil, "Alice should accept handshake to fix desync")
|
||||||
|
|
||||||
// Complete handshake
|
// Complete handshake
|
||||||
let rehandshake3 = try bobManager.handleIncomingHandshake(from: bobPeerID, message: rehandshake2!)
|
let rehandshake3 = try bobManager.handleIncomingHandshake(
|
||||||
|
from: bobPeerID, message: rehandshake2!)
|
||||||
#expect(rehandshake3 != nil)
|
#expect(rehandshake3 != nil)
|
||||||
_ = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: rehandshake3!)
|
_ = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: rehandshake3!)
|
||||||
|
|
||||||
@@ -514,6 +581,18 @@ struct NoiseProtocolTests {
|
|||||||
#expect(decryptedResync == testResynced)
|
#expect(decryptedResync == testResynced)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Test Vector Tests
|
||||||
|
|
||||||
|
@Test func noiseTestVectors() throws {
|
||||||
|
// Load test vectors from bundle
|
||||||
|
let testVectors = try loadTestVectors()
|
||||||
|
|
||||||
|
for (index, testVector) in testVectors.enumerated() {
|
||||||
|
print("Running test vector \(index + 1): \(testVector.protocol_name)")
|
||||||
|
try runTestVector(testVector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
// MARK: - Helper Methods
|
||||||
|
|
||||||
private func performHandshake(initiator: NoiseSession, responder: NoiseSession) throws {
|
private func performHandshake(initiator: NoiseSession, responder: NoiseSession) throws {
|
||||||
@@ -523,10 +602,147 @@ struct NoiseProtocolTests {
|
|||||||
_ = try responder.processHandshakeMessage(msg3)
|
_ = try responder.processHandshakeMessage(msg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func establishManagerSessions(aliceManager: NoiseSessionManager, bobManager: NoiseSessionManager) throws {
|
private func establishManagerSessions(
|
||||||
|
aliceManager: NoiseSessionManager, bobManager: NoiseSessionManager
|
||||||
|
) throws {
|
||||||
let msg1 = try aliceManager.initiateHandshake(with: alicePeerID)
|
let msg1 = try aliceManager.initiateHandshake(with: alicePeerID)
|
||||||
let msg2 = try bobManager.handleIncomingHandshake(from: bobPeerID, message: msg1)!
|
let msg2 = try bobManager.handleIncomingHandshake(from: bobPeerID, message: msg1)!
|
||||||
let msg3 = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: msg2)!
|
let msg3 = try aliceManager.handleIncomingHandshake(from: alicePeerID, message: msg2)!
|
||||||
_ = try bobManager.handleIncomingHandshake(from: bobPeerID, message: msg3)
|
_ = try bobManager.handleIncomingHandshake(from: bobPeerID, message: msg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func loadTestVectors() throws -> [NoiseTestVector] {
|
||||||
|
// Try to load from test bundle
|
||||||
|
let testBundle = Bundle(for: MockKeychain.self)
|
||||||
|
guard let url = testBundle.url(forResource: "NoiseTestVectors", withExtension: "json")
|
||||||
|
else {
|
||||||
|
throw NSError(
|
||||||
|
domain: "NoiseTests", code: 1,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Could not find NoiseTestVectors.json in test bundle"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
|
return try JSONDecoder().decode([NoiseTestVector].self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func runTestVector(_ testVector: NoiseTestVector) throws {
|
||||||
|
// Parse test inputs
|
||||||
|
guard let initStatic = Data(hex: testVector.init_static),
|
||||||
|
let initEphemeral = Data(hex: testVector.init_ephemeral),
|
||||||
|
let respStatic = Data(hex: testVector.resp_static),
|
||||||
|
let respEphemeral = Data(hex: testVector.resp_ephemeral),
|
||||||
|
let prologue = Data(hex: testVector.init_prologue)
|
||||||
|
else {
|
||||||
|
throw NSError(
|
||||||
|
domain: "NoiseTests", code: 2,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "Failed to parse test vector hex strings"])
|
||||||
|
}
|
||||||
|
|
||||||
|
let expectedHash = testVector.handshake_hash.flatMap { Data(hex: $0) }
|
||||||
|
|
||||||
|
// Create keys
|
||||||
|
guard
|
||||||
|
let initStaticKey = try? Curve25519.KeyAgreement.PrivateKey(
|
||||||
|
rawRepresentation: initStatic),
|
||||||
|
let initEphemeralKey = try? Curve25519.KeyAgreement.PrivateKey(
|
||||||
|
rawRepresentation: initEphemeral),
|
||||||
|
let respStaticKey = try? Curve25519.KeyAgreement.PrivateKey(
|
||||||
|
rawRepresentation: respStatic),
|
||||||
|
let respEphemeralKey = try? Curve25519.KeyAgreement.PrivateKey(
|
||||||
|
rawRepresentation: respEphemeral)
|
||||||
|
else {
|
||||||
|
throw NSError(
|
||||||
|
domain: "NoiseTests", code: 3,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "Failed to create keys from test vectors"])
|
||||||
|
}
|
||||||
|
|
||||||
|
let keychain = MockKeychain()
|
||||||
|
|
||||||
|
// Create handshake states
|
||||||
|
let initiatorHandshake = NoiseHandshakeState(
|
||||||
|
role: .initiator,
|
||||||
|
pattern: .XX,
|
||||||
|
keychain: keychain,
|
||||||
|
localStaticKey: initStaticKey,
|
||||||
|
prologue: prologue,
|
||||||
|
predeterminedEphemeralKey: initEphemeralKey
|
||||||
|
)
|
||||||
|
|
||||||
|
let responderHandshake = NoiseHandshakeState(
|
||||||
|
role: .responder,
|
||||||
|
pattern: .XX,
|
||||||
|
keychain: keychain,
|
||||||
|
localStaticKey: respStaticKey,
|
||||||
|
prologue: prologue,
|
||||||
|
predeterminedEphemeralKey: respEphemeralKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message 1: Initiator -> Responder (e)
|
||||||
|
let msg1 = try initiatorHandshake.writeMessage()
|
||||||
|
#expect(!msg1.isEmpty, "Message 1 should not be empty")
|
||||||
|
|
||||||
|
_ = try responderHandshake.readMessage(msg1)
|
||||||
|
|
||||||
|
// Message 2: Responder -> Initiator (e, ee, s, es)
|
||||||
|
let msg2 = try responderHandshake.writeMessage()
|
||||||
|
#expect(!msg2.isEmpty, "Message 2 should not be empty")
|
||||||
|
|
||||||
|
_ = try initiatorHandshake.readMessage(msg2)
|
||||||
|
|
||||||
|
// Message 3: Initiator -> Responder (s, se)
|
||||||
|
let msg3 = try initiatorHandshake.writeMessage()
|
||||||
|
#expect(!msg3.isEmpty, "Message 3 should not be empty")
|
||||||
|
|
||||||
|
_ = try responderHandshake.readMessage(msg3)
|
||||||
|
|
||||||
|
// Verify handshake hash
|
||||||
|
let initiatorHash = initiatorHandshake.getHandshakeHash()
|
||||||
|
let responderHash = responderHandshake.getHandshakeHash()
|
||||||
|
|
||||||
|
#expect(initiatorHash == responderHash, "Initiator and responder hashes should match")
|
||||||
|
|
||||||
|
if let expectedHash = expectedHash {
|
||||||
|
#expect(
|
||||||
|
initiatorHash == expectedHash,
|
||||||
|
"Handshake hash should match expected value from test vector")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get transport ciphers
|
||||||
|
let (initSend, initRecv) = try initiatorHandshake.getTransportCiphers()
|
||||||
|
let (respSend, respRecv) = try responderHandshake.getTransportCiphers()
|
||||||
|
|
||||||
|
// Test transport messages
|
||||||
|
for (index, testMsg) in testVector.messages.enumerated() {
|
||||||
|
guard let payload = Data(hex: testMsg.payload) else {
|
||||||
|
throw NSError(
|
||||||
|
domain: "NoiseTests", code: 4,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey:
|
||||||
|
"Message \(index + 1): Failed to parse payload hex"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternate between initiator and responder sending
|
||||||
|
let (sender, receiver): (NoiseCipherState, NoiseCipherState)
|
||||||
|
if index % 2 == 0 {
|
||||||
|
sender = initSend
|
||||||
|
receiver = respRecv
|
||||||
|
} else {
|
||||||
|
sender = respSend
|
||||||
|
receiver = initRecv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
let ciphertext = try sender.encrypt(plaintext: payload)
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
let decrypted = try receiver.decrypt(ciphertext: ciphertext)
|
||||||
|
|
||||||
|
#expect(
|
||||||
|
decrypted == payload,
|
||||||
|
"Message \(index + 1): Decrypted payload should match original")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"protocol_name": "Noise_XX_25519_ChaChaPoly_SHA256",
|
||||||
|
"init_prologue": "4a6f686e2047616c74",
|
||||||
|
"init_static": "e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1",
|
||||||
|
"init_ephemeral": "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a",
|
||||||
|
"resp_prologue": "4a6f686e2047616c74",
|
||||||
|
"resp_static": "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893",
|
||||||
|
"resp_ephemeral": "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b",
|
||||||
|
"handshake_hash": "c8e5f64e846193be2a834104c2a009868d6c9f3bd3c186299888b488b2f1f58e",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"payload": "4c756477696720766f6e204d69736573",
|
||||||
|
"ciphertext": "ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c79444c756477696720766f6e204d69736573"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "4d757272617920526f746862617264",
|
||||||
|
"ciphertext": "95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f14480884381cbad1f276e038c48378ffce2b65285e08d6b68aaa3629a5a8639392490e5b9bd5269c2f1e4f488ed8831161f19b7815528f8982ffe09be9b5c412f8a0db50f8814c7194e83f23dbd8d162c9326ad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "462e20412e20486179656b",
|
||||||
|
"ciphertext": "c7195ffacac1307ff99046f219750fc47693e23c3cb08b89c2af808b444850a80ae475b9df0f169ae80a89be0865b57f58c9fea0d4ec82a286427402f113e4b6ae769a1d95941d49b25030"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "4361726c204d656e676572",
|
||||||
|
"ciphertext": "96763ed773f8e47bb3712f0e29b3060ffc956ffc146cee53d5e1df"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "4a65616e2d426170746973746520536179",
|
||||||
|
"ciphertext": "3e40f15f6f3a46ae446b253bf8b1d9ffb6ed9b174d272328ff91a7e2e5c79c07f5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "457567656e2042f6686d20766f6e2042617765726b",
|
||||||
|
"ciphertext": "eb3f3515110702e047a6c9da4478b6ead94873c11c0f2d710ddb3f09fce024b3a58502ae3f"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"protocol_name": "Noise_XX_25519_ChaChaPoly_SHA256",
|
||||||
|
"init_prologue": "5468657265206973206e6f20726967687420616e642077726f6e672e2054686572652773206f6e6c792066756e20616e6420626f72696e672e",
|
||||||
|
"init_psks": [],
|
||||||
|
"init_static": "7dec208517a3b81a2861d7a71266d5d6dc944c5a8816634a86fe63198a0148ee",
|
||||||
|
"init_ephemeral": "a32daf21e93c0131495ce1d903181fde81cc46937daaeb990bae7c992709421e",
|
||||||
|
"resp_prologue": "5468657265206973206e6f20726967687420616e642077726f6e672e2054686572652773206f6e6c792066756e20616e6420626f72696e672e",
|
||||||
|
"resp_psks": [],
|
||||||
|
"resp_static": "4d0aed5098e3b4ef20357e9f686ce66204c792b358da2e475017d6c485304881",
|
||||||
|
"resp_ephemeral": "4eece0f195d026db035ff987597c429d3ad3bcc2944df37d649528951b2a27c5",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"payload": "d03c489139e645d0711a3c9e810d776b46a84912463fafa87b884eebf242dc34",
|
||||||
|
"ciphertext": "f9fa868ba97ab8a2686deccfaad5a484ee10a5bb85e3d1dce015a84797f92818d03c489139e645d0711a3c9e810d776b46a84912463fafa87b884eebf242dc34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "d8190a92f7dc0c93dbea9118ba8055751fb7c6590c416ffbd419964132b99a85",
|
||||||
|
"ciphertext": "8c4e6fdb7d09d501a86f7eca5c234522751706ed409182c05cdf5f827d4dae47b81c6c5f43b025692c24391eefee725c17d8cb0fbe3e4abb8aedf42c4fd2592d4ea48ac08989d6ae8b4adae08b2c34087c808c7aa55a63c02b0fab9e930612336bd43eaea04d3c670a0a146691aa9cc9d357872320dc735dbc48580cffb553db"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "77891b19dcb92ef7c055b672c4a5aa7fdf1c84146b8b303459022729473ce254",
|
||||||
|
"ciphertext": "933ca6b5ed60df3df66121f0ab49a09e49efa45c613a86a3cecbf4c535cef2f83f72b42837b18e3572f2fdc2b74c331e2368a545cef54bdca081678ab0e9dd5348122459e0c034c851984d88ce610963d43cde6cfe73a67fbd5a63e8bfca96d0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "d7efdf988072881941db045a42882433817555128fbf5663e56081712ec7d212",
|
||||||
|
"ciphertext": "54ef0ff0629e1aaa7685a2806ab111cba76b52331f2642276736f415868eacb69ab2577f3bda0cbf72f879685f6ed25f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payload": "dd7bf01a588bafb52c6cfba952e5d8fe35cc2b3f92b4730ae2474615157345ce",
|
||||||
|
"ciphertext": "356be70f110306d5c699bb834bb9d58d909e325924dfbec972e406e6f294dc63e1daebefe8a62a334facc8048ab4ad66"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user