Skip to content

Commit bcb721c

Browse files
committed
Experimental extensions.
1 parent 72728a9 commit bcb721c

File tree

9 files changed

+196
-35
lines changed

9 files changed

+196
-35
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ NS_ASSUME_NONNULL_BEGIN
2828
excludeList:(NSArray * _Nullable)excludeList
2929
pinAuth:(NSData * _Nullable)pinAuth
3030
pinProtocol:(NSUInteger)pinProtocol
31-
options:(NSDictionary * _Nullable)options NS_DESIGNATED_INITIALIZER;
31+
options:(NSDictionary * _Nullable)options
32+
extensions:(NSDictionary * _Nullable)extensions NS_DESIGNATED_INITIALIZER;
3233

3334
//- (nullable instancetype)initWithRequest:(YKFFIDO2MakeCredentialRequest *)request NS_DESIGNATED_INITIALIZER;
3435
- (instancetype)init NS_UNAVAILABLE;

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

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#import "YKFAssert.h"
1818
#import "YKFFIDO2Type.h"
1919
#import "YKFFIDO2Type+Private.h"
20+
#import "YKFNSDataAdditions.h"
2021

2122
typedef NS_ENUM(NSUInteger, YKFFIDO2MakeCredentialAPDUKey) {
2223
YKFFIDO2MakeCredentialAPDUKeyClientDataHash = 0x01,
@@ -39,13 +40,32 @@ - (nullable instancetype)initWithClientDataHash:(NSData *)clientDataHash
3940
excludeList:(NSArray * _Nullable)excludeList
4041
pinAuth:(NSData * _Nullable)pinAuth
4142
pinProtocol:(NSUInteger)pinProtocol
42-
options:(NSDictionary * _Nullable)options {
43+
options:(NSDictionary * _Nullable)options
44+
extensions:(NSDictionary * _Nullable)extensions {
4345

4446
YKFAssertAbortInit(clientDataHash);
4547
YKFAssertAbortInit(rp);
4648
YKFAssertAbortInit(user);
4749
YKFAssertAbortInit(pubKeyCredParams);
4850

51+
52+
/*
53+
dictionary PublicKeyCredentialCreationOptionsJSON {
54+
required PublicKeyCredentialRpEntity rp;
55+
required PublicKeyCredentialUserEntityJSON user;
56+
required Base64URLString challenge;
57+
required sequence<PublicKeyCredentialParameters> pubKeyCredParams;
58+
unsigned long timeout;
59+
sequence<PublicKeyCredentialDescriptorJSON> excludeCredentials = [];
60+
AuthenticatorSelectionCriteria authenticatorSelection; // This is hardcoded for now
61+
sequence<DOMString> hints = [];
62+
DOMString attestation = "none";
63+
sequence<DOMString> attestationFormats = [];
64+
AuthenticationExtensionsClientInputsJSON extensions;
65+
};
66+
67+
*/
68+
4969
NSMutableDictionary *requestDictionary = [[NSMutableDictionary alloc] init];
5070

5171
// Client Data Hash
@@ -84,6 +104,47 @@ - (nullable instancetype)initWithClientDataHash:(NSData *)clientDataHash
84104
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyOptions)] = YKFCBORMap(mutableOptions);
85105
}
86106

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+
}
137+
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);
145+
}
146+
147+
87148
// Pin Auth
88149
if (pinAuth) {
89150
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyPinAuth)] = YKFCBORByteString(pinAuth);

YubiKit/YubiKit/Connections/Shared/Errors/YKFFIDO2Error.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
static NSString* const YKFFIDO2ErrorNO_CREDENTIALS = @"No valid credentials provided.";
5050
static NSString* const YKFFIDO2ErrorUSER_ACTION_TIMEOUT = @"Timeout waiting for user interaction.";
5151
static NSString* const YKFFIDO2ErrorNOT_ALLOWED = @"Continuation command, such as, authenticatorGetNextAssertion not allowed.";
52-
static NSString* const YKFFIDO2ErrorPIN_INVALID = @"PIN Invalid.";
53-
static NSString* const YKFFIDO2ErrorPIN_BLOCKED = @"PIN Blocked.";
52+
static NSString* const YKFFIDO2ErrorPIN_INVALID = @"PIN is invalid.";
53+
static NSString* const YKFFIDO2ErrorPIN_BLOCKED = @"PIN is blocked.";
5454
static NSString* const YKFFIDO2ErrorPIN_AUTH_INVALID = @"PIN authentication,pinAuth, verification failed.";
5555
static NSString* const YKFFIDO2ErrorPIN_AUTH_BLOCKED = @"PIN authentication,pinAuth, blocked. Requires power recycle to reset.";
5656
static NSString* const YKFFIDO2ErrorPIN_NOT_SET = @"No PIN has been set.";

YubiKit/YubiKit/Connections/Shared/Requests/FIDO2/YKFFIDO2MakeCredentialResponse.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
#import <Foundation/Foundation.h>
1616

