Skip to content

Commit 67773d7

Browse files
committed
Merge branch 'joao/piv-curve25519-keys'
2 parents cca1602 + 22cde65 commit 67773d7

21 files changed

+2215
-1354
lines changed

FullStackTests/Tests/PIVFullStackTests.swift

Lines changed: 725 additions & 579 deletions
Large diffs are not rendered by default.

FullStackTests/Tests/SCP/SCP11FullStackTests.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,10 @@ final class SCP11bFullStackTests: XCTestCase {
199199

200200
let chain = try await securityDomainSession.getCertificateBundle(scpKeyRef: scpKeyRef)
201201
let first: X509Cert = chain.first!
202-
let publicKey = first.publicKey!.asEC()! // make sure we can read the public key
202+
guard case let .ec(publicKey) = first.publicKey! else {
203+
XCTFail("Expected EC public key")
204+
return
205+
}
203206

204207
let params = try SCP11KeyParams(keyRef: scpKeyRef, pkSdEcka: publicKey)
205208

@@ -297,7 +300,10 @@ extension SecurityDomainSession {
297300

298301
// Upload the CA public key to the YubiKey so it can verify signatures
299302
let ca = Scp11TestData.caCert
300-
let certificatePublicKey = ca.publicKey!.asEC()!
303+
guard case let .ec(certificatePublicKey) = ca.publicKey! else {
304+
XCTFail("Expected EC public key")
305+
return nil
306+
}
301307
try await putKey(keyRef: oceRef, publicKey: certificatePublicKey, replaceKvn: 0)
302308

303309
// Extract the CA certificate's Subject Key Identifier for issuer referencing

FullStackTests/Tests/SCP/SCPFullStackTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class SCPFullStackTests: XCTestCase {
4141
let scpKeyRef = SCPKeyRef(kid: .scp11b, kvn: 0x01)
4242
let certificates = try await securityDomainSession.getCertificateBundle(scpKeyRef: scpKeyRef)
4343
guard let last = certificates.last,
44-
let publicKey = last.publicKey?.asEC()
44+
case let .ec(publicKey) = last.publicKey
4545
else {
4646
XCTFail()
4747
return
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright Yubico AB
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import CryptoKit
16+
import Foundation
17+
import Testing
18+
19+
@testable import YubiKit
20+
21+
/// Tests for Ed25519 and X25519 key handling and validation.
22+
struct Curve25519KeysTests {
23+
24+
// MARK: - Ed25519 Tests
25+
26+
/// Test Ed25519 public key creation with valid data.
27+
@Test func ed25519PublicKeyValidData() {
28+
// Generate a valid Ed25519 key pair using CryptoKit
29+
let cryptoKitPrivateKey = Curve25519.Signing.PrivateKey()
30+
let validKeyData = cryptoKitPrivateKey.publicKey.rawRepresentation
31+
let publicKey = Ed25519.PublicKey(keyData: validKeyData)
32+
33+
#expect(publicKey != nil)
34+
#expect(publicKey?.keyData == validKeyData)
35+
#expect(publicKey?.keyData.count == 32)
36+
}
37+
38+
/// Test Ed25519 public key creation with invalid data.
39+
@Test func ed25519PublicKeyInvalidData() {
40+
let invalidKeyData31 = Data(repeating: 0x01, count: 31)
41+
let invalidKeyData33 = Data(repeating: 0x01, count: 33)
42+
let emptyData = Data()
43+
44+
#expect(Ed25519.PublicKey(keyData: invalidKeyData31) == nil)
45+
#expect(Ed25519.PublicKey(keyData: invalidKeyData33) == nil)
46+
#expect(Ed25519.PublicKey(keyData: emptyData) == nil)
47+
}
48+
49+
/// Test Ed25519 private key creation with valid data.
50+
@Test func ed25519PrivateKeyValidData() {
51+
// Generate a valid Ed25519 key pair using CryptoKit
52+
let cryptoKitPrivateKey = Curve25519.Signing.PrivateKey()
53+
let validSeed = cryptoKitPrivateKey.rawRepresentation
54+
let validPublicKeyData = cryptoKitPrivateKey.publicKey.rawRepresentation
55+
let publicKey = Ed25519.PublicKey(keyData: validPublicKeyData)!
56+
57+
let privateKey = Ed25519.PrivateKey(seed: validSeed, publicKey: publicKey)
58+
59+
#expect(privateKey != nil)
60+
#expect(privateKey?.seed == validSeed)
61+
#expect(privateKey?.publicKey == publicKey)
62+
#expect(privateKey?.seed.count == 32)
63+
}
64+
65+
/// Test Ed25519 private key creation with invalid data.
66+
@Test func ed25519PrivateKeyInvalidData() {
67+
let invalidSeed31 = Data(repeating: 0x02, count: 31)
68+
let invalidSeed33 = Data(repeating: 0x02, count: 33)
69+
// Generate a valid public key for testing
70+
let cryptoKitPrivateKey = Curve25519.Signing.PrivateKey()
71+
let validPublicKeyData = cryptoKitPrivateKey.publicKey.rawRepresentation
72+
let publicKey = Ed25519.PublicKey(keyData: validPublicKeyData)!
73+
74+
#expect(Ed25519.PrivateKey(seed: invalidSeed31, publicKey: publicKey) == nil)
75+
#expect(Ed25519.PrivateKey(seed: invalidSeed33, publicKey: publicKey) == nil)
76+
}
77+
78+
// MARK: - X25519 Tests
79+
80+
/// Test X25519 public key creation with valid data.
81+
@Test func x25519PublicKeyValidData() {
82+
// Generate a valid X25519 key pair using CryptoKit
83+
let cryptoKitPrivateKey = Curve25519.KeyAgreement.PrivateKey()
84+
let validKeyData = cryptoKitPrivateKey.publicKey.rawRepresentation
85+
let publicKey = X25519.PublicKey(keyData: validKeyData)
86+
87+
#expect(publicKey != nil)
88+
#expect(publicKey?.keyData == validKeyData)
89+
#expect(publicKey?.keyData.count == 32)
90+
}
91+
92+
/// Test X25519 public key creation with invalid data.
93+
@Test func x25519PublicKeyInvalidData() {
94+
let invalidKeyData31 = Data(repeating: 0x04, count: 31)
95+
let invalidKeyData33 = Data(repeating: 0x04, count: 33)
96+
let emptyData = Data()
97+
98+
#expect(X25519.PublicKey(keyData: invalidKeyData31) == nil)
99+
#expect(X25519.PublicKey(keyData: invalidKeyData33) == nil)
100+
#expect(X25519.PublicKey(keyData: emptyData) == nil)
101+
}
102+
103+
/// Test X25519 private key creation with valid data.
104+
@Test func x25519PrivateKeyValidData() {
105+
// Generate a valid X25519 key pair using CryptoKit
106+
let cryptoKitPrivateKey = Curve25519.KeyAgreement.PrivateKey()
107+
let validScalar = cryptoKitPrivateKey.rawRepresentation
108+
let validPublicKeyData = cryptoKitPrivateKey.publicKey.rawRepresentation
109+
let publicKey = X25519.PublicKey(keyData: validPublicKeyData)!
110+
111+
let privateKey = X25519.PrivateKey(scalar: validScalar, publicKey: publicKey)
112+
113+
#expect(privateKey != nil)
114+
#expect(privateKey?.scalar == validScalar)
115+
#expect(privateKey?.publicKey == publicKey)
116+
#expect(privateKey?.scalar.count == 32)
117+
}
118+
119+
/// Test X25519 private key creation with invalid data.
120+
@Test func x25519PrivateKeyInvalidData() {
121+
let invalidScalar31 = Data(repeating: 0x05, count: 31)
122+
let invalidScalar33 = Data(repeating: 0x05, count: 33)
123+
// Generate a valid public key for testing
124+
let cryptoKitPrivateKey = Curve25519.KeyAgreement.PrivateKey()
125+
let validPublicKeyData = cryptoKitPrivateKey.publicKey.rawRepresentation
126+
let publicKey = X25519.PublicKey(keyData: validPublicKeyData)!
127+
128+
#expect(X25519.PrivateKey(scalar: invalidScalar31, publicKey: publicKey) == nil)
129+
#expect(X25519.PrivateKey(scalar: invalidScalar33, publicKey: publicKey) == nil)
130+
}
131+
132+
// MARK: - Equality Tests
133+
134+
/// Test Ed25519 key equality.
135+
@Test func ed25519KeyEquality() {
136+
let keyData1 = Data(repeating: 0x07, count: 32)
137+
let keyData2 = Data(repeating: 0x08, count: 32)
138+
139+
let publicKey1a = Ed25519.PublicKey(keyData: keyData1)!
140+
let publicKey1b = Ed25519.PublicKey(keyData: keyData1)!
141+
let publicKey2 = Ed25519.PublicKey(keyData: keyData2)!
142+
143+
#expect(publicKey1a == publicKey1b)
144+
#expect(publicKey1a != publicKey2)
145+
}
146+
147+
/// Test X25519 key equality.
148+
@Test func x25519KeyEquality() {
149+
let keyData1 = Data(repeating: 0x09, count: 32)
150+
let keyData2 = Data(repeating: 0x0A, count: 32)
151+
152+
let publicKey1a = X25519.PublicKey(keyData: keyData1)!
153+
let publicKey1b = X25519.PublicKey(keyData: keyData1)!
154+
let publicKey2 = X25519.PublicKey(keyData: keyData2)!
155+
156+
#expect(publicKey1a == publicKey1b)
157+
#expect(publicKey1a != publicKey2)
158+
}
159+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright Yubico AB
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import CommonCrypto
16+
import CryptoKit
17+
import Foundation
18+
import Testing
19+
20+
@testable import YubiKit
21+
22+
struct PIVDataFormatterTests {
23+
24+
// Test data for ECDSA signing with messages
25+
struct ECDSATestCase: Sendable {
26+
let curve: EC.Curve
27+
let algorithm: PIV.ECDSASignatureAlgorithm
28+
let expectedHex: String
29+
}
30+
31+
@Test(
32+
"Prepare ECDSA Signing",
33+
arguments: [
34+
ECDSATestCase(
35+
curve: .p256,
36+
algorithm: .message(.sha256),
37+
expectedHex: "c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a"
38+
),
39+
ECDSATestCase(
40+
curve: .p384,
41+
algorithm: .message(.sha256),
42+
expectedHex:
43+
"00000000000000000000000000000000c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a"
44+
),
45+
ECDSATestCase(
46+
curve: .p256,
47+
algorithm: .message(.sha1),
48+
expectedHex: "000000000000000000000000d3486ae9136e7856bc42212385ea797094475802"
49+
),
50+
ECDSATestCase(
51+
curve: .p256,
52+
algorithm: .message(.sha512),
53+
expectedHex: "f6cde2a0f819314cdde55fc227d8d7dae3d28cc556222a0a8ad66d91ccad4aad"
54+
),
55+
ECDSATestCase(
56+
curve: .p384,
57+
algorithm: .message(.sha512),
58+
expectedHex:
59+
"f6cde2a0f819314cdde55fc227d8d7dae3d28cc556222a0a8ad66d91ccad4aad6094f517a2182360c9aacf6a3dc32316"
60+
),
61+
]
62+
)
63+
func prepareECDSASigning(testCase: ECDSATestCase) throws {
64+
let data = "Hello world!".data(using: .utf8)!
65+
let result = PIVDataFormatter.prepareDataForECDSASigning(
66+
data,
67+
curve: testCase.curve,
68+
algorithm: testCase.algorithm
69+
)
70+
let expected = Data(hexEncodedString: testCase.expectedHex)!
71+
#expect(result == expected, "Got \(result.hexEncodedString), expected: \(expected.hexEncodedString)")
72+
}
73+
74+
@Test func prepareECDSADigestSigning() throws {
75+
let data = Data(hexEncodedString: "c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a")!
76+
do {
77+
let result = PIVDataFormatter.prepareDataForECDSASigning(
78+
data,
79+
curve: .p256,
80+
algorithm: .digest(.sha256)
81+
)
82+
let expected = Data(hexEncodedString: "c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a")!
83+
#expect(result == expected, "Got \(result.hexEncodedString), expected: \(expected.hexEncodedString)")
84+
}
85+
}
86+
87+
// Test data for RSA signing
88+
struct RSATestCase {
89+
let algorithm: PIV.RSASignatureAlgorithm
90+
let expectedHex: String
91+
}
92+
93+
@Test(
94+
"Prepare RSA Signing",
95+
arguments: [
96+
RSATestCase(
97+
algorithm: .pkcs1v15(.sha256),
98+
expectedHex:
99+
"0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d060960864801650304020105000420c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a"
100+
),
101+
RSATestCase(
102+
algorithm: .pkcs1v15(.sha1),
103+
expectedHex:
104+
"0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a05000414d3486ae9136e7856bc42212385ea797094475802"
105+
),
106+
]
107+
)
108+
func prepareRSASigning(testCase: RSATestCase) throws {
109+
let data = "Hello world!".data(using: .utf8)!
110+
let result = try PIVDataFormatter.prepareDataForRSASigning(
111+
data,
112+
keySize: .bits1024,
113+
algorithm: testCase.algorithm
114+
)
115+
let expected = Data(hexEncodedString: testCase.expectedHex)!
116+
#expect(result == expected, "Got \(result.hexEncodedString), expected: \(expected.hexEncodedString)")
117+
}
118+
119+
@Test func prepareECDSADigestSigningP384WithPadding() throws {
120+
let data = "Hello world!".data(using: .utf8)!
121+
do {
122+
let result = PIVDataFormatter.prepareDataForECDSASigning(
123+
data,
124+
curve: .p384,
125+
algorithm: .digest(.sha256)
126+
)
127+
let expected = Data(
128+
hexEncodedString:
129+
"00000000000000000000000000000000000000000000000000000000000000000000000048656c6c6f20776f726c6421"
130+
)!
131+
#expect(result == expected, "Got \(result.hexEncodedString), expected: \(expected.hexEncodedString)")
132+
}
133+
}
134+
135+
// Test data for RSA decryption
136+
struct RSADecryptionTestCase {
137+
let algorithm: PIV.RSAEncryptionAlgorithm
138+
let encryptedHex: String
139+
}
140+
141+
@Test(
142+
"Extract RSA Encryption",
143+
arguments: [
144+
RSADecryptionTestCase(
145+
algorithm: .pkcs1v15,
146+
encryptedHex:
147+
"00022b781255b78f9570844701748107f506effbea5f0822b41dded192938906cefe16eef190d4cf7f7b0866badf94ca0e4e08fda43e4619edec2703987a56a78aa4c2d36a8f89c43f1f9c0ab681e45a759744ef946d65d95e74536b28b83cdc1c62e36c014c8b4a50c178a54306ce7395240e0048656c6c6f20576f726c6421"
148+
),
149+
RSADecryptionTestCase(
150+
algorithm: .oaep(.sha224),
151+
encryptedHex:
152+
"00bcbb35b6ef5c94a85fb3439a6dabda617a08963cf81023bac19c619b024cb71b8aee25cc30991279c908198ba623fba88547741dbf17a6f2a737ec95542b56b2b429bea8bd3145af7c8f144dcf804b89d3f9de21d6d6dc852fc91c666b8582bf348e1388ac2f54651ae6a1f5355c8d96daf96c922a9f1a499d890412d09454"
153+
),
154+
]
155+
)
156+
func extractRSAEncryption(testCase: RSADecryptionTestCase) throws {
157+
let data = Data(hexEncodedString: testCase.encryptedHex)!
158+
let result = try PIVDataFormatter.extractDataFromRSAEncryption(data, algorithm: testCase.algorithm)
159+
let expected = "Hello World!".data(using: .utf8)!
160+
#expect(result == expected, "Got \(result.hexEncodedString), expected: \(expected.hexEncodedString)")
161+
}
162+
163+
@Test func extractMalformedRSAData() throws {
164+
let data = Data(
165+
hexEncodedString:
166+
"79ce573cfc2bdfe835175ffd4bd01ab35eccfd31e2b009a1943123e9cb2db4878608c821fb96a6c63382aaf1c12ce0f03b83"
167+
)!
168+
do {
169+
let _ = try PIVDataFormatter.extractDataFromRSAEncryption(data, algorithm: .pkcs1v15)
170+
Issue.record("extractDataFromRSAEncryption returned although the data had the wrong size.")
171+
} catch PIV.SessionError.invalidDataSize {
172+
#expect(true, "Failed as expected with invalid data size error")
173+
} catch {
174+
Issue.record("Unexpected error: \(error)")
175+
}
176+
}
177+
}

0 commit comments

Comments
 (0)