|
1 | 1 | import XCTest |
2 | 2 | @testable import CryptoKit25519 |
| 3 | +import CryptoKit |
| 4 | +import CEd25519 |
3 | 5 |
|
| 6 | +@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) |
4 | 7 | final class CryptoKit25519Tests: XCTestCase { |
5 | 8 |
|
6 | | - func testSignature() throws { |
| 9 | + static var allTests = [ |
| 10 | + ("testKeyAgreementPublicKey", testKeyAgreementPublicKey), |
| 11 | + ("testSigningPublicKey", testSigningPublicKey), |
| 12 | + ("testVerifySignature", testVerifySignature), |
| 13 | + ("testSignatureCompatibility", testSignatureCompatibility), |
| 14 | + ("testKeyAgreement", testKeyAgreement) |
| 15 | + ] |
| 16 | + |
| 17 | + func testKeyAgreementPublicKey() throws { |
| 18 | + // Noise protocol test vector |
| 19 | + let sk1 = Data(base64Encoded: "S51mhgw53jFJK9s7CQUnv2bvHqdfEFu2+HMo37uf4zc=")! |
| 20 | + let pk1 = Data(base64Encoded: "Ht4jMICpMF9liuztB+8EztNwtfG7oJmzq8Oex7T1qD8=")! |
| 21 | + // Check CryptoKit |
| 22 | + let key1 = try CryptoKit.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: sk1) |
| 23 | + XCTAssertEqual(key1.publicKey.rawRepresentation, pk1) |
| 24 | + // Check CryptoKit25519 |
| 25 | + let key2 = try CryptoKit25519.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: sk1) |
| 26 | + XCTAssertEqual(key2.publicKey.rawRepresentation, pk1) |
| 27 | + |
| 28 | + // Curve25519 test vector |
| 29 | + let sk2 = Data(base64Encoded: "yAZDncnSxHb/7Y8lgMCIjVirQGv3rjaYh5AhuWu0v1k=")! |
| 30 | + let pk2 = Data(base64Encoded: "G7dZZvLpOjaR3/+UK7KkZqHAi414yj9Nbfi4v6Lk7ig=")! |
| 31 | + |
| 32 | + // Check CryptoKit |
| 33 | + let key3 = try CryptoKit.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: sk2) |
| 34 | + XCTAssertEqual(key3.publicKey.rawRepresentation, pk2) |
| 35 | + |
| 36 | + // Check CryptoKit25519 |
| 37 | + let key4 = try CryptoKit25519.Curve25519.KeyAgreement.PrivateKey(rawRepresentation: sk2) |
| 38 | + XCTAssertEqual(key4.publicKey.rawRepresentation, pk2) |
| 39 | + } |
| 40 | + |
| 41 | + func testSigningPublicKey() throws { |
| 42 | + // Test a few iterations |
| 43 | + //for _ in 0..<10 { |
| 44 | + let sk = Data(base64Encoded: "nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=")! |
| 45 | + let pk = Data(base64Encoded: "11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=")! |
| 46 | + |
| 47 | + // Generate private key through CryptoKit |
| 48 | + let key1 = try CryptoKit.Curve25519.Signing.PrivateKey(rawRepresentation: sk) |
| 49 | + XCTAssertEqual(key1.publicKey.rawRepresentation, pk) |
| 50 | + |
| 51 | + // Compare with CryptoKit25519 |
| 52 | + let key2 = try CryptoKit25519.Curve25519.Signing.PrivateKey(rawRepresentation: sk) |
| 53 | + XCTAssertEqual(key2.publicKey.rawRepresentation, pk) |
| 54 | + } |
| 55 | + |
| 56 | + func testVerifySignature() throws { |
| 57 | + let sk = try CryptoKit25519.Curve25519.Signing.PrivateKey() |
| 58 | + let signature = sk.signature(for: Data()) |
| 59 | + let pk = sk.publicKey |
| 60 | + XCTAssertTrue(pk.isValidSignature(signature, for: Data())) |
| 61 | + } |
| 62 | + |
| 63 | + func testSignatureCompatibility() throws { |
| 64 | + let sk = Data(base64Encoded: "nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=")! |
| 65 | + |
| 66 | + let key1 = try CryptoKit.Curve25519.Signing.PrivateKey(rawRepresentation: sk) |
| 67 | + let key2 = try CryptoKit25519.Curve25519.Signing.PrivateKey(rawRepresentation: sk) |
| 68 | + |
| 69 | + let signature1 = try key1.signature(for: Data()) |
| 70 | + XCTAssertTrue(key1.publicKey.isValidSignature(signature1, for: Data())) |
| 71 | + XCTAssertTrue(key2.publicKey.isValidSignature(signature1, for: Data())) |
| 72 | + |
| 73 | + let signature2 = key2.signature(for: Data()) |
| 74 | + XCTAssertTrue(key1.publicKey.isValidSignature(signature2, for: Data())) |
| 75 | + XCTAssertTrue(key2.publicKey.isValidSignature(signature2, for: Data())) |
| 76 | + } |
| 77 | + |
| 78 | + func testKeyAgreement() throws { |
| 79 | + let salt = "Salt".data(using: .utf8)! |
| 80 | + let sharedInfo = "Info".data(using: .utf8)! |
| 81 | + let keyA = CryptoKit.Curve25519.KeyAgreement.PrivateKey() |
| 82 | + let keyB = try CryptoKit25519.Curve25519.KeyAgreement.PrivateKey() |
| 83 | + |
| 84 | + // Calculate key with CryptoKit |
| 85 | + let pubB = try CryptoKit.Curve25519.KeyAgreement.PublicKey(rawRepresentation: keyB.publicKey.rawRepresentation) |
| 86 | + let s1 = try keyA.sharedSecretFromKeyAgreement(with: pubB) |
| 87 | + let k1 = s1.hkdfDerivedSymmetricKey( |
| 88 | + using: SHA256.self, salt: salt, sharedInfo: sharedInfo, outputByteCount: 32) |
| 89 | + |
| 90 | + let pubA = try CryptoKit25519.Curve25519.KeyAgreement.PublicKey(rawRepresentation: keyA.publicKey.rawRepresentation) |
| 91 | + let s2 = try keyB.sharedSecretFromKeyAgreement(with: pubA) |
| 92 | + let k2 = try s2.hkdfDerivedSymmetricKey( |
| 93 | + using: .sha256, salt: salt, sharedInfo: sharedInfo, outputByteCount: 32) |
| 94 | + |
| 95 | + XCTAssertEqual(k1.rawBytes, k2.rawBytes) |
| 96 | + } |
| 97 | + |
| 98 | + func testEncrypt() throws { |
| 99 | + let message = "Hi there".data(using: .utf8)! |
| 100 | + |
| 101 | + let key1 = CryptoKit.SymmetricKey(size: .bits256) |
| 102 | + let key2 = CryptoKit25519.SymmetricKey(data: key1.rawBytes) |
7 | 103 |
|
| 104 | + let nonce1 = CryptoKit.AES.GCM.Nonce() |
| 105 | + let nonce2 = try CryptoKit25519.AES.GCM.Nonce(data: nonce1.rawRepresentation) |
| 106 | + |
| 107 | + // Encrypt the data |
| 108 | + let encrypted1 = try CryptoKit.AES.GCM.seal(message, using: key1, nonce: nonce1) |
| 109 | + let encrypted2 = try CryptoKit25519.AES.GCM.seal(message, using: key2, nonce: nonce2) |
| 110 | + XCTAssertEqual(encrypted1.ciphertext, encrypted2.ciphertext) |
| 111 | + XCTAssertEqual(encrypted1.nonce.rawRepresentation, nonce1.rawRepresentation) |
| 112 | + XCTAssertEqual(encrypted1.tag, encrypted2.tag) |
| 113 | + XCTAssertEqual(encrypted1.combined!, encrypted2.combined) |
| 114 | + |
| 115 | + // Decrypt the data |
| 116 | + let box1 = try CryptoKit.AES.GCM.SealedBox(combined: encrypted2.combined) |
| 117 | + let box2 = try CryptoKit25519.AES.GCM.SealedBox(combined: encrypted1.combined!) |
| 118 | + |
| 119 | + let decrypted1 = try CryptoKit.AES.GCM.open(box1, using: key1) |
| 120 | + let decrypted2 = try CryptoKit25519.AES.GCM.open(box2, using: key2) |
| 121 | + |
| 122 | + XCTAssertEqual(decrypted1, message) |
| 123 | + XCTAssertEqual(decrypted2, message) |
8 | 124 | } |
| 125 | +} |
9 | 126 |
|
10 | | - static var allTests = [ |
11 | | - ("testSignature", testSignature), |
12 | | - ] |
| 127 | +@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) |
| 128 | +extension CryptoKit.SymmetricKey { |
| 129 | + |
| 130 | + var rawBytes: Data { |
| 131 | + withUnsafeBytes { Data(Array($0)) } |
| 132 | + } |
13 | 133 | } |
| 134 | + |
| 135 | +@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) |
| 136 | +extension CryptoKit.AES.GCM.Nonce { |
| 137 | + |
| 138 | + var rawRepresentation: Data { |
| 139 | + self.withUnsafeBytes { Data(Array($0)) } |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +extension Data { |
| 144 | + |
| 145 | + public func hexEncodedString() -> String { |
| 146 | + return map { String(format: "%02hhx", $0) }.joined() |
| 147 | + } |
| 148 | + |
| 149 | + // Convert 0 ... 9, a ... f, A ...F to their decimal value, |
| 150 | + // return nil for all other input characters |
| 151 | + private func decodeNibble(_ u: UInt16) -> UInt8? { |
| 152 | + switch(u) { |
| 153 | + case 0x30 ... 0x39: |
| 154 | + return UInt8(u - 0x30) |
| 155 | + case 0x41 ... 0x46: |
| 156 | + return UInt8(u - 0x41 + 10) |
| 157 | + case 0x61 ... 0x66: |
| 158 | + return UInt8(u - 0x61 + 10) |
| 159 | + default: |
| 160 | + return nil |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + public init?(hexEncoded string: String) { |
| 165 | + var str = string |
| 166 | + if str.count%2 != 0 { |
| 167 | + // insert 0 to get even number of chars |
| 168 | + str.insert("0", at: str.startIndex) |
| 169 | + } |
| 170 | + |
| 171 | + let utf16 = str.utf16 |
| 172 | + self.init(capacity: utf16.count/2) |
| 173 | + |
| 174 | + var i = utf16.startIndex |
| 175 | + while i != str.utf16.endIndex { |
| 176 | + guard let hi = decodeNibble(utf16[i]), |
| 177 | + let lo = decodeNibble(utf16[utf16.index(i, offsetBy: 1, limitedBy: utf16.endIndex)!]) else { |
| 178 | + return nil |
| 179 | + } |
| 180 | + var value = hi << 4 + lo |
| 181 | + self.append(&value, count: 1) |
| 182 | + i = utf16.index(i, offsetBy: 2, limitedBy: utf16.endIndex)! |
| 183 | + } |
| 184 | + } |
| 185 | +} |
| 186 | + |
0 commit comments