17-
@class YKFFIDO2AuthenticatorData;
17+
@class YKFFIDO2AuthenticatorData, YKFCBORMap;
1818

1919
NS_ASSUME_NONNULL_BEGIN
2020

@@ -35,11 +35,6 @@ NS_ASSUME_NONNULL_BEGIN
3535
*/
3636
@interface YKFFIDO2MakeCredentialResponse: NSObject
3737

38-
/*!
39-
The authenticator data object.
40-
*/
41-
@property (nonatomic, readonly) NSData *authData;
42-
4338
/*!
4439
The attestation statement format identifier.
4540
*/
@@ -49,7 +44,13 @@ NS_ASSUME_NONNULL_BEGIN
4944
The attestation statement, whose format is identified by the "fmt" object member. The client should treat it
5045
as an opaque object.
5146
*/
52-
@property (nonatomic, readonly) NSData *attStmt;
47+
@property (nonatomic, readonly, nullable) NSData *attStmt;
48+
49+
/*!
50+
The authenticator data object.
51+
*/
52+
@property (nonatomic, readonly) NSData *authData;
53+
5354

5455
/*!
5556
@abstract
@@ -152,6 +153,10 @@ NS_ASSUME_NONNULL_BEGIN
152153
*/
153154
@property (nonatomic, readonly, nullable) NSData *coseEncodedCredentialPublicKey;
154155

156+
157+
158+
@property (nonatomic, readonly, nullable) YKFCBORMap *extensions;
159+
155160
/*
156161
Not available: instances should be created only by the library.
157162
*/

YubiKit/YubiKit/Connections/Shared/Requests/FIDO2/YKFFIDO2MakeCredentialResponse.m

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ @interface YKFFIDO2AuthenticatorData()
4141
@property (nonatomic, readwrite) NSData *aaguid;
4242
@property (nonatomic, readwrite) NSData *credentialId;
4343
@property (nonatomic, readwrite) NSData *coseEncodedCredentialPublicKey;
44+
@property (nonatomic, readwrite) YKFCBORMap *extensions;
4445

4546
- (instancetype)initWithData:(NSData *)data NS_DESIGNATED_INITIALIZER;
4647

@@ -56,7 +57,6 @@ @interface YKFFIDO2MakeCredentialResponse()
5657

5758
@property (nonatomic, readwrite) NSData *ctapAttestationObject;
5859
@property (nonatomic, readwrite) NSData *webauthnAttestationObject;
59-
6060
@end
6161

