Skip to content

Commit 70ac4d1

Browse files
committed
Merge branch 'jens/pin-protocol-v2'
2 parents 8a3609a + 0b16c22 commit 70ac4d1

File tree

5 files changed

+122
-18
lines changed

5 files changed

+122
-18
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818

1919
@protocol YKFConnectionControllerProtocol, YKFSCPKeyParamsProtocol;
2020

21+
typedef NS_ENUM(NSUInteger, YKFFIDOPinProtocol) {
22+
YKFFIDOPinProtocolV1 = 1,
23+
YKFFIDOPinProtocolV2 = 2,
24+
};
25+
26+
2127
NS_ASSUME_NONNULL_BEGIN
2228

2329
@interface YKFFIDO2Session()<YKFSessionProtocol>

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

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@
5858
NSString* const YKFFIDO2OptionUV = @"uv";
5959
NSString* const YKFFIDO2OptionUP = @"up";
6060

61+
@interface NSData(NSData_PinProtocols)
62+
63+
- (NSData *)ykf_authenticateDataWithKey:(NSData *)data pinProtocol:(YKFFIDOPinProtocol)pinProtocol;
64+
- (NSData *)ykf_encryptDataWithKey:(NSData *)data pinProtocol:(YKFFIDOPinProtocol)pinProtocol;
65+
- (NSData *)ykf_decryptDataWithKey:(NSData *)data pinProtocol:(YKFFIDOPinProtocol)pinProtocol;
66+
67+
@end
68+
6169
#pragma mark - Private Response Blocks
6270

6371
typedef void (^YKFFIDO2SessionResultCompletionBlock)
@@ -80,6 +88,8 @@ @interface YKFFIDO2Session()
8088
// Keeps the state of the application selection to avoid reselecting the application.
8189
@property BOOL applicationSelected;
8290

91+
@property (nonatomic, readwrite) YKFFIDOPinProtocol pinProtocol;
92+
8393
@end
8494

