From 757daf0406898a3a3abed7cefbc3dd356a9902f0 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Wed, 7 Feb 2024 17:11:46 +0100 Subject: [PATCH] Tests for writing and reading compressed certs, delete certs, authenticate with management key, set management key and pin functionality. --- FullStackTests/Tests/PIVFullStackTests.swift | 123 ++++++++++++++++++- YubiKit/YubiKit/Connection.swift | 2 +- YubiKit/YubiKit/PIV/PIVSession.swift | 16 +-- 3 files changed, 130 insertions(+), 11 deletions(-) diff --git a/FullStackTests/Tests/PIVFullStackTests.swift b/FullStackTests/Tests/PIVFullStackTests.swift index 669ee09..c624f5c 100644 --- a/FullStackTests/Tests/PIVFullStackTests.swift +++ b/FullStackTests/Tests/PIVFullStackTests.swift @@ -20,6 +20,7 @@ final class PIVFullStackTests: XCTestCase { let signature = try await session.signWithKeyInSlot(.signature, keyType: .ECCP256, algorithm: .ecdsaSignatureMessageX962SHA256, message: message) var error: Unmanaged? let result = SecKeyVerifySignature(publicKey, SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256, message as CFData, signature as CFData, &error); + XCTAssertTrue(result) if let error { XCTFail((error.takeRetainedValue() as Error).localizedDescription) } @@ -35,6 +36,7 @@ final class PIVFullStackTests: XCTestCase { let signature = try await session.signWithKeyInSlot(.signature, keyType: .RSA1024, algorithm: .rsaSignatureMessagePKCS1v15SHA512, message: message) var error: Unmanaged? let result = SecKeyVerifySignature(publicKey, SecKeyAlgorithm.rsaSignatureMessagePKCS1v15SHA512, message as CFData, signature as CFData, &error); + XCTAssertTrue(result) if let error { XCTFail((error.takeRetainedValue() as Error).localizedDescription) } @@ -243,12 +245,11 @@ final class PIVFullStackTests: XCTestCase { let attestKeyData = SecKeyCopyExternalRepresentation(attestKey, nil)! let keyData = SecKeyCopyExternalRepresentation(publicKey, nil)! XCTAssert((attestKeyData as Data) == (keyData as Data)) - } } let testCertificate = SecCertificateCreateWithData(nil, Data(base64Encoded: "MIIBKzCB0qADAgECAhQTuU25u6oazORvKfTleabdQaDUGzAKBggqhkjOPQQDAjAWMRQwEgYDVQQDDAthbW9zLmJ1cnRvbjAeFw0yMTAzMTUxMzU5MjVaFw0yODA1MTcwMDAwMDBaMBYxFDASBgNVBAMMC2Ftb3MuYnVydG9uMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEofwN6S+atSZmzeLK7aSI+mJJwxh0oUBiCOngHLeToYeanrTGvCZQ2AK/R9esnqSxMyBUDp91UO4F6U4c6RTooTAKBggqhkjOPQQDAgNIADBFAiAnj/KUSpW7l5wnenQEbwWudK/7q3WtyrqdB0H1xc258wIhALDLImzu3S+0TT2/ggM95LLWE4Llfa2RQM71bnW6zqqn")! as CFData)! - + func testPutAndReadCertificate() throws { runAuthenticatedPIVTest { session in try await session.putCertificate(certificate: self.testCertificate, inSlot: .authentication, compress: false) @@ -256,6 +257,124 @@ final class PIVFullStackTests: XCTestCase { XCTAssert(self.testCertificate == retrievedCertificate) } } + + func testPutCompressedAndReadCertificate() throws { + runAuthenticatedPIVTest { session in + try await session.putCertificate(certificate: self.testCertificate, inSlot: .authentication, compress: true) + let retrievedCertificate = try await session.getCertificateInSlot(.authentication) + XCTAssert(self.testCertificate == retrievedCertificate) + } + } + + func testPutAndDeleteCertificate() throws { + runAuthenticatedPIVTest { session in + try await session.putCertificate(certificate: self.testCertificate, inSlot: .authentication) + try await session.deleteCertificateInSlot(slot: .authentication) + do { + _ = try await session.getCertificateInSlot(.authentication) + XCTFail("Deleted certificate still present on YubiKey.") + } catch { + guard let error = error as? ResponseError else { XCTFail("Deleted certificate returned unexpected error: \(error)"); return } + XCTAssert(error.responseStatus.status == .fileNotFound) + } + } + } + + func testAuthenticateWithDefaultManagementKey() throws { + runPIVTest { session in + try await session.reset() + let defaultManagementKey = Data(hexEncodedString: "010203040506070801020304050607080102030405060708")! + do { + try await session.authenticateWith(managementKey: defaultManagementKey, keyType: .tripleDES) + } catch { + XCTFail("Failed authenticating with default management key.") + } + } + } + + func testSet3DESManagementKey() throws { + runAuthenticatedPIVTest { session in + let newManagementKey = Data(hexEncodedString: "3ec950f1c126b314a80edd752694c328656db96f1c65cc4f")! + do { + try await session.setManagementKey(newManagementKey, type: .tripleDES, requiresTouch: false) + try await session.authenticateWith(managementKey: newManagementKey, keyType: .tripleDES) + } catch { + XCTFail("Failed setting new management key with: \(error)") + } + } + } + + func testSetAESManagementKey() throws { + runAuthenticatedPIVTest { session in + let newManagementKey = Data(hexEncodedString: "f7ef787b46aa50de066bdade00aee17fc2b710372b722de5")! + do { + try await session.setManagementKey(newManagementKey, type: .AES192, requiresTouch: false) + try await session.authenticateWith(managementKey: newManagementKey, keyType: .AES192) + } catch { + XCTFail("Failed setting new management key with: \(error)") + } + } + } + + func testAuthenticateWithWrongManagementKey() throws { + runPIVTest { session in + try await session.reset() + let defaultManagementKey = Data(hexEncodedString: "010101010101010101010101010101010101010101010101")! + do { + try await session.authenticateWith(managementKey: defaultManagementKey, keyType: .tripleDES) + XCTFail("Successfully authenticated with the wrong management key.") + } catch { + guard let error = error as? ResponseError else { XCTFail("Failed with unexpected error: \(error)"); return } + XCTAssert(error.responseStatus.status == .securityConditionNotSatisfied) + } + } + } + + func testVerifyPIN() throws { + runAuthenticatedPIVTest { session in + do { + let result = try await session.verifyPin("123456") + if case .success(let counter) = result { + XCTAssertEqual(counter, 3) + } else { + XCTFail("Got unexpected result from verifyPin: \(result)") + } + } catch { + XCTFail("Got unexpected error verifying pin: \(error)") + } + } + } + + func testVerifyPINRetryCount() throws { + runAuthenticatedPIVTest { session in + let resultOne = try await session.verifyPin("654321") + if case .fail(let counter) = resultOne { + XCTAssertEqual(counter, 2) + } else { + XCTFail("Got unexpected result from verifyPin: \(resultOne)") + } + let resultTwo = try await session.verifyPin("101010") + if case .fail(let counter) = resultTwo { + XCTAssertEqual(counter, 1) + } else { + XCTFail("Got unexpected result from verifyPin: \(resultTwo)") + } + let resultThree = try await session.verifyPin("142857") + XCTAssert(resultThree == .pinLocked) + let resultFour = try await session.verifyPin("740737") + XCTAssert(resultFour == .pinLocked) + } + } + + func testGetPinAttempts() throws { + runAuthenticatedPIVTest { session in + var count = try await session.getPinAttempts() + XCTAssertEqual(count, 3) + _ = try await session.verifyPin("740601") + count = try await session.getPinAttempts() + XCTAssertEqual(count, 2) + } + } } extension XCTestCase { diff --git a/YubiKit/YubiKit/Connection.swift b/YubiKit/YubiKit/Connection.swift index 7039716..2d484d3 100644 --- a/YubiKit/YubiKit/Connection.swift +++ b/YubiKit/YubiKit/Connection.swift @@ -95,7 +95,7 @@ public enum ConnectionError: Error { /// A ResponseError containing the status code. public struct ResponseError: Error { /// Status code of the response. - let responseStatus: ResponseStatus + public let responseStatus: ResponseStatus } diff --git a/YubiKit/YubiKit/PIV/PIVSession.swift b/YubiKit/YubiKit/PIV/PIVSession.swift index 9379c64..f603d78 100644 --- a/YubiKit/YubiKit/PIV/PIVSession.swift +++ b/YubiKit/YubiKit/PIV/PIVSession.swift @@ -112,7 +112,7 @@ public enum PIVKeyType: UInt8 { } } -public enum PIVVerifyPinResult { +public enum PIVVerifyPinResult: Equatable { case success(Int) case fail(Int) case pinLocked @@ -348,13 +348,13 @@ public final actor PIVSession: Session, InternalSession { return keyType } - public func putCertificate(certificate: SecCertificate, inSlot slot:PIVSlot, compress: Bool) async throws { - var certData = SecCertificateCopyData(certificate) + public func putCertificate(certificate: SecCertificate, inSlot slot:PIVSlot, compress: Bool = false) async throws { + var certData = SecCertificateCopyData(certificate) as Data if compress { - certData = try (certData as NSData).compressed(using: .zlib) + certData = try certData.gzipped() } var data = Data() - data.append(TKBERTLVRecord(tag: tagCertificate, value: certData as Data).data) + data.append(TKBERTLVRecord(tag: tagCertificate, value: certData).data) let isCompressed: UInt8 = compress ? 1 : 0 data.append(TKBERTLVRecord(tag: tagCertificateInfo, value: isCompressed.data).data) data.append(TKBERTLVRecord(tag: tagLRC, value: Data()).data) @@ -386,13 +386,13 @@ public final actor PIVSession: Session, InternalSession { try await putObject(Data(), objectId: slot.objectId) } - public func setManagementKey(_ managementKeyData: Data, type: PIVManagementKeyType, requiresTouch: Bool) async throws -> Data { + public func setManagementKey(_ managementKeyData: Data, type: PIVManagementKeyType, requiresTouch: Bool) async throws { guard let connection = _connection else { throw SessionError.noConnection } let tlv = TKBERTLVRecord(tag: tagSlotCardManagement, value: managementKeyData) var data = Data([type.rawValue]) data.append(tlv.data) let apdu = APDU(cla: 0, ins: insSetManagementKey, p1: 0xff, p2: requiresTouch ? 0xfe : 0xff, command: data, type: .short) - return try await connection.send(apdu: apdu) + try await connection.send(apdu: apdu) } public func authenticateWith(managementKey: Data, keyType: PIVManagementKeyType) async throws { @@ -625,7 +625,7 @@ extension PIVSession { let statusCode = responseError.responseStatus.rawStatus if statusCode == 0x6983 { return 0 - } else if self.version > Version(withString: "1.0.4")! { + } else if self.version < Version(withString: "1.0.4")! { if statusCode >= 0x6300 && statusCode <= 0x63ff { return Int(statusCode & 0xff); }