Skip to content

Commit

Permalink
Implemented support for getting slot metadata. PIV tests now run on y…
Browse files Browse the repository at this point in the history
…ubikeys with arbitrary management key types.
  • Loading branch information
jensutbult committed Feb 21, 2024
1 parent 086b97c commit d8a76b4
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 30 deletions.
61 changes: 56 additions & 5 deletions FullStackTests/Tests/PIVFullStackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,14 @@ final class PIVFullStackTests: XCTestCase {
func testAuthenticateWithDefaultManagementKey() throws {
runPIVTest { session in
do {
try await session.authenticateWith(managementKey: self.defaultManagementKey, keyType: .tripleDES)
let keyType: PIVManagementKeyType
if session.supports(PIVSessionFeature.metadata) {
let metadata = try await session.getManagementKeyMetadata()
keyType = metadata.keyType
} else {
keyType = .tripleDES
}
try await session.authenticateWith(managementKey: self.defaultManagementKey, keyType: keyType)
} catch {
XCTFail("Failed authenticating with default management key.")
}
Expand Down Expand Up @@ -325,7 +332,14 @@ final class PIVFullStackTests: XCTestCase {
runPIVTest { session in
let wrongManagementKey = Data(hexEncodedString: "010101010101010101010101010101010101010101010101")!
do {
try await session.authenticateWith(managementKey: wrongManagementKey, keyType: .tripleDES)
let keyType: PIVManagementKeyType
if session.supports(PIVSessionFeature.metadata) {
let metadata = try await session.getManagementKeyMetadata()
keyType = metadata.keyType
} else {
keyType = .tripleDES
}
try await session.authenticateWith(managementKey: wrongManagementKey, keyType: keyType)
XCTFail("Successfully authenticated with the wrong management key.")
} catch {
guard let error = error as? ResponseError else { XCTFail("Failed with unexpected error: \(error)"); return }
Expand Down Expand Up @@ -400,7 +414,7 @@ final class PIVFullStackTests: XCTestCase {
runPIVTest { session in
let version = session.version
XCTAssertEqual(version.major, 5)
XCTAssert(version.minor == 2 || version.minor == 3 || version.minor == 4)
XCTAssert(version.minor == 2 || version.minor == 3 || version.minor == 4 || version.minor == 7)
print("➡️ Version: \(session.version.major).\(session.version.minor).\(session.version.micro)")
}
}
Expand All @@ -419,8 +433,38 @@ final class PIVFullStackTests: XCTestCase {
guard session.supports(PIVSessionFeature.metadata) else { print("⚠️ Skip testManagementKeyMetadata()"); return }
let metadata = try await session.getManagementKeyMetadata()
XCTAssertEqual(metadata.isDefault, true)
XCTAssertEqual(metadata.keyType, .tripleDES)
print("➡️ Management key type: \(metadata.keyType)")
print("➡️ Management touch policy: \(metadata.touchPolicy)")
}

}

func testSlotMetadata() throws {
runAuthenticatedPIVTest { session in
guard session.supports(PIVSessionFeature.metadata) else { print("⚠️ Skip testSlotMetadata()"); return }
_ = try await session.generateKeyInSlot(slot: .authentication, type: .ECCP256, pinPolicy: .always, touchPolicy: .always)
var metadata = try await session.getSlotMetadata(.authentication)
XCTAssertEqual(metadata.keyType, .ECCP256)
XCTAssertEqual(metadata.pinPolicy, .always)
XCTAssertEqual(metadata.touchPolicy, .always)
XCTAssertEqual(metadata.generated, true)
XCTAssertTrue(metadata.publicKey.count > 0)

_ = try await session.generateKeyInSlot(slot: .authentication, type: .ECCP384, pinPolicy: .never, touchPolicy: .never)
metadata = try await session.getSlotMetadata(.authentication)
XCTAssertEqual(metadata.keyType, .ECCP384)
XCTAssertEqual(metadata.pinPolicy, .never)
XCTAssertEqual(metadata.touchPolicy, .never)
XCTAssertEqual(metadata.generated, true)
XCTAssertTrue(metadata.publicKey.count > 0)

_ = try await session.generateKeyInSlot(slot: .authentication, type: .ECCP256, pinPolicy: .once, touchPolicy: .cached)
metadata = try await session.getSlotMetadata(.authentication)
XCTAssertEqual(metadata.keyType, .ECCP256)
XCTAssertEqual(metadata.pinPolicy, .once)
XCTAssertEqual(metadata.touchPolicy, .cached)
XCTAssertEqual(metadata.generated, true)
XCTAssertTrue(metadata.publicKey.count > 0)
}

}
Expand Down Expand Up @@ -547,7 +591,14 @@ extension XCTestCase {
let session = try await PIVSession.session(withConnection: connection)
try await session.reset()
let defaultManagementKey = Data(hexEncodedString: "010203040506070801020304050607080102030405060708")!
try await session.authenticateWith(managementKey: defaultManagementKey, keyType: .tripleDES)
let keyType: PIVManagementKeyType
if session.supports(PIVSessionFeature.metadata) {
let metadata = try await session.getManagementKeyMetadata()
keyType = metadata.keyType
} else {
keyType = .tripleDES
}
try await session.authenticateWith(managementKey: defaultManagementKey, keyType: keyType)
Logger.test.debug("⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ PIV Session test ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️")
try await test(session)
Logger.test.debug("\(testName) passed")
Expand Down
3 changes: 2 additions & 1 deletion FullStackTests/allowed-yubikeys.csv
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
24636873,
14453003,
27365403,
9681623
9681623,
27365450
80 changes: 56 additions & 24 deletions YubiKit/YubiKit/PIV/PIVSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ import Gzip

/// Touch policy for PIV application.
public enum PIVTouchPolicy: UInt8 {
case `default` = 0x0
case defaultPolicy = 0x0
case never = 0x1
case always = 0x2
case cached = 0x3
}

/// Pin policy for PIV application.
public enum PIVPinPolicy: UInt8 {
case `default` = 0x0
case defaultPolicy = 0x0
case never = 0x1
case nce = 0x2
case once = 0x2
case always = 0x3
};

Expand Down Expand Up @@ -136,18 +136,19 @@ public enum PIVSessionError: Error {

public struct PIVManagementKeyMetadata {

public enum PIVTouchPolicy: UInt8 {
case defaultPolicy = 0x0
case never = 0x1
case always = 0x2
case cached = 0x3
}

public let isDefault: Bool
public let keyType: PIVManagementKeyType
public let touchPolicy: PIVTouchPolicy
}

public struct PIVSlotMetadata {
public let keyType: PIVKeyType
public let pinPolicy: PIVPinPolicy
public let touchPolicy: PIVTouchPolicy
public let generated: Bool
public let publicKey: Data
}

public struct PIVPinPukMetadata {
public let isDefault: Bool
public let retriesTotal: Int
Expand Down Expand Up @@ -274,13 +275,13 @@ public final actor PIVSession: Session, InternalSession {
return certificate
}

public func generateKeyInSlot(slot: PIVSlot, type: PIVKeyType, pinPolicy: PIVPinPolicy = .default, touchPolicy: PIVTouchPolicy = .default) async throws -> SecKey {
public func generateKeyInSlot(slot: PIVSlot, type: PIVKeyType, pinPolicy: PIVPinPolicy = .`defaultPolicy`, touchPolicy: PIVTouchPolicy = .`defaultPolicy`) async throws -> SecKey {
Logger.piv.debug("\(String(describing: self).lastComponent), \(#function)")
guard let connection = _connection else { throw SessionError.noConnection }
try checkKeyFeatures(keyType: type, pinPolicy: pinPolicy, touchPolicy: touchPolicy, generateKey: true)
let records: [TKBERTLVRecord] = [TKBERTLVRecord(tag: tagGenAlgorithm, value: type.rawValue.data),
pinPolicy != .default ? TKBERTLVRecord(tag: tagPinPolicy, value: pinPolicy.rawValue.data) : nil,
touchPolicy != .default ? TKBERTLVRecord(tag: tagTouchpolicy, value: touchPolicy.rawValue.data) : nil].compactMap { $0 }
pinPolicy != .`defaultPolicy` ? TKBERTLVRecord(tag: tagPinPolicy, value: pinPolicy.rawValue.data) : nil,
touchPolicy != .`defaultPolicy` ? TKBERTLVRecord(tag: tagTouchpolicy, value: touchPolicy.rawValue.data) : nil].compactMap { $0 }
let tlvContainer = TKBERTLVRecord(tag: 0xac, records: records)
let apdu = APDU(cla: 0, ins: insGenerateAsymetric, p1: 0, p2: slot.rawValue, command: tlvContainer.data)
let result = try await connection.send(apdu: apdu)
Expand Down Expand Up @@ -349,10 +350,10 @@ public final actor PIVSession: Session, InternalSession {
case .unknown:
throw PIVSessionError.unknownKeyType
}
if pinPolicy != .default {
if pinPolicy != .`defaultPolicy` {
data.append(TKBERTLVRecord(tag: tagPinPolicy, value: pinPolicy.rawValue.data).data)
}
if touchPolicy != .default {
if touchPolicy != .`defaultPolicy` {
data.append(TKBERTLVRecord(tag: tagTouchpolicy, value: touchPolicy.rawValue.data).data)
}
let apdu = APDU(cla: 0, ins: insImportKey, p1: keyType.rawValue, p2: slot.rawValue, command: data, type: .extended)
Expand All @@ -365,7 +366,7 @@ public final actor PIVSession: Session, InternalSession {
if keyType == .ECCP384 {
guard self.supports(PIVSessionFeature.p384) else { throw SessionError.notSupported }
}
if pinPolicy != .default || touchPolicy != .default {
if pinPolicy != .`defaultPolicy` || touchPolicy != .`defaultPolicy` {
guard self.supports(PIVSessionFeature.usagePolicy) else { throw SessionError.notSupported }
}
if generateKey && (keyType == .RSA1024 || keyType == .RSA2048) {
Expand Down Expand Up @@ -465,16 +466,34 @@ public final actor PIVSession: Session, InternalSession {
guard challengeSent == challengeReturned else { throw PIVSessionError.authenticationFailed }
}

public func getSlotMetadata(_ slot: PIVSlot) async throws -> PIVSlotMetadata {
Logger.piv.debug("\(String(describing: self).lastComponent), \(#function)")
guard let connection = _connection else { throw SessionError.noConnection }
guard self.supports(PIVSessionFeature.metadata) else { throw SessionError.notSupported }
let result = try await connection.send(apdu: APDU(cla: 0, ins: insGetMetadata, p1: 0, p2: slot.rawValue))
guard let records = TKBERTLVRecord.sequenceOfRecords(from: result) else { throw PIVSessionError.dataParseError }

guard let rawKeyType = records.recordWithTag(tagMetadataAlgorithm)?.value.bytes[0], let keyType = PIVKeyType(rawValue: rawKeyType),
let policyBytes = records.recordWithTag(tagMetadataPolicy)?.value.bytes, policyBytes.count > 1,
let pinPolicy = PIVPinPolicy(rawValue: policyBytes[indexPinPolicy]),
let touchPolicy = PIVTouchPolicy(rawValue: policyBytes[indexTouchPolicy]),
let origin = records.recordWithTag(tagMetadataOrigin)?.value.uint8,
let publicKey = records.recordWithTag(tagMetadataPublicKey)?.value
else { throw PIVSessionError.dataParseError }

return PIVSlotMetadata(keyType: keyType, pinPolicy: pinPolicy, touchPolicy: touchPolicy, generated: origin == originGenerated, publicKey: publicKey)
}

public func getManagementKeyMetadata() async throws -> PIVManagementKeyMetadata {
Logger.piv.debug("\(String(describing: self).lastComponent), \(#function)")
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)
let isDefault = records.recordWithTag(tagMetadataIsDefault)?.value.bytes[0],
let rawTouchPolicy = records.recordWithTag(tagMetadataPolicy)?.value.bytes[1],
let touchPolicy = PIVTouchPolicy(rawValue: rawTouchPolicy)
else { throw PIVSessionError.responseDataNotTLVFormatted }

let keyType: PIVManagementKeyType
Expand Down Expand Up @@ -668,7 +687,7 @@ extension PIVSession {
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),
let isDefault = records.recordWithTag(tagMetadataDefault)?.value.bytes[0],
let isDefault = records.recordWithTag(tagMetadataIsDefault)?.value.bytes[0],
let retriesTotal = records.recordWithTag(tagMetadataRetries)?.value.bytes[0],
let retriesRemaining = records.recordWithTag(tagMetadataRetries)?.value.bytes[1]
else { throw PIVSessionError.responseDataNotTLVFormatted }
Expand Down Expand Up @@ -747,10 +766,6 @@ fileprivate let insGenerateAsymetric: UInt8 = 0x47;
fileprivate let insAttest: UInt8 = 0xf9;

// Tags
fileprivate let tagMetadataDefault: TKTLVTag = 0x05
fileprivate let tagMetadataAlgorithm: TKTLVTag = 0x01
fileprivate let tagMetadataTouchPolicy: TKTLVTag = 0x02
fileprivate let tagMetadataRetries: TKTLVTag = 0x06
fileprivate let tagDynAuth: TKTLVTag = 0x7c
fileprivate let tagAuthWitness: TKTLVTag = 0x80
fileprivate let tagChallenge: TKTLVTag = 0x81
Expand All @@ -765,6 +780,23 @@ fileprivate let tagLRC: TKTLVTag = 0xfe
fileprivate let tagPinPolicy: TKTLVTag = 0xaa
fileprivate let tagTouchpolicy: TKTLVTag = 0xab


// Metadata tags
fileprivate let tagMetadataAlgorithm: TKTLVTag = 0x01
fileprivate let tagMetadataOrigin: TKTLVTag = 0x03
fileprivate let tagMetadataPublicKey: TKTLVTag = 0x04
fileprivate let tagMetadataIsDefault: TKTLVTag = 0x05
fileprivate let tagMetadataRetries: TKTLVTag = 0x06
fileprivate let tagMetadataPolicy: TKTLVTag = 0x02

fileprivate let originGenerated: UInt8 = 1
fileprivate let originImported: UInt8 = 2

fileprivate let indexPinPolicy: Int = 0
fileprivate let indexTouchPolicy: Int = 1
fileprivate let indexRetriesTotal: Int = 0
fileprivate let indexRetriesRemaining: Int = 1

// P2
fileprivate let p2Pin: UInt8 = 0x80
fileprivate let p2Puk: UInt8 = 0x81
Expand Down

0 comments on commit d8a76b4

Please sign in to comment.