Skip to content

Commit 0b64e79

Browse files
committed
Make credential PRF extension.
1 parent bcb721c commit 0b64e79

File tree

4 files changed

+75
-50
lines changed

4 files changed

+75
-50
lines changed

YubiKit/YubiKit/Connections/Shared/APDU/FIDO2/YKFFIDO2MakeCredentialAPDU.m

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -103,48 +103,12 @@ - (nullable instancetype)initWithClientDataHash:(NSData *)clientDataHash
103103
}
104104
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyOptions)] = YKFCBORMap(mutableOptions);
105105
}
106-
107-
// Extensions
108-
NSMutableDictionary *extensionsDict = [NSMutableDictionary new];
109-
// Sign
110-
if (extensions && extensions[@"sign"] && extensions[@"sign"][@"generateKey"]) {
111-
NSDictionary *generateKeyDict = (NSDictionary *) extensions[@"sign"][@"generateKey"];
112-
NSMutableDictionary *signExtensionDict = [NSMutableDictionary new];
113-
// Flags hard coded for now. More information here:
114-
// https://github.com/Yubico/python-fido2/blob/8722a8925509d3320f8cb6d8a22c76e2af08fb20/fido2/ctap2/extensions.py#L493
115-
int flags = 0b101;
116-
117-
NSMutableArray *algorithms = [NSMutableArray array];
118-
[(NSArray *)generateKeyDict[@"algorithms"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
119-
NSInteger intValue = [(NSNumber *)obj integerValue];
120-
[algorithms addObject:YKFCBORInteger(intValue)];
121-
}];
122-
signExtensionDict[YKFCBORInteger(3)] = YKFCBORArray(algorithms);
123-
signExtensionDict[YKFCBORInteger(4)] = YKFCBORInteger(flags);
124-
125-
if (generateKeyDict[@"phData"]) {
126-
NSString * phData = generateKeyDict[@"phData"];
127-
NSData *phDataBase64Encoded = [[NSData alloc] initWithBase64EncodedString:phData options:0];
128-
signExtensionDict[YKFCBORInteger(0)] = YKFCBORByteString(phDataBase64Encoded);
129-
}
130-
extensionsDict[YKFCBORTextString(@"sign")] = YKFCBORMap(signExtensionDict);
131-
}
132-
133-
// Extensions large blob
134-
if (extensions && extensions[@"largeBlobKey"] && [extensions[@"largeBlobKey"][@"support"] isEqual: @"required"]) {
135-
extensionsDict[YKFCBORTextString(@"largeBlobKey")] = YKFCBORBool(true);
136-
}
137106

138-
// Extensions hmac-secret
139-
if (extensions && extensions[@"hmac-secret"]) {
140-
extensionsDict[YKFCBORTextString(@"hmac-secret")] = YKFCBORBool(true);
141-
}
142-
143-
if (extensionsDict.count > 0) {
144-
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyExtensions)] = YKFCBORMap(extensionsDict);
107+
// Extensions
108+
if (extensions.count > 0) {
109+
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyExtensions)] = YKFCBORMap(extensions);
145110
}
146111

147-
148112
// Pin Auth
149113
if (pinAuth) {
150114
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyPinAuth)] = YKFCBORByteString(pinAuth);

YubiKit/YubiKit/Connections/Shared/Sessions/FIDO2/YKFFIDO2Session.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ typedef void (^YKFFIDO2SessionGetInfoCompletionBlock)
101101
parameter is nil.
102102
*/
103103
typedef void (^YKFFIDO2SessionMakeCredentialCompletionBlock)
104-
(YKFFIDO2MakeCredentialResponse* _Nullable response, NSError* _Nullable error);
104+
(YKFFIDO2MakeCredentialResponse* _Nullable response, NSDictionary* _Nullable clientExtensionsOutput, NSError* _Nullable error);
105105

106106
/*!
107107
@abstract

YubiKit/YubiKit/Connections/Shared/Sessions/FIDO2/YKFFIDO2Session.m

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#import "YKFFIDO2ResetAPDU.h"
3737

3838
#import "YKFFIDO2GetInfoResponse+Private.h"
39+
#import "YKFFIDO2MakeCredentialResponse.h"
3940
#import "YKFFIDO2MakeCredentialResponse+Private.h"
4041
#import "YKFFIDO2GetAssertionResponse+Private.h"
4142

@@ -335,33 +336,88 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
335336
NSData *hmac = [clientDataHash ykf_fido2HMACWithKey:self.pinToken];
336337
pinAuth = [hmac subdataWithRange:NSMakeRange(0, 16)];
337338
if (!pinAuth) {
338-
completion(nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
339+
completion(nil, nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
339340
}
340341
}
341342

342-
YKFAPDU *apdu = [[YKFFIDO2MakeCredentialAPDU alloc] initWithClientDataHash:clientDataHash rp:rp user:user pubKeyCredParams:pubKeyCredParams excludeList:excludeList pinAuth:pinAuth pinProtocol:pinProtocol options:options extensions:extensions];
343+
344+
// Extensions, client authentictor input
345+
NSMutableDictionary *authenticatorInputs = [NSMutableDictionary new];
346+
// Sign
347+
if (extensions && extensions[@"sign"] && extensions[@"sign"][@"generateKey"]) {
348+
NSDictionary *generateKeyDict = (NSDictionary *) extensions[@"sign"][@"generateKey"];
349+
NSMutableDictionary *signExtensionDict = [NSMutableDictionary new];
350+
// Flags hard coded for now. More information here:
351+
// https://github.com/Yubico/python-fido2/blob/8722a8925509d3320f8cb6d8a22c76e2af08fb20/fido2/ctap2/extensions.py#L493
352+
int flags = 0b101;
353+
354+
NSMutableArray *algorithms = [NSMutableArray array];
355+
[(NSArray *)generateKeyDict[@"algorithms"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
356+
NSInteger intValue = [(NSNumber *)obj integerValue];
357+
[algorithms addObject:YKFCBORInteger(intValue)];
358+
}];
359+
signExtensionDict[YKFCBORInteger(3)] = YKFCBORArray(algorithms);
360+
signExtensionDict[YKFCBORInteger(4)] = YKFCBORInteger(flags);
361+
362+
if (generateKeyDict[@"phData"]) {
363+
NSString * phData = generateKeyDict[@"phData"];
364+
NSData *phDataBase64Encoded = [[NSData alloc] initWithBase64EncodedString:phData options:0];
365+
signExtensionDict[YKFCBORInteger(0)] = YKFCBORByteString(phDataBase64Encoded);
366+
}
367+
authenticatorInputs[YKFCBORTextString(@"sign")] = YKFCBORMap(signExtensionDict);
368+
}
369+
370+
// Extensions large blob
371+
if (extensions && extensions[@"largeBlobKey"] && [extensions[@"largeBlobKey"][@"support"] isEqual: @"required"]) {
372+
authenticatorInputs[YKFCBORTextString(@"largeBlobKey")] = YKFCBORBool(true);
373+
}
374+
375+
// Extensions hmac-secret
376+
if (extensions && extensions[@"prf"]) {
377+
authenticatorInputs[YKFCBORTextString(@"hmac-secret")] = YKFCBORBool(true);
378+
}
379+
380+
YKFAPDU *apdu = [[YKFFIDO2MakeCredentialAPDU alloc] initWithClientDataHash:clientDataHash rp:rp user:user pubKeyCredParams:pubKeyCredParams excludeList:excludeList pinAuth:pinAuth pinProtocol:pinProtocol options:options extensions:authenticatorInputs];
343381

344382
if (!apdu) {
345383
YKFSessionError *error = [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER];
346-
completion(nil, error);
384+
completion(nil, nil, error);
347385
return;
348386
}
349387

350388
ykf_weak_self();
351389
[self executeFIDO2Command:apdu retryCount:0 completion:^(NSData *data, NSError *error) {
352390
ykf_safe_strong_self();
353391
if (error) {
354-
completion(nil, error);
392+
completion(nil, nil, error);
355393
return;
356394
}
357395

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

399+
// Extensions, authenticator output
400+
NSMutableDictionary *extensionsClientOutput = [NSMutableDictionary new];
401+
if (authenticatorInputs[YKFCBORTextString(@"hmac-secret")]) {
402+
YKFCBORBool *cborBool = makeCredentialResponse.authenticatorData.extensions.value[YKFCBORTextString(@"hmac-secret")];
403+
if (cborBool && cborBool.value) {
404+
extensionsClientOutput[@"prf"] = @{@"enabled" : @YES};
405+
} else {
406+
extensionsClientOutput[@"prf"] = @{@"enabled" : @NO};
407+
}
408+
//
409+
// NSError *error;
410+
// NSData *jsonData = [NSJSONSerialization dataWithJSONObject:extensionsClientOutput
411+
// options:NSJSONWritingPrettyPrinted
412+
// error:&error];
413+
// NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
414+
// NSLog(@"%@", jsonString);
415+
}
416+
361417
if (makeCredentialResponse) {
362-
completion(makeCredentialResponse, nil);
418+
completion(makeCredentialResponse, extensionsClientOutput, nil);
363419
} else {
364-
completion(nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeINVALID_CBOR]);
420+
completion(nil, nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeINVALID_CBOR]);
365421
}
366422
}];
367423
}

YubiKitTests/Tests/FIDO2Tests.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class FIDO2Tests: XCTestCase {
129129
if connection as? YKFNFCConnection != nil {
130130
connection.fido2TestSession { session in
131131
session.setPin("123456") { _ in
132-
session.addCredential(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, error in
132+
session.addCredential(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response, _, error in
133133
if let error = error {
134134
XCTAssertTrue((error as NSError).code == 54, "🔴 Unexpected error: \(error)")
135135
} else {
@@ -246,12 +246,12 @@ class FIDO2Tests: XCTestCase {
246246
}
247247
}
248248

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

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

0 commit comments

Comments
 (0)