Skip to content

Commit

Permalink
Make credential PRF extension.
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed Nov 5, 2024
1 parent bcb721c commit 0b64e79
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,48 +103,12 @@ - (nullable instancetype)initWithClientDataHash:(NSData *)clientDataHash
}
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyOptions)] = YKFCBORMap(mutableOptions);
}

// Extensions
NSMutableDictionary *extensionsDict = [NSMutableDictionary new];
// Sign
if (extensions && extensions[@"sign"] && extensions[@"sign"][@"generateKey"]) {
NSDictionary *generateKeyDict = (NSDictionary *) extensions[@"sign"][@"generateKey"];
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;

NSMutableArray *algorithms = [NSMutableArray array];
[(NSArray *)generateKeyDict[@"algorithms"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSInteger intValue = [(NSNumber *)obj integerValue];
[algorithms addObject:YKFCBORInteger(intValue)];
}];
signExtensionDict[YKFCBORInteger(3)] = YKFCBORArray(algorithms);
signExtensionDict[YKFCBORInteger(4)] = YKFCBORInteger(flags);

if (generateKeyDict[@"phData"]) {
NSString * phData = generateKeyDict[@"phData"];
NSData *phDataBase64Encoded = [[NSData alloc] initWithBase64EncodedString:phData options:0];
signExtensionDict[YKFCBORInteger(0)] = YKFCBORByteString(phDataBase64Encoded);
}
extensionsDict[YKFCBORTextString(@"sign")] = YKFCBORMap(signExtensionDict);
}

// Extensions large blob
if (extensions && extensions[@"largeBlobKey"] && [extensions[@"largeBlobKey"][@"support"] isEqual: @"required"]) {
extensionsDict[YKFCBORTextString(@"largeBlobKey")] = YKFCBORBool(true);
}

// Extensions hmac-secret
if (extensions && extensions[@"hmac-secret"]) {
extensionsDict[YKFCBORTextString(@"hmac-secret")] = YKFCBORBool(true);
}

if (extensionsDict.count > 0) {
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyExtensions)] = YKFCBORMap(extensionsDict);
// Extensions
if (extensions.count > 0) {
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyExtensions)] = YKFCBORMap(extensions);
}


// Pin Auth
if (pinAuth) {
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyPinAuth)] = YKFCBORByteString(pinAuth);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ typedef void (^YKFFIDO2SessionGetInfoCompletionBlock)
parameter is nil.
*/
typedef void (^YKFFIDO2SessionMakeCredentialCompletionBlock)
(YKFFIDO2MakeCredentialResponse* _Nullable response, NSError* _Nullable error);
(YKFFIDO2MakeCredentialResponse* _Nullable response, NSDictionary* _Nullable clientExtensionsOutput, NSError* _Nullable error);

/*!
@abstract
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#import "YKFFIDO2ResetAPDU.h"

#import "YKFFIDO2GetInfoResponse+Private.h"
#import "YKFFIDO2MakeCredentialResponse.h"
#import "YKFFIDO2MakeCredentialResponse+Private.h"
#import "YKFFIDO2GetAssertionResponse+Private.h"

Expand Down Expand Up @@ -335,33 +336,88 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
NSData *hmac = [clientDataHash ykf_fido2HMACWithKey:self.pinToken];
pinAuth = [hmac subdataWithRange:NSMakeRange(0, 16)];
if (!pinAuth) {
completion(nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
completion(nil, nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
}
}

YKFAPDU *apdu = [[YKFFIDO2MakeCredentialAPDU alloc] initWithClientDataHash:clientDataHash rp:rp user:user pubKeyCredParams:pubKeyCredParams excludeList:excludeList pinAuth:pinAuth pinProtocol:pinProtocol options:options extensions:extensions];

// Extensions, client authentictor input
NSMutableDictionary *authenticatorInputs = [NSMutableDictionary new];
// Sign
if (extensions && extensions[@"sign"] && extensions[@"sign"][@"generateKey"]) {
NSDictionary *generateKeyDict = (NSDictionary *) extensions[@"sign"][@"generateKey"];
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;

NSMutableArray *algorithms = [NSMutableArray array];
[(NSArray *)generateKeyDict[@"algorithms"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSInteger intValue = [(NSNumber *)obj integerValue];
[algorithms addObject:YKFCBORInteger(intValue)];
}];
signExtensionDict[YKFCBORInteger(3)] = YKFCBORArray(algorithms);
signExtensionDict[YKFCBORInteger(4)] = YKFCBORInteger(flags);

if (generateKeyDict[@"phData"]) {
NSString * phData = generateKeyDict[@"phData"];
NSData *phDataBase64Encoded = [[NSData alloc] initWithBase64EncodedString:phData options:0];
signExtensionDict[YKFCBORInteger(0)] = YKFCBORByteString(phDataBase64Encoded);
}
authenticatorInputs[YKFCBORTextString(@"sign")] = YKFCBORMap(signExtensionDict);
}

// Extensions large blob
if (extensions && extensions[@"largeBlobKey"] && [extensions[@"largeBlobKey"][@"support"] isEqual: @"required"]) {
authenticatorInputs[YKFCBORTextString(@"largeBlobKey")] = YKFCBORBool(true);
}

// Extensions hmac-secret
if (extensions && extensions[@"prf"]) {
authenticatorInputs[YKFCBORTextString(@"hmac-secret")] = YKFCBORBool(true);
}

YKFAPDU *apdu = [[YKFFIDO2MakeCredentialAPDU alloc] initWithClientDataHash:clientDataHash rp:rp user:user pubKeyCredParams:pubKeyCredParams excludeList:excludeList pinAuth:pinAuth pinProtocol:pinProtocol options:options extensions:authenticatorInputs];

if (!apdu) {
YKFSessionError *error = [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER];
completion(nil, error);
completion(nil, nil, error);
return;
}

ykf_weak_self();
[self executeFIDO2Command:apdu retryCount:0 completion:^(NSData *data, NSError *error) {
ykf_safe_strong_self();
if (error) {
completion(nil, error);
completion(nil, nil, error);
return;
}

NSData *cborData = [strongSelf cborFromKeyResponseData:data];
YKFFIDO2MakeCredentialResponse *makeCredentialResponse = [[YKFFIDO2MakeCredentialResponse alloc] initWithCBORData:cborData];

// Extensions, authenticator output
NSMutableDictionary *extensionsClientOutput = [NSMutableDictionary new];
if (authenticatorInputs[YKFCBORTextString(@"hmac-secret")]) {
YKFCBORBool *cborBool = makeCredentialResponse.authenticatorData.extensions.value[YKFCBORTextString(@"hmac-secret")];
if (cborBool && cborBool.value) {
extensionsClientOutput[@"prf"] = @{@"enabled" : @YES};
} 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 (makeCredentialResponse) {
completion(makeCredentialResponse, nil);
completion(makeCredentialResponse, extensionsClientOutput, nil);
} else {
completion(nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeINVALID_CBOR]);
completion(nil, nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeINVALID_CBOR]);
}
}];
}
Expand Down
13 changes: 9 additions & 4 deletions YubiKitTests/Tests/FIDO2Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class FIDO2Tests: XCTestCase {
if connection as? YKFNFCConnection != nil {
connection.fido2TestSession { session in
session.setPin("123456") { _ in
session.addCredential(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, error in
session.addCredential(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, _, error in
if let error = error {
XCTAssertTrue((error as NSError).code == 54, "🔴 Unexpected error: \(error)")
} else {
Expand Down Expand Up @@ -246,12 +246,12 @@ class FIDO2Tests: XCTestCase {
}
}

func testCreateHmacSecretExtensionCredential() {
func testCreatePRFSecretExtensionCredential() {
runYubiKitTest { connection, completion in
connection.fido2TestSession { session in
session.verifyPin("123456") { error in
if let error { XCTFail("verifyPin failed with: \(error)"); return }
let extensions = ["hmac-secret" : true]
let extensions = ["prf" : true]
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmEdDSA, options: [YKFFIDO2OptionRK: true], extensions: extensions) { response in
print(response.authenticatorData)
print("✅ Created new FIDO2 credential: \(response)")
Expand Down Expand Up @@ -308,7 +308,12 @@ class FIDO2Tests: XCTestCase {

extension YKFFIDO2Session {
func addCredentialAndAssert(algorithm: Int, options: [String: Any]? = nil, extensions: [String: Any]? = nil, completion: @escaping (_ response: YKFFIDO2MakeCredentialResponse) -> Void) {
addCredential(algorithm: algorithm, options: options, extensions: extensions) { response, error in
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)
}
Expand Down

0 comments on commit 0b64e79

Please sign in to comment.