Skip to content

Commit

Permalink
Sign extension for assert.
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed Nov 7, 2024
1 parent a2e92d9 commit 3018ae2
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 39 deletions.
72 changes: 47 additions & 25 deletions YubiKit/YubiKit/Connections/Shared/Sessions/FIDO2/YKFFIDO2Session.m
Original file line number Diff line number Diff line change
Expand Up @@ -500,33 +500,55 @@ - (void)getAssertionWithClientDataHash:(NSData *)clientDataHash
}

// Extensions, client authenticator input
if (extensions && extensions[@"prf"] && extensions[@"prf"][@"eval"]) {
NSString *base64EncodedFirst = extensions[@"prf"][@"eval"][@"first"];
NSString *base64EncodedSecond = extensions[@"prf"][@"eval"][@"second"];

NSData *first = [[[NSData alloc] initWithBase64EncodedString:base64EncodedFirst options:0] ykf_prfSaltData];
NSData *second = [[[NSData alloc] initWithBase64EncodedString:base64EncodedSecond options:0] ykf_prfSaltData];

if (first.length != 32 || (second && second.length != 32)) {
[NSException raise:@"Invalid input" format:@"Salt is not 32 bytes long."];
}
if (extensions) {
[self executeGetSharedSecretWithCompletion:^(NSData * _Nullable sharedSecret, YKFCBORMap * _Nullable cosePlatformPublicKey, NSError * _Nullable error) {
NSMutableData *salts = [NSMutableData new];
[salts appendData:first];
if (second) {
[salts appendData:second];
}

NSData *saltEnc = [salts ykf_aes256EncryptedDataWithKey:sharedSecret];
NSData *saltAuth = [saltEnc ykf_fido2HMACWithKey:sharedSecret];
NSMutableDictionary *hmacSecretInput = [NSMutableDictionary new];
hmacSecretInput[YKFCBORInteger(1)] = cosePlatformPublicKey;
hmacSecretInput[YKFCBORInteger(2)] = YKFCBORByteString(saltEnc);
hmacSecretInput[YKFCBORInteger(3)] = YKFCBORByteString([saltAuth subdataWithRange:NSMakeRange(0, 16)]);
hmacSecretInput[YKFCBORInteger(4)] = YKFCBORInteger(1); // pin uv auth protocol version
NSMutableDictionary *authenticatorInputs = [NSMutableDictionary new];
authenticatorInputs[YKFCBORTextString(@"hmac-secret")] = YKFCBORMap(hmacSecretInput);

if (extensions[@"prf"] && extensions[@"prf"][@"eval"]) {
NSString *base64EncodedFirst = extensions[@"prf"][@"eval"][@"first"];
NSString *base64EncodedSecond = extensions[@"prf"][@"eval"][@"second"];

NSData *first = [[[NSData alloc] initWithBase64EncodedString:base64EncodedFirst options:0] ykf_prfSaltData];
NSData *second = [[[NSData alloc] initWithBase64EncodedString:base64EncodedSecond options:0] ykf_prfSaltData];

if (first.length != 32 || (second && second.length != 32)) {
[NSException raise:@"Invalid input" format:@"Salt is not 32 bytes long."];
}
NSMutableData *salts = [NSMutableData new];
[salts appendData:first];
if (second) {
[salts appendData:second];
}

NSData *saltEnc = [salts ykf_aes256EncryptedDataWithKey:sharedSecret];
NSData *saltAuth = [saltEnc ykf_fido2HMACWithKey:sharedSecret];
NSMutableDictionary *hmacSecretInput = [NSMutableDictionary new];
hmacSecretInput[YKFCBORInteger(1)] = cosePlatformPublicKey;
hmacSecretInput[YKFCBORInteger(2)] = YKFCBORByteString(saltEnc);
hmacSecretInput[YKFCBORInteger(3)] = YKFCBORByteString([saltAuth subdataWithRange:NSMakeRange(0, 16)]);
hmacSecretInput[YKFCBORInteger(4)] = YKFCBORInteger(1); // pin uv auth protocol version
authenticatorInputs[YKFCBORTextString(@"hmac-secret")] = YKFCBORMap(hmacSecretInput);
}
if (extensions[@"sign"] && extensions[@"sign"][@"sign"]) {
NSString *phDataString = extensions[@"sign"][@"sign"][@"phData"];
NSData *phData = [[NSData alloc] ykf_initWithWebsafeBase64EncodedString:phDataString dataLength:phDataString.length];
NSDictionary *keyHandleDict = extensions[@"sign"][@"sign"][@"keyHandleByCredential"];
NSMutableDictionary *decodedKeyHandleDict = [NSMutableDictionary new];
for (id key in keyHandleDict) {
decodedKeyHandleDict[key] = [[NSData alloc] ykf_initWithWebsafeBase64EncodedString:keyHandleDict[key] dataLength:((NSString *)keyHandleDict[key]).length];
}
if (allowList.count == 0) {
[NSException raise:@"Invalid input" format:@"Allow list can not be empty."];
}
NSString *credentialId = [((YKFFIDO2PublicKeyCredentialDescriptor *) allowList.firstObject).credentialId ykf_websafeBase64EncodedString];

NSData *keyHandle = decodedKeyHandleDict[credentialId];

NSMutableDictionary *output = [NSMutableDictionary new];
output[YKFCBORInteger(0)] = YKFCBORByteString(phData);
output[YKFCBORInteger(5)] = YKFCBORArray(@[YKFCBORByteString(keyHandle)]);
authenticatorInputs[YKFCBORTextString(@"sign")] = YKFCBORMap(output);
}

YKFFIDO2GetAssertionAPDU *apdu = [[YKFFIDO2GetAssertionAPDU alloc] initWithClientDataHash:clientDataHash
rpId:rpId
allowList:allowList
Expand Down
38 changes: 24 additions & 14 deletions YubiKitTests/Tests/FIDO2Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class FIDO2Tests: XCTestCase {
connection.fido2TestSession { session in
session.setPin("123456") { _ in
session.verifyPin("123456") { error in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, _ in
print("✅ Created new FIDO2 credential: \(response)")
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: true]) { response in
// https://www.w3.org/TR/webauthn/#authenticator-data
Expand Down Expand Up @@ -98,7 +98,7 @@ class FIDO2Tests: XCTestCase {
connection.fido2TestSession { session in
session.setPin("123456") { _ in
session.verifyPin("123456") { error in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, _ in
session.clearUserVerification()
session.getAssertion(response: response, options: [YKFFIDO2OptionUP: true]) { response, error in
if let response = response {
Expand Down Expand Up @@ -171,7 +171,7 @@ class FIDO2Tests: XCTestCase {
func testCreateECCNonRKCredential() {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, _ in
print("✅ New FIDO2 credential: \(response)")
completion()
}
Expand All @@ -182,7 +182,7 @@ class FIDO2Tests: XCTestCase {
func testCreateEdSANonRKCredential() {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: false]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: false]) { response, _ in
print("✅ Created new FIDO2 credential: \(response)")
completion()
}
Expand All @@ -193,7 +193,7 @@ class FIDO2Tests: XCTestCase {
func testCreateECCNonRKCredentialAndAssert() {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, _ in
print("✅ Created new FIDO2 credential: \(response)")
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: true]) { response in
print("✅ Asserted FIDO2 credential: \(response)")
Expand All @@ -207,7 +207,7 @@ class FIDO2Tests: XCTestCase {
func testCreateEdDSANonRKCredentialAndAssert() {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: false]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: false]) { response, _ in
print("✅ Created new FIDO2 credential: \(response)")
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: true]) { response in
print("✅ Asserted FIDO2 credential: \(response)")
Expand All @@ -221,7 +221,7 @@ class FIDO2Tests: XCTestCase {
func testCreateEdDSARKCredentialAndAssert() {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true]) { response, _ in
print("✅ Created new FIDO2 credential: \(response)")
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: true]) { response in
print("✅ Asserted FIDO2 credential: \(response)")
Expand All @@ -235,7 +235,7 @@ class FIDO2Tests: XCTestCase {
func testCreateEdDSARKCredentialAndAssertWithoutUP() {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true]) { response, _ in
print("✅ Created new FIDO2 credential: \(response)")
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: false]) { response in
print("✅ Asserted FIDO2 credential: \(response)")
Expand All @@ -250,9 +250,19 @@ class FIDO2Tests: XCTestCase {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
let createExtensions = ["sign" : ["generateKey": ["algorithms": [-65539]]]]
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false, YKFFIDO2OptionUV: false], extensions: createExtensions) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false, YKFFIDO2OptionUV: false], extensions: createExtensions) { response, extensionResponse in
print(response.authenticatorData)
print("✅ Created new FIDO2 credential: \(response)")
print("✅ Created new FIDO2 credential: \(response), \(extensionResponse)")

let credentialId = (response.authenticatorData!.credentialId! as NSData).ykf_websafeBase64EncodedString()
let sign = (extensionResponse!["sign"] as! [String: Any])
let generatedKey = sign["generatedKey"] as! [String: Any]
let keyHandle = generatedKey["keyHandle"] as! String
let assertExtensions = ["sign" : ["sign" : ["phData" : "Mp80eo3FdxJ2hLtD7HHQO5hhzjyDD57Kdvuqi_weTGE", "keyHandleByCredential" : [credentialId: keyHandle]]]]
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: true, YKFFIDO2OptionUV: false], extensions: assertExtensions) { response in
print("✅ Asserted FIDO2 credential: \(response)")
completion()
}
}
}
}
Expand All @@ -264,7 +274,7 @@ class FIDO2Tests: XCTestCase {
session.verifyPin("123456") { error in
if let error { XCTFail("verifyPin failed with: \(error)"); return }
let createExtensions = ["prf" : []]
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: true], extensions: createExtensions) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: true], extensions: createExtensions) { response, _ in
print(response.authenticatorData)
print("✅ Created new FIDO2 credential: \(response)")

Expand All @@ -286,7 +296,7 @@ class FIDO2Tests: XCTestCase {
if let error { XCTFail("verifyPin failed with: \(error)"); return }
// let extensions = ["largeBlobKey" : ["support": "required"]]
// session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true], extensions: extensions) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true]) { response in
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true]) { response, _ in
print("✅ Created new FIDO2 credential: \(response)")
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: true]) { response in
print("✅ Asserted FIDO2 credential: \(response)")
Expand All @@ -300,15 +310,15 @@ class FIDO2Tests: XCTestCase {
}

extension YKFFIDO2Session {
func addCredentialAndAssert(algorithm: Int, options: [String: Any]? = nil, extensions: [String: Any]? = nil, completion: @escaping (_ response: YKFFIDO2MakeCredentialResponse) -> Void) {
func addCredentialAndAssert(algorithm: Int, options: [String: Any]? = nil, extensions: [String: Any]? = nil, completion: @escaping (_ response: YKFFIDO2MakeCredentialResponse, _ clientExtensionResults: [AnyHashable: Any]?) -> Void) {
addCredential(algorithm: algorithm, options: options, extensions: extensions) { response, clientExtensionResults, error in
if let clientExtensionResults {
let jsonData = try! JSONSerialization.data(withJSONObject: clientExtensionResults)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString as Any)
}
guard let response = response else { XCTAssertTrue(false, "🔴 Failed making FIDO2 credential: \(error!)"); return }
completion(response)
completion(response, clientExtensionResults)
}
}

Expand Down

0 comments on commit 3018ae2

Please sign in to comment.