From f64da37479917c090696bc5c17c3245d0250d5d9 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Thu, 7 Nov 2024 12:57:47 +0100 Subject: [PATCH] Sign extension for make credential. --- .../Shared/Sessions/FIDO2/YKFFIDO2Session.m | 62 ++++++++++++++++--- YubiKitTests/Tests/FIDO2Tests.swift | 33 ++++------ 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/FIDO2/YKFFIDO2Session.m b/YubiKit/YubiKit/Connections/Shared/Sessions/FIDO2/YKFFIDO2Session.m index 93b6d26b..8b2d26c4 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/FIDO2/YKFFIDO2Session.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/FIDO2/YKFFIDO2Session.m @@ -44,6 +44,9 @@ #import "YKFFIDO2MakeCredentialResponse.h" #import "YKFFIDO2GetAssertionResponse.h" +#import "YKFCBORDecoder.h" +#import "YKFCBOREncoder.h" + #import "YKFNSDataAdditions+Private.h" #import "YKFSessionError+Private.h" @@ -349,7 +352,13 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash NSMutableDictionary *signExtensionDict = [NSMutableDictionary new]; // Flags hard coded for now. More information here: // https://github.com/Yubico/python-fido2/blob/8722a8925509d3320f8cb6d8a22c76e2af08fb20/fido2/ctap2/extensions.py#L493 - int flags = 0b101; + + int flags; + if (options[@"userVerification"] && [options[@"userVerification"] isEqual:@"required"]) { + flags = 0b101; + } else { + flags = 0b001; + } NSMutableArray *algorithms = [NSMutableArray array]; [(NSArray *)generateKeyDict[@"algorithms"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { @@ -405,13 +414,50 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash } else { extensionsClientOutput[@"prf"] = @{@"enabled" : @NO}; } -// -// NSError *error; -// NSData *jsonData = [NSJSONSerialization dataWithJSONObject:extensionsClientOutput -// options:NSJSONWritingPrettyPrinted -// error:&error]; -// NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; -// NSLog(@"%@", jsonString); + } + if (authenticatorInputs[YKFCBORTextString(@"sign")]) { + YKFCBORMap *cborMap = makeCredentialResponse.authenticatorData.extensions.value[YKFCBORTextString(@"sign")]; + YKFCBORByteString *signAttestationObject = cborMap.value[YKFCBORInteger(7)]; + NSData *signAttestationData = signAttestationObject.value; + if (!signAttestationData) { + [NSException raise:@"Invalid input" format:@"Invalid data."]; + } + YKFCBORMap *signAttestation = nil; + NSInputStream *decoderInputStream = [[NSInputStream alloc] initWithData:signAttestationData]; + [decoderInputStream open]; + signAttestation = [YKFCBORDecoder decodeObjectFrom:decoderInputStream]; + [decoderInputStream close]; + if (!signAttestation) { + [NSException raise:@"Invalid input" format:@"Invalid data"]; + } + NSData *authenticatorDataBytes = ((YKFCBORByteString *)signAttestation.value[YKFCBORInteger(2)]).value; + YKFFIDO2AuthenticatorData *authenticatorData = [[YKFFIDO2AuthenticatorData alloc] initWithData:authenticatorDataBytes]; + + + YKFCBORMap *coseKeyCborMap = nil; + decoderInputStream = [[NSInputStream alloc] initWithData:authenticatorData.coseEncodedCredentialPublicKey]; + [decoderInputStream open]; + coseKeyCborMap = [YKFCBORDecoder decodeObjectFrom:decoderInputStream]; + [decoderInputStream close]; + + NSMutableDictionary *keyHandleDict = [NSMutableDictionary new]; + for (int i = 1; i < 4; i++) { + if (coseKeyCborMap.value[YKFCBORInteger(i)]) { + keyHandleDict[YKFCBORInteger(i)] = i == 1 ? YKFCBORInteger(-2) : coseKeyCborMap.value[YKFCBORInteger(i)]; + } + } + + NSData *keyHandleData = [YKFCBOREncoder encodeMap:YKFCBORMap(keyHandleDict)]; + NSMutableDictionary *generatedKeyDict = [NSMutableDictionary new]; + generatedKeyDict[@"publicKey"] = [authenticatorData.coseEncodedCredentialPublicKey ykf_websafeBase64EncodedString]; + generatedKeyDict[@"keyHandle"] = [keyHandleData ykf_websafeBase64EncodedString]; + + NSMutableDictionary *signDict = [NSMutableDictionary new]; + signDict[@"generatedKey"] = generatedKeyDict; + if (cborMap.value[YKFCBORInteger(6)]) { + signDict[@"signature"] = ((YKFCBORByteString *)cborMap.value[YKFCBORInteger(6)]).value; + } + extensionsClientOutput[@"sign"] = signDict; } if (makeCredentialResponse) { diff --git a/YubiKitTests/Tests/FIDO2Tests.swift b/YubiKitTests/Tests/FIDO2Tests.swift index 1ce3107f..32d0400b 100644 --- a/YubiKitTests/Tests/FIDO2Tests.swift +++ b/YubiKitTests/Tests/FIDO2Tests.swift @@ -246,6 +246,18 @@ class FIDO2Tests: XCTestCase { } } + func testCreateSignExtensionCredential() { + 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 + print(response.authenticatorData) + print("✅ Created new FIDO2 credential: \(response)") + } + } + } + } + func testCreatePRFSecretExtensionCredential() { runYubiKitTest { connection, completion in connection.fido2TestSession { session in @@ -285,27 +297,6 @@ class FIDO2Tests: XCTestCase { } } } - - - func testCreateSignExtensionCredential() { - runYubiKitTest { connection, completion in - connection.fido2TestSession { session in - session.verifyPin("123456") { error in - if let error { XCTFail("verifyPin failed with: \(error)"); return } - - let extensions = ["sign" : ["generateKey" : ["algorithms" : [-65539, -7]], - "phData" : "84ecfc628f1576ed179241e240db0a77f986954546adbc207e9c43e032f18450"]] - session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true], extensions: extensions) { response in - print("✅ Created new FIDO2 credential: \(response)") - session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: false]) { response in - print("✅ Asserted FIDO2 credential: \(response)") - completion() - } - } - } - } - } - } } extension YKFFIDO2Session {