6262
@implementation YKFFIDO2MakeCredentialResponse
@@ -159,28 +159,40 @@ - (instancetype)initWithData:(NSData *)data {
159159
UInt32 bigEndianSignCount = *((UInt32 *)(&dataBytes[33]));
160160
self.signCount = CFSwapInt32BigToHost(bigEndianSignCount);
161161

162+
// If
162163
if (self.flags & YKFFIDO2AuthenticatorDataFlagAttested) {
163-
NSUInteger attestedCredentialDataOffset = 37;
164-
165-
NSData *attestedCredentialData = [data subdataWithRange:NSMakeRange(attestedCredentialDataOffset, data.length - attestedCredentialDataOffset)];
166-
YKFAssertAbortInit(attestedCredentialData.length >= 18); // AAGUID(16) + CredentialIdLength(2)
167-
168-
self.aaguid = [attestedCredentialData subdataWithRange:NSMakeRange(0, 16)];
169-
170-
UInt8 *attestedCredentialDataBytes = (UInt8 *)attestedCredentialData.bytes;
171-
UInt16 bigEndianCredentialIdLength = *((UInt16 *)(&attestedCredentialDataBytes[16]));
164+
UInt16 attestAndExtensionsStartIndex = 37;
165+
NSData *attestAndExtensionsData = [data subdataWithRange:NSMakeRange(attestAndExtensionsStartIndex, data.length - attestAndExtensionsStartIndex)];
166+
self.aaguid = [attestAndExtensionsData subdataWithRange:NSMakeRange(0, 16)];
167+
UInt16 bigEndianCredentialIdLength = *((UInt16 *)(&(attestAndExtensionsData.bytes)[16]));
172168
UInt16 credentialIdLength = CFSwapInt16BigToHost(bigEndianCredentialIdLength);
169+
self.credentialId = [attestAndExtensionsData subdataWithRange:NSMakeRange(16 + 2, credentialIdLength)];
170+
UInt16 coseKeyStartIndex = 16 + 2 + credentialIdLength;
171+
NSData *coseKeyAndExtensionData = [attestAndExtensionsData subdataWithRange:NSMakeRange(coseKeyStartIndex, attestAndExtensionsData.length - coseKeyStartIndex)];
173172

174-
if (credentialIdLength > 0) {
175-
NSUInteger coseKeyOffset = 18 + credentialIdLength;
176-
177-
YKFAssertAbortInit(attestedCredentialData.length > coseKeyOffset);
178-
self.credentialId = [attestedCredentialData subdataWithRange:NSMakeRange(18, credentialIdLength)];
179-
180-
NSRange coseKeyRange = NSMakeRange(coseKeyOffset, attestedCredentialData.length - coseKeyOffset);
181-
self.coseEncodedCredentialPublicKey = [attestedCredentialData subdataWithRange: coseKeyRange];
182-
YKFAssertAbortInit(self.coseEncodedCredentialPublicKey.length > 0);
173+
YKFCBORMap *coseKeyCborMap = nil;
174+
YKFCBORMap *extensionCborMap = nil;
175+
NSInputStream *decoderInputStream = [[NSInputStream alloc] initWithData:coseKeyAndExtensionData];
176+
[decoderInputStream open];
177+
coseKeyCborMap = [YKFCBORDecoder decodeObjectFrom:decoderInputStream];
178+
YKFAssertAbortInit(coseKeyCborMap);
179+
self.coseEncodedCredentialPublicKey = [YKFCBOREncoder encodeMap:coseKeyCborMap];
180+
if (self.flags & YKFFIDO2AuthenticatorDataFlagExtensionData) {
181+
extensionCborMap = [YKFCBORDecoder decodeObjectFrom:decoderInputStream];
182+
YKFAssertAbortInit(extensionCborMap);
183+
self.extensions = extensionCborMap;
183184
}
185+
[decoderInputStream close];
186+
} else if (self.flags & YKFFIDO2AuthenticatorDataFlagExtensionData) {
187+
UInt16 attestAndExtensionsStartIndex = 37;
188+
NSData *attestAndExtensionsData = [data subdataWithRange:NSMakeRange(attestAndExtensionsStartIndex, data.length - attestAndExtensionsStartIndex)];
189+
YKFCBORMap *extensionCborMap = nil;
190+
NSInputStream *decoderInputStream = [[NSInputStream alloc] initWithData:attestAndExtensionsData];
191+
[decoderInputStream open];
192+
extensionCborMap = [YKFCBORDecoder decodeObjectFrom:decoderInputStream];
193+
YKFAssertAbortInit(extensionCborMap);
194+
self.extensions = extensionCborMap;
195+
[decoderInputStream close];
184196
}
185197
}
186198
return self;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,16 @@ typedef NS_ENUM(NSUInteger, YKFFIDO2SessionKeyState) {
389389
options:(NSDictionary * _Nullable)options
390390
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion;
391391

392+
393+
- (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
394+
rp:(YKFFIDO2PublicKeyCredentialRpEntity *)rp
395+
user:(YKFFIDO2PublicKeyCredentialUserEntity *)user
396+
pubKeyCredParams:(NSArray *)pubKeyCredParams
397+
excludeList:(NSArray * _Nullable)excludeList
398+
options:(NSDictionary * _Nullable)options
399+
extensions:(NSDictionary * _Nullable)extensions
400+
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion;
401+
392402
/*!
393403
@method getAssertionWithClientDataHash:rpId:allowList:options:completion:
394404

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,17 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
309309
excludeList:(NSArray * _Nullable)excludeList
310310
options:(NSDictionary * _Nullable)options
311311
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion {
312+
[self makeCredentialWithClientDataHash:clientDataHash rp:rp user:user pubKeyCredParams:pubKeyCredParams excludeList:excludeList options:options extensions:nil completion:completion];
313+
}
314+
315+
- (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
316+
rp:(YKFFIDO2PublicKeyCredentialRpEntity *)rp
317+
user:(YKFFIDO2PublicKeyCredentialUserEntity *)user
318+
pubKeyCredParams:(NSArray *)pubKeyCredParams
319+
excludeList:(NSArray * _Nullable)excludeList
320+
options:(NSDictionary * _Nullable)options
321+
extensions:(NSDictionary * _Nullable)extensions
322+
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion {
312323
YKFParameterAssertReturn(clientDataHash);
313324
YKFParameterAssertReturn(rp);
314325
YKFParameterAssertReturn(user);
@@ -328,7 +339,7 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
328339
}
329340
}
330341

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

333344
if (!apdu) {
334345
YKFSessionError *error = [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER];

YubiKitDemo/YubiKitDemo.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@
754754
DEVELOPMENT_TEAM = LQA3CS5MM7;
755755
HEADER_SEARCH_PATHS = "../YubiKit/**";
756756
INFOPLIST_FILE = YubiKitDemo/Info.plist;
757+
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
757758
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
758759
MARKETING_VERSION = 2.9.2;
759760
PRODUCT_BUNDLE_IDENTIFIER = com.yubico.YubiKitDemoApp;
@@ -775,6 +776,7 @@
775776
DEVELOPMENT_TEAM = LQA3CS5MM7;
776777
HEADER_SEARCH_PATHS = "../YubiKit/**";
777778
INFOPLIST_FILE = YubiKitDemo/Info.plist;
779+
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
778780
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
779781
MARKETING_VERSION = 2.9.2;
780782
PRODUCT_BUNDLE_IDENTIFIER = com.yubico.YubiKitDemoApp;

0 commit comments

Comments
 (0)