Skip to content

Commit

Permalink
Tests for writing and reading compressed certs, delete certs, authent…
Browse files Browse the repository at this point in the history
…icate with management key, set management key and pin functionality.
  • Loading branch information
jensutbult committed Feb 7, 2024
1 parent 40aebb3 commit 757daf0
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 11 deletions.
123 changes: 121 additions & 2 deletions FullStackTests/Tests/PIVFullStackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class PIVFullStackTests: XCTestCase {
let signature = try await session.signWithKeyInSlot(.signature, keyType: .ECCP256, algorithm: .ecdsaSignatureMessageX962SHA256, message: message)
var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(publicKey, SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256, message as CFData, signature as CFData, &error);
XCTAssertTrue(result)
if let error {
XCTFail((error.takeRetainedValue() as Error).localizedDescription)
}
Expand All @@ -35,6 +36,7 @@ final class PIVFullStackTests: XCTestCase {
let signature = try await session.signWithKeyInSlot(.signature, keyType: .RSA1024, algorithm: .rsaSignatureMessagePKCS1v15SHA512, message: message)
var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(publicKey, SecKeyAlgorithm.rsaSignatureMessagePKCS1v15SHA512, message as CFData, signature as CFData, &error);
XCTAssertTrue(result)
if let error {
XCTFail((error.takeRetainedValue() as Error).localizedDescription)
}
Expand Down Expand Up @@ -243,19 +245,136 @@ 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)
let retrievedCertificate = try await session.getCertificateInSlot(.authentication)
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 {
Expand Down
2 changes: 1 addition & 1 deletion YubiKit/YubiKit/Connection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}


Expand Down
16 changes: 8 additions & 8 deletions YubiKit/YubiKit/PIV/PIVSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public enum PIVKeyType: UInt8 {
}
}

public enum PIVVerifyPinResult {
public enum PIVVerifyPinResult: Equatable {
case success(Int)
case fail(Int)
case pinLocked
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 757daf0

Please sign in to comment.