8595
@implementation YKFFIDO2Session
@@ -97,8 +107,18 @@ + (void)sessionWithConnectionController:(nonnull id<YKFConnectionControllerProto
97107
if (error) {
98108
completion(nil, error);
99109
} else {
100-
[session updateKeyState:YKFFIDO2SessionKeyStateIdle];
101-
completion(session, nil);
110+
[session getInfoWithCompletion:^(YKFFIDO2GetInfoResponse * _Nullable response, NSError * _Nullable error) {
111+
if ([response.pinProtocols containsObject: @2]) {
112+
session.pinProtocol = YKFFIDOPinProtocolV2;
113+
} else if ([response.pinProtocols containsObject: @1]) {
114+
session.pinProtocol = YKFFIDOPinProtocolV1;
115+
} else {
116+
completion(nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
117+
return;
118+
}
119+
completion(session, nil);
120+
[session updateKeyState:YKFFIDO2SessionKeyStateIdle];
121+
}];
102122
}
103123
}];
104124
}
@@ -189,13 +209,13 @@ - (void)verifyPin:(NSString *)pin completion:(YKFFIDO2SessionGenericCompletionBl
189209

190210
// Get the authenticator pinToken
191211
YKFFIDO2ClientPinRequest *clientPinGetPinTokenRequest = [[YKFFIDO2ClientPinRequest alloc] init];
192-
clientPinGetPinTokenRequest.pinProtocol = 1;
212+
clientPinGetPinTokenRequest.pinProtocol = self.pinProtocol;
193213
clientPinGetPinTokenRequest.subCommand = YKFFIDO2ClientPinRequestSubCommandGetPINToken;
194214
clientPinGetPinTokenRequest.keyAgreement = cosePlatformPublicKey;
195215

196216
NSData *pinData = [pin dataUsingEncoding:NSUTF8StringEncoding];
197217
NSData *pinHash = [[pinData ykf_SHA256] subdataWithRange:NSMakeRange(0, 16)];
198-
clientPinGetPinTokenRequest.pinHashEnc = [pinHash ykf_aes256EncryptedDataWithKey:sharedSecret];
218+
clientPinGetPinTokenRequest.pinHashEnc = [pinHash ykf_encryptDataWithKey:sharedSecret pinProtocol:self.pinProtocol];
199219

200220
[strongSelf executeClientPinRequest:clientPinGetPinTokenRequest completion:^(YKFFIDO2ClientPinResponse *response, NSError *error) {
201221
if (error) {
@@ -209,7 +229,7 @@ - (void)verifyPin:(NSString *)pin completion:(YKFFIDO2SessionGenericCompletionBl
209229
}
210230

211231
// Cache the pinToken
212-
strongSelf.pinToken = [response.pinToken ykf_aes256DecryptedDataWithKey:sharedSecret];
232+
strongSelf.pinToken = [encryptedPinToken ykf_decryptDataWithKey:sharedSecret pinProtocol:self.pinProtocol];
213233

214234
if (!strongSelf.pinToken) {
215235
completion([YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeINVALID_CBOR]);
@@ -299,13 +319,12 @@ - (void)setPin:(nonnull NSString *)pin completion:(nonnull YKFFIDO2SessionGeneri
299319

300320
// Set the new PIN
301321
YKFFIDO2ClientPinRequest *setPinRequest = [[YKFFIDO2ClientPinRequest alloc] init];
302-
setPinRequest.pinProtocol = 1;
322+
setPinRequest.pinProtocol = self.pinProtocol;
303323
setPinRequest.subCommand = YKFFIDO2ClientPinRequestSubCommandSetPIN;
304324
setPinRequest.keyAgreement = cosePlatformPublicKey;
305325

306-
307-
setPinRequest.pinEnc = [pinData ykf_aes256EncryptedDataWithKey:sharedSecret];
308-
setPinRequest.pinAuth = [[setPinRequest.pinEnc ykf_fido2HMACWithKey:sharedSecret] subdataWithRange:NSMakeRange(0, 16)];
326+
setPinRequest.pinEnc = [pinData ykf_encryptDataWithKey:sharedSecret pinProtocol:self.pinProtocol];
327+
setPinRequest.pinAuth = [setPinRequest.pinEnc ykf_authenticateDataWithKey:sharedSecret pinProtocol:self.pinProtocol];
309328

310329
[strongSelf executeClientPinRequest:setPinRequest completion:^(YKFFIDO2ClientPinResponse *response, NSError *error) {
311330
if (error) {
@@ -351,9 +370,8 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
351370
NSUInteger pinProtocol = 0;
352371
if (self.pinToken) {
353372
YKFParameterAssertReturn(clientDataHash);
354-
pinProtocol = 1;
355-
NSData *hmac = [clientDataHash ykf_fido2HMACWithKey:self.pinToken];
356-
pinAuth = [hmac subdataWithRange:NSMakeRange(0, 16)];
373+
pinProtocol = self.pinProtocol;
374+
pinAuth = [clientDataHash ykf_authenticateDataWithKey:self.pinToken pinProtocol:self.pinProtocol];
357375
if (!pinAuth) {
358376
completion(nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
359377
}
@@ -400,9 +418,8 @@ - (void)getAssertionWithClientDataHash:(NSData *)clientDataHash
400418
NSUInteger pinProtocol = 0;
401419
if (self.pinToken) {
402420
YKFParameterAssertReturn(clientDataHash);
403-
pinProtocol = 1;
404-
NSData *hmac = [clientDataHash ykf_fido2HMACWithKey:self.pinToken];
405-
pinAuth = [hmac subdataWithRange:NSMakeRange(0, 16)];
421+
pinProtocol = self.pinProtocol;
422+
pinAuth = [clientDataHash ykf_authenticateDataWithKey:self.pinToken pinProtocol:self.pinProtocol];
406423
if (!pinAuth) {
407424
completion(nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
408425
}
@@ -537,7 +554,7 @@ - (void)executeGetSharedSecretWithCompletion:(YKFFIDO2SessionClientPinSharedSecr
537554

538555
// Get the authenticator public key.
539556
YKFFIDO2ClientPinRequest *clientPinKeyAgreementRequest = [[YKFFIDO2ClientPinRequest alloc] init];
540-
clientPinKeyAgreementRequest.pinProtocol = 1;
557+
clientPinKeyAgreementRequest.pinProtocol = self.pinProtocol;
541558
clientPinKeyAgreementRequest.subCommand = YKFFIDO2ClientPinRequestSubCommandGetKeyAgreement;
542559
clientPinKeyAgreementRequest.keyAgreement = cosePlatformPublicKey;
543560

@@ -563,7 +580,20 @@ - (void)executeGetSharedSecretWithCompletion:(YKFFIDO2SessionClientPinSharedSecr
563580
completion(nil, nil, [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER]);
564581
return;
565582
}
566-
sharedSecret = [sharedSecret ykf_SHA256];
583+
584+
switch (_pinProtocol) {
585+
case YKFFIDOPinProtocolV1:
586+
sharedSecret = [sharedSecret ykf_SHA256];
587+
break;
588+
case YKFFIDOPinProtocolV2: {
589+
NSData *hmacKey = [sharedSecret ykf_deriveHKDFWithSalt:[NSMutableData dataWithLength:32] info:[@"CTAP2 HMAC key" dataUsingEncoding:NSUTF8StringEncoding]];
590+
NSData *aesKey = [sharedSecret ykf_deriveHKDFWithSalt:[NSMutableData dataWithLength:32] info:[@"CTAP2 AES key" dataUsingEncoding:NSUTF8StringEncoding]];
591+
NSMutableData *result = [hmacKey mutableCopy];
592+
[result appendData:aesKey];
593+
sharedSecret = result;
594+
break;
595+
}
596+
}
567597

568598
// Success
569599
completion(sharedSecret, cosePlatformPublicKey, nil);
@@ -641,3 +671,50 @@ - (void)handleTouchRequired:(YKFAPDU *)apdu retryCount:(int)retryCount completi
641671
}
642672

643673
@end
674+
675+
676+
#pragma mark - INT conversion
677+
678+
@implementation NSData (NSData_PinProtocols)
679+
680+
- (NSData *)ykf_authenticateDataWithKey:(NSData *)key pinProtocol:(YKFFIDOPinProtocol)pinProtocol {
681+
switch (pinProtocol) {
682+
case YKFFIDOPinProtocolV1:
683+
return [[self ykf_fido2HMACWithKey:key] subdataWithRange:NSMakeRange(0, 16)];
684+
case YKFFIDOPinProtocolV2:
685+
return [self ykf_fido2HMACWithKey:[key subdataWithRange:NSMakeRange(0, 32)]];
686+
}
687+
return [NSData new];
688+
}
689+
690+
- (NSData *)ykf_encryptDataWithKey:(NSData *)key pinProtocol:(YKFFIDOPinProtocol)pinProtocol {
691+
switch (pinProtocol) {
692+
case YKFFIDOPinProtocolV1:
693+
return [self ykf_aes256Operation:kCCEncrypt withKey:key];
694+
case YKFFIDOPinProtocolV2: {
695+
NSData *aesKey = [key subdataWithRange:NSMakeRange(32, key.length-32)];
696+
NSData *iv = [NSData ykf_randomDataOfSize:16];
697+
NSData *cipher = [self ykf_cryptOperation:kCCEncrypt algorithm:kCCAlgorithmAES mode:kCCModeCBC key:aesKey iv:iv];
698+
NSMutableData *result = [iv mutableCopy];
699+
[result appendData:cipher];
700+
return result;
701+
}
702+
}
703+
return [NSData new];
704+
}
705+
706+
- (NSData *)ykf_decryptDataWithKey:(NSData *)key pinProtocol:(YKFFIDOPinProtocol)pinProtocol {
707+
switch (pinProtocol) {
708+
case YKFFIDOPinProtocolV1:
709+
return [self ykf_aes256Operation:kCCDecrypt withKey:key];
710+
case YKFFIDOPinProtocolV2: {
711+
NSData *aesKey = [key subdataWithRange:NSMakeRange(32, key.length-32)];
712+
NSData *iv = [self subdataWithRange:NSMakeRange(0, 16)];
713+
NSData *cipher = [self subdataWithRange:NSMakeRange(16, self.length-16)];
714+
return [cipher ykf_cryptOperation:kCCDecrypt algorithm:kCCAlgorithmAES mode:kCCModeCBC key:aesKey iv:iv];
715+
}
716+
}
717+
return [NSData new];
718+
}
719+
720+
@end

YubiKit/YubiKit/Helpers/Additions/YKFNSDataAdditions+Private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ NS_ASSUME_NONNULL_BEGIN
5151

5252
+ (nullable NSData *)ykf_randomDataOfSize:(size_t)sizeInBytes;
5353

54+
- (NSData *)ykf_deriveHKDFWithSalt:(NSData *)salt info:(NSData *)info;
55+
5456
- (NSData *)ykf_toLength:(int)length;
5557

5658
@end

YubiKit/YubiKit/Helpers/Additions/YKFNSDataAdditions.m

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,23 @@ + (nullable NSData *)ykf_randomDataOfSize:(size_t)sizeInBytes {
237237
return [NSData dataWithBytesNoCopy:buff length:sizeInBytes freeWhenDone:YES];
238238
}
239239

240+
// Note that this is not a complete implementation of hkdf. This only outputs 32 bytes.
241+
- (NSData *)ykf_hkdfExtract:(NSData *)salt {
242+
return [self ykf_fido2HMACWithKey:salt];
243+
}
244+
245+
-(NSData *)ykf_hkdfExpand:(NSData *)info {
246+
NSMutableData *data = [info mutableCopy];
247+
UInt8 zero = 0x01;
248+
[data appendBytes:&zero length:1];
249+
return [data ykf_fido2HMACWithKey:self];
250+
}
251+
252+
- (NSData *)ykf_deriveHKDFWithSalt:(NSData *)salt info:(NSData *)info {
253+
NSData *prk = [self ykf_hkdfExtract:salt];
254+
return [prk ykf_hkdfExpand:info];
255+
}
256+
240257
- (NSData *)ykf_toLength:(int)length {
241258
if (self.length == length) {
242259
return self;

YubiKitTests/Tests/FIDO2Tests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ class FIDO2Tests: XCTestCase {
6767
runYubiKitTest { connection, completion in
6868
if connection as? YKFNFCConnection != nil {
6969
connection.fido2TestSession { session in
70-
session.setPin("123456") { _ in
70+
session.setPin("123456") { error in
71+
guard error == nil else { XCTAssertTrue(false, "🔴 Failed to set pin: \(error!)"); return }
7172
session.verifyPin("123456") { error in
73+
guard error == nil else { XCTAssertTrue(false, "🔴 Failed to verify pin: \(error!)"); return }
7274
session.addCredentialAndAssert(algorithm: YKFFIDO2PublicKeyAlgorithmES256, options: [YKFFIDO2OptionRK: false]) { response in
7375
print("✅ Created new FIDO2 credential: \(response)")
7476
session.getAssertionAndAssert(response: response, options: [YKFFIDO2OptionUP: true]) { response in

0 commit comments

Comments
 (0)