Skip to content

Commit

Permalink
Experimental extensions.
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed Nov 5, 2024
1 parent 72728a9 commit bcb721c
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ NS_ASSUME_NONNULL_BEGIN
excludeList:(NSArray * _Nullable)excludeList
pinAuth:(NSData * _Nullable)pinAuth
pinProtocol:(NSUInteger)pinProtocol
options:(NSDictionary * _Nullable)options NS_DESIGNATED_INITIALIZER;
options:(NSDictionary * _Nullable)options
extensions:(NSDictionary * _Nullable)extensions NS_DESIGNATED_INITIALIZER;

//- (nullable instancetype)initWithRequest:(YKFFIDO2MakeCredentialRequest *)request NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import "YKFAssert.h"
#import "YKFFIDO2Type.h"
#import "YKFFIDO2Type+Private.h"
#import "YKFNSDataAdditions.h"

typedef NS_ENUM(NSUInteger, YKFFIDO2MakeCredentialAPDUKey) {
YKFFIDO2MakeCredentialAPDUKeyClientDataHash = 0x01,
Expand All @@ -39,13 +40,32 @@ - (nullable instancetype)initWithClientDataHash:(NSData *)clientDataHash
excludeList:(NSArray * _Nullable)excludeList
pinAuth:(NSData * _Nullable)pinAuth
pinProtocol:(NSUInteger)pinProtocol
options:(NSDictionary * _Nullable)options {
options:(NSDictionary * _Nullable)options
extensions:(NSDictionary * _Nullable)extensions {

YKFAssertAbortInit(clientDataHash);
YKFAssertAbortInit(rp);
YKFAssertAbortInit(user);
YKFAssertAbortInit(pubKeyCredParams);


/*
dictionary PublicKeyCredentialCreationOptionsJSON {
required PublicKeyCredentialRpEntity rp;
required PublicKeyCredentialUserEntityJSON user;
required Base64URLString challenge;
required sequence<PublicKeyCredentialParameters> pubKeyCredParams;
unsigned long timeout;
sequence<PublicKeyCredentialDescriptorJSON> excludeCredentials = [];
AuthenticatorSelectionCriteria authenticatorSelection; // This is hardcoded for now
sequence<DOMString> hints = [];
DOMString attestation = "none";
sequence<DOMString> attestationFormats = [];
AuthenticationExtensionsClientInputsJSON extensions;
};
*/

NSMutableDictionary *requestDictionary = [[NSMutableDictionary alloc] init];

// Client Data Hash
Expand Down Expand Up @@ -84,6 +104,47 @@ - (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);
}


// Pin Auth
if (pinAuth) {
requestDictionary[YKFCBORInteger(YKFFIDO2MakeCredentialAPDUKeyPinAuth)] = YKFCBORByteString(pinAuth);
Expand Down
4 changes: 2 additions & 2 deletions YubiKit/YubiKit/Connections/Shared/Errors/YKFFIDO2Error.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
static NSString* const YKFFIDO2ErrorNO_CREDENTIALS = @"No valid credentials provided.";
static NSString* const YKFFIDO2ErrorUSER_ACTION_TIMEOUT = @"Timeout waiting for user interaction.";
static NSString* const YKFFIDO2ErrorNOT_ALLOWED = @"Continuation command, such as, authenticatorGetNextAssertion not allowed.";
static NSString* const YKFFIDO2ErrorPIN_INVALID = @"PIN Invalid.";
static NSString* const YKFFIDO2ErrorPIN_BLOCKED = @"PIN Blocked.";
static NSString* const YKFFIDO2ErrorPIN_INVALID = @"PIN is invalid.";
static NSString* const YKFFIDO2ErrorPIN_BLOCKED = @"PIN is blocked.";
static NSString* const YKFFIDO2ErrorPIN_AUTH_INVALID = @"PIN authentication,pinAuth, verification failed.";
static NSString* const YKFFIDO2ErrorPIN_AUTH_BLOCKED = @"PIN authentication,pinAuth, blocked. Requires power recycle to reset.";
static NSString* const YKFFIDO2ErrorPIN_NOT_SET = @"No PIN has been set.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#import <Foundation/Foundation.h>

@class YKFFIDO2AuthenticatorData;
@class YKFFIDO2AuthenticatorData, YKFCBORMap;

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -35,11 +35,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface YKFFIDO2MakeCredentialResponse: NSObject

/*!
The authenticator data object.
*/
@property (nonatomic, readonly) NSData *authData;

/*!
The attestation statement format identifier.
*/
Expand All @@ -49,7 +44,13 @@ NS_ASSUME_NONNULL_BEGIN
The attestation statement, whose format is identified by the "fmt" object member. The client should treat it
as an opaque object.
*/
@property (nonatomic, readonly) NSData *attStmt;
@property (nonatomic, readonly, nullable) NSData *attStmt;

/*!
The authenticator data object.
*/
@property (nonatomic, readonly) NSData *authData;


/*!
@abstract
Expand Down Expand Up @@ -152,6 +153,10 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, readonly, nullable) NSData *coseEncodedCredentialPublicKey;



@property (nonatomic, readonly, nullable) YKFCBORMap *extensions;

/*
Not available: instances should be created only by the library.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ @interface YKFFIDO2AuthenticatorData()
@property (nonatomic, readwrite) NSData *aaguid;
@property (nonatomic, readwrite) NSData *credentialId;
@property (nonatomic, readwrite) NSData *coseEncodedCredentialPublicKey;
@property (nonatomic, readwrite) YKFCBORMap *extensions;

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

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

@property (nonatomic, readwrite) NSData *ctapAttestationObject;
@property (nonatomic, readwrite) NSData *webauthnAttestationObject;

@end

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

// If
if (self.flags & YKFFIDO2AuthenticatorDataFlagAttested) {
NSUInteger attestedCredentialDataOffset = 37;

NSData *attestedCredentialData = [data subdataWithRange:NSMakeRange(attestedCredentialDataOffset, data.length - attestedCredentialDataOffset)];
YKFAssertAbortInit(attestedCredentialData.length >= 18); // AAGUID(16) + CredentialIdLength(2)

self.aaguid = [attestedCredentialData subdataWithRange:NSMakeRange(0, 16)];

UInt8 *attestedCredentialDataBytes = (UInt8 *)attestedCredentialData.bytes;
UInt16 bigEndianCredentialIdLength = *((UInt16 *)(&attestedCredentialDataBytes[16]));
UInt16 attestAndExtensionsStartIndex = 37;
NSData *attestAndExtensionsData = [data subdataWithRange:NSMakeRange(attestAndExtensionsStartIndex, data.length - attestAndExtensionsStartIndex)];
self.aaguid = [attestAndExtensionsData subdataWithRange:NSMakeRange(0, 16)];
UInt16 bigEndianCredentialIdLength = *((UInt16 *)(&(attestAndExtensionsData.bytes)[16]));
UInt16 credentialIdLength = CFSwapInt16BigToHost(bigEndianCredentialIdLength);
self.credentialId = [attestAndExtensionsData subdataWithRange:NSMakeRange(16 + 2, credentialIdLength)];
UInt16 coseKeyStartIndex = 16 + 2 + credentialIdLength;
NSData *coseKeyAndExtensionData = [attestAndExtensionsData subdataWithRange:NSMakeRange(coseKeyStartIndex, attestAndExtensionsData.length - coseKeyStartIndex)];

if (credentialIdLength > 0) {
NSUInteger coseKeyOffset = 18 + credentialIdLength;

YKFAssertAbortInit(attestedCredentialData.length > coseKeyOffset);
self.credentialId = [attestedCredentialData subdataWithRange:NSMakeRange(18, credentialIdLength)];

NSRange coseKeyRange = NSMakeRange(coseKeyOffset, attestedCredentialData.length - coseKeyOffset);
self.coseEncodedCredentialPublicKey = [attestedCredentialData subdataWithRange: coseKeyRange];
YKFAssertAbortInit(self.coseEncodedCredentialPublicKey.length > 0);
YKFCBORMap *coseKeyCborMap = nil;
YKFCBORMap *extensionCborMap = nil;
NSInputStream *decoderInputStream = [[NSInputStream alloc] initWithData:coseKeyAndExtensionData];
[decoderInputStream open];
coseKeyCborMap = [YKFCBORDecoder decodeObjectFrom:decoderInputStream];
YKFAssertAbortInit(coseKeyCborMap);
self.coseEncodedCredentialPublicKey = [YKFCBOREncoder encodeMap:coseKeyCborMap];
if (self.flags & YKFFIDO2AuthenticatorDataFlagExtensionData) {
extensionCborMap = [YKFCBORDecoder decodeObjectFrom:decoderInputStream];
YKFAssertAbortInit(extensionCborMap);
self.extensions = extensionCborMap;
}
[decoderInputStream close];
} else if (self.flags & YKFFIDO2AuthenticatorDataFlagExtensionData) {
UInt16 attestAndExtensionsStartIndex = 37;
NSData *attestAndExtensionsData = [data subdataWithRange:NSMakeRange(attestAndExtensionsStartIndex, data.length - attestAndExtensionsStartIndex)];
YKFCBORMap *extensionCborMap = nil;
NSInputStream *decoderInputStream = [[NSInputStream alloc] initWithData:attestAndExtensionsData];
[decoderInputStream open];
extensionCborMap = [YKFCBORDecoder decodeObjectFrom:decoderInputStream];
YKFAssertAbortInit(extensionCborMap);
self.extensions = extensionCborMap;
[decoderInputStream close];
}
}
return self;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,16 @@ typedef NS_ENUM(NSUInteger, YKFFIDO2SessionKeyState) {
options:(NSDictionary * _Nullable)options
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion;


- (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
rp:(YKFFIDO2PublicKeyCredentialRpEntity *)rp
user:(YKFFIDO2PublicKeyCredentialUserEntity *)user
pubKeyCredParams:(NSArray *)pubKeyCredParams
excludeList:(NSArray * _Nullable)excludeList
options:(NSDictionary * _Nullable)options
extensions:(NSDictionary * _Nullable)extensions
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion;

/*!
@method getAssertionWithClientDataHash:rpId:allowList:options:completion:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
excludeList:(NSArray * _Nullable)excludeList
options:(NSDictionary * _Nullable)options
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion {
[self makeCredentialWithClientDataHash:clientDataHash rp:rp user:user pubKeyCredParams:pubKeyCredParams excludeList:excludeList options:options extensions:nil completion:completion];
}

- (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
rp:(YKFFIDO2PublicKeyCredentialRpEntity *)rp
user:(YKFFIDO2PublicKeyCredentialUserEntity *)user
pubKeyCredParams:(NSArray *)pubKeyCredParams
excludeList:(NSArray * _Nullable)excludeList
options:(NSDictionary * _Nullable)options
extensions:(NSDictionary * _Nullable)extensions
completion:(YKFFIDO2SessionMakeCredentialCompletionBlock)completion {
YKFParameterAssertReturn(clientDataHash);
YKFParameterAssertReturn(rp);
YKFParameterAssertReturn(user);
Expand All @@ -328,7 +339,7 @@ - (void)makeCredentialWithClientDataHash:(NSData *)clientDataHash
}
}

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

if (!apdu) {
YKFSessionError *error = [YKFFIDO2Error errorWithCode:YKFFIDO2ErrorCodeOTHER];
Expand Down
2 changes: 2 additions & 0 deletions YubiKitDemo/YubiKitDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,7 @@
DEVELOPMENT_TEAM = LQA3CS5MM7;
HEADER_SEARCH_PATHS = "../YubiKit/**";
INFOPLIST_FILE = YubiKitDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 2.9.2;
PRODUCT_BUNDLE_IDENTIFIER = com.yubico.YubiKitDemoApp;
Expand All @@ -775,6 +776,7 @@
DEVELOPMENT_TEAM = LQA3CS5MM7;
HEADER_SEARCH_PATHS = "../YubiKit/**";
INFOPLIST_FILE = YubiKitDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 2.9.2;
PRODUCT_BUNDLE_IDENTIFIER = com.yubico.YubiKitDemoApp;
Expand Down
Loading

0 comments on commit bcb721c

Please sign in to comment.