Skip to content

Commit

Permalink
Added more yubikey feature tests to PIVSession and its full stack tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed Feb 14, 2024
1 parent dfa2db0 commit 471e5a3
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 38 deletions.
6 changes: 4 additions & 2 deletions FullStackTests/Tests/PIVFullStackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ final class PIVFullStackTests: XCTestCase {

func testSet3DESManagementKey() throws {
runAuthenticatedPIVTest { session in
guard session.supports(PIVSessionFeature.aesKey) else { print("⚠️ Skip testSet3DESManagementKey()"); return }
let newManagementKey = Data(hexEncodedString: "3ec950f1c126b314a80edd752694c328656db96f1c65cc4f")!
do {
try await session.setManagementKey(newManagementKey, type: .tripleDES, requiresTouch: false)
Expand All @@ -306,6 +307,7 @@ final class PIVFullStackTests: XCTestCase {

func testSetAESManagementKey() throws {
runAuthenticatedPIVTest { session in
guard session.supports(PIVSessionFeature.aesKey) else { print("⚠️ Skip testSetAESManagementKey()"); return }
let newManagementKey = Data(hexEncodedString: "f7ef787b46aa50de066bdade00aee17fc2b710372b722de5")!
do {
try await session.setManagementKey(newManagementKey, type: .AES192, requiresTouch: false)
Expand Down Expand Up @@ -404,7 +406,7 @@ final class PIVFullStackTests: XCTestCase {
func testSerialNumber() throws {
runPIVTest { session in
guard session.supports(PIVSessionFeature.serialNumber) else { print("⚠️ Skip testSerialNumber()"); return }
let serialNumber = try await session.serialNumber()
let serialNumber = try await session.getSerialNumber()
XCTAssert(serialNumber > 0)
print("✅ Got serial number: \(serialNumber)")
}
Expand Down Expand Up @@ -434,7 +436,7 @@ final class PIVFullStackTests: XCTestCase {
}

func testPinMetadata() throws {
runPIVTest { session in
runAuthenticatedPIVTest { session in
guard session.supports(PIVSessionFeature.metadata) else { print("⚠️ Skip testPinMetadata()"); return }
let result = try await session.getPinMetadata()
XCTAssertEqual(result.isDefault, true)
Expand Down
3 changes: 2 additions & 1 deletion FullStackTests/allowed-yubikeys.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
9681725,
13084656,
24636873,
14453003
14453003,
27365403
96 changes: 61 additions & 35 deletions YubiKit/YubiKit/PIV/PIVSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ public final actor PIVSession: Session, InternalSession {
}

public func attestKeyInSlot(slot: PIVSlot) async throws -> SecCertificate {
let apdu = APDU(cla: 0, ins: insAttest, p1: slot.rawValue, p2: 0, type: .extended)
guard self.supports(PIVSessionFeature.attestation) else { throw SessionError.notSupported }
let apdu = APDU(cla: 0, ins: insAttest, p1: slot.rawValue, p2: 0)
guard let connection = _connection else { throw SessionError.noConnection }
let result = try await connection.send(apdu: apdu)
guard let certificate = SecCertificateCreateWithData(nil, result as CFData) else { throw PIVSessionError.dataParseError }
Expand All @@ -274,6 +275,7 @@ public final actor PIVSession: Session, InternalSession {

public func generateKeyInSlot(slot: PIVSlot, type: PIVKeyType, pinPolicy: PIVPinPolicy = .default, touchPolicy: PIVTouchPolicy = .default) async throws -> SecKey {
guard let connection = _connection else { throw SessionError.noConnection }
try checkKeyFeatures(keyType: type, pinPolicy: pinPolicy, touchPolicy: touchPolicy, generateKey: true)
let tlv = TKBERTLVRecord(tag: tagGenAlgorithm, value: type.rawValue.data)
let tlvContainer = TKBERTLVRecord(tag: 0xac, value: tlv.data) // What is 0xac?
let apdu = APDU(cla: 0, ins: insGenerateAsymetric, p1: 0, p2: slot.rawValue, command: tlvContainer.data)
Expand Down Expand Up @@ -313,6 +315,7 @@ public final actor PIVSession: Session, InternalSession {
public func putKey(key: SecKey, inSlot slot: PIVSlot, pinPolicy: PIVPinPolicy, touchPolicy: PIVTouchPolicy) async throws -> PIVKeyType {
guard let connection = _connection else { throw SessionError.noConnection }
guard let keyType = key.type else { throw PIVSessionError.unknownKeyType }
try checkKeyFeatures(keyType: keyType, pinPolicy: pinPolicy, touchPolicy: touchPolicy, generateKey: false)
var error: Unmanaged<CFError>?
guard let cfKeyData = SecKeyCopyExternalRepresentation(key, &error) else { throw error!.takeRetainedValue() as Error }
let keyData = cfKeyData as Data
Expand Down Expand Up @@ -352,6 +355,18 @@ public final actor PIVSession: Session, InternalSession {
return keyType
}

private func checkKeyFeatures(keyType: PIVKeyType, pinPolicy: PIVPinPolicy, touchPolicy: PIVTouchPolicy, generateKey: Bool) throws {
if keyType == .ECCP384 {
guard self.supports(PIVSessionFeature.p384) else { throw SessionError.notSupported }
}
if pinPolicy != .default || touchPolicy != .default {
guard self.supports(PIVSessionFeature.usagePolicy) else { throw SessionError.notSupported }
}
if generateKey && (keyType == .RSA1024 || keyType == .RSA2048) {
guard self.supports(PIVSessionFeature.rsaGeneration) else { throw SessionError.notSupported }
}
}

public func putCertificate(certificate: SecCertificate, inSlot slot:PIVSlot, compress: Bool = false) async throws {
var certData = SecCertificateCopyData(certificate) as Data
if compress {
Expand Down Expand Up @@ -392,6 +407,12 @@ public final actor PIVSession: Session, InternalSession {

public func setManagementKey(_ managementKeyData: Data, type: PIVManagementKeyType, requiresTouch: Bool) async throws {
guard let connection = _connection else { throw SessionError.noConnection }
if requiresTouch {
guard self.supports(PIVSessionFeature.usagePolicy) else { throw SessionError.notSupported }
}
if type == .tripleDES {
guard self.supports(PIVSessionFeature.aesKey) else { throw SessionError.notSupported }
}
let tlv = TKBERTLVRecord(tag: tagSlotCardManagement, value: managementKeyData)
var data = Data([type.rawValue])
data.append(tlv.data)
Expand Down Expand Up @@ -433,6 +454,26 @@ public final actor PIVSession: Session, InternalSession {
guard challengeSent == challengeReturned else { throw PIVSessionError.authenticationFailed }
}

public func getManagementKeyMetadata() async throws -> PIVManagementKeyMetadata {
guard let connection = _connection else { throw SessionError.noConnection }
guard self.supports(PIVSessionFeature.metadata) else { throw SessionError.notSupported }
let apdu = APDU(cla: 0, ins: insGetMetadata, p1: 0, p2: p2SlotCardmanagement)
let result = try await connection.send(apdu: apdu)
guard let records = TKBERTLVRecord.sequenceOfRecords(from: result),
let isDefault = records.recordWithTag(tagMetadataDefault)?.value.bytes[0],
let rawTouchPolicy = records.recordWithTag(tagMetadataTouchPolicy)?.value.bytes[1],
let touchPolicy = PIVManagementKeyMetadata.PIVTouchPolicy(rawValue: rawTouchPolicy)
else { throw PIVSessionError.responseDataNotTLVFormatted }

let keyType: PIVManagementKeyType
if let rawKeyType = records.recordWithTag(tagMetadataAlgorithm)?.value.bytes[0] {
guard let parsedKeyType = PIVManagementKeyType(rawValue: rawKeyType) else { throw PIVSessionError.unknownKeyType }
keyType = parsedKeyType
} else {
keyType = .tripleDES
}
return PIVManagementKeyMetadata(isDefault: isDefault != 0, keyType: keyType, touchPolicy: touchPolicy)
}

public func reset() async throws {
try await blockPin()
Expand All @@ -442,7 +483,7 @@ public final actor PIVSession: Session, InternalSession {
try await connection.send(apdu: apdu)
}

public func serialNumber() async throws -> UInt32 {
public func getSerialNumber() async throws -> UInt32 {
guard self.supports(PIVSessionFeature.serialNumber) else { throw SessionError.notSupported }
guard let connection = _connection else { throw SessionError.noConnection }
let apdu = APDU(cla: 0, ins: insGetSerial, p1: 0, p2: 0)
Expand Down Expand Up @@ -485,8 +526,6 @@ public final actor PIVSession: Session, InternalSession {
return try await _ = changeReference(ins: insResetRetry, p2: p2Pin, valueOne: puk, valueTwo: newPin)
}



public func getPinMetadata() async throws -> PIVPinPukMetadata {
try await getPinPukMetadata(p2: p2Pin)
}
Expand All @@ -495,39 +534,25 @@ public final actor PIVSession: Session, InternalSession {
try await getPinPukMetadata(p2: p2Puk)
}

public func getManagementKeyMetadata() async throws -> PIVManagementKeyMetadata {
guard let connection = _connection else { throw SessionError.noConnection }
let apdu = APDU(cla: 0, ins: insGetMetadata, p1: 0, p2: p2SlotCardmanagement)
let result = try await connection.send(apdu: apdu)
guard let records = TKBERTLVRecord.sequenceOfRecords(from: result),
let isDefault = records.recordWithTag(tagMetadataDefault)?.value.bytes[0],
let rawTouchPolicy = records.recordWithTag(tagMetadataTouchPolicy)?.value.bytes[1],
let touchPolicy = PIVManagementKeyMetadata.PIVTouchPolicy(rawValue: rawTouchPolicy)
else { throw PIVSessionError.responseDataNotTLVFormatted }

let keyType: PIVManagementKeyType
if let rawKeyType = records.recordWithTag(tagMetadataAlgorithm)?.value.bytes[0] {
guard let parsedKeyType = PIVManagementKeyType(rawValue: rawKeyType) else { throw PIVSessionError.unknownKeyType }
keyType = parsedKeyType
} else {
keyType = .tripleDES
}
return PIVManagementKeyMetadata(isDefault: isDefault != 0, keyType: keyType, touchPolicy: touchPolicy)
}

public func getPinAttempts() async throws -> Int {
guard let connection = _connection else { throw SessionError.noConnection }
let apdu = APDU(cla: 0, ins: insVerify, p1: 0, p2: p2Pin)
do {
try await connection.send(apdu: apdu)
return currentPinAttempts
} catch {
guard let responseError = error as? ResponseError else { throw error }
let retries = retriesFrom(responseError: responseError)
if retries < 0 {
throw error
} else {
return retries
if self.supports(PIVSessionFeature.metadata) {
let metadata = try await getPinMetadata()
return metadata.retriesRemaining
} else {
let apdu = APDU(cla: 0, ins: insVerify, p1: 0, p2: p2Pin)
do {
try await connection.send(apdu: apdu)
// Already verified, no way to know true count
return currentPinAttempts
} catch {
guard let responseError = error as? ResponseError else { throw error }
let retries = retriesFrom(responseError: responseError)
if retries < 0 {
throw error
} else {
return retries
}
}
}
}
Expand Down Expand Up @@ -615,6 +640,7 @@ extension PIVSession {

private func getPinPukMetadata(p2: UInt8) async throws -> PIVPinPukMetadata {
guard let connection = _connection else { throw SessionError.noConnection }
guard self.supports(PIVSessionFeature.metadata) else { throw SessionError.notSupported }
let apdu = APDU(cla: 0, ins: insGetMetadata, p1: 0, p2: p2)
let result = try await connection.send(apdu: apdu)
guard let records = TKBERTLVRecord.sequenceOfRecords(from: result),
Expand Down

0 comments on commit 471e5a3

Please sign in to comment.