Skip to content

Commit

Permalink
YubiKit 4.1 (#71)
Browse files Browse the repository at this point in the history
* Add timestamp to calculate OATH OTP.

* Add timestamp to calculate-all OATH OTP.

* Make version public in YKFManagementSession.

* If a connection is present when the delegate is set on the YubiKitManager it will immediately call the didConnect delegate methods.

* Refactored Management session to use TKTLVRecord instead of our own tlv-parsing.

* Removed extra dot at end of version string.

* Forgot to make delegate weak when moving it to a class variable.

* Fixed bug that made it impossible to unlock with password if a command had failed with not authenticated.

* Added stopWithErrorMessage: to nfc connection.

* Added stopNFCConnectionWithErrorMessage: to YubiKitManager.

* Improved Swift method translation.

* Added stopNFCConnectionWithMessage: to YubiKitManager.

* Forgot implementation of stopWithMessage:.

* Added dispatchAfterCurrentCommands to sessions to enable running a code block after all enqueued commands have completed.

* Only signal NFC disconnect if previous state was YKFNFCConnectionStateOpen.

* Send proper error codes when unlocking the OATH application

* Remove unused error definition.

* Remove unused files from project file as well.

* Return defined errors for wrong pin and pin entry locked.

* Less fragile comparision of algorithm type.

* Fixed bug when we tried to calculate more than 8 OATH credentials.

* Accidentally sent a auth required error instead of touch timeout.

* Fixed bug in authentication/touch timeout error handling.

* Reverted to previous behaviour where we singaled an error for NFC modal timeout and user cancel.

* New delegate method to catch NFC timeout and user cancelling the NFC modal.

* Make the didFailConnectingNFC method optional in the connection protocol.

* Fix broken non-trunkated calculation of OATH code.

* Remove broken OATH tests.

* Improved error checks and fixes a bug where authenticateWithManagementKey would fail to call its completion handler upon failure.

* Improved array out of bounds checks and more cautious logging.

* Bumped version number to 4.1.0 and updated changes.
  • Loading branch information
jensutbult authored Oct 26, 2021
1 parent fcceff9 commit 7e75fe8
Show file tree
Hide file tree
Showing 72 changed files with 1,210 additions and 721 deletions.
21 changes: 21 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# YubiKit Changelog

## 4.1.0

- Optional timestamp parameter added to OATH calculate and calculateAll methods.
- Firmware version is now a public variable on `YKFManagementSession`.
- If a connection is already present when setting the `YKFManagerDelegate` it will return that connection immideatly.
- Extra dot at the end of `YKFVersion` string removed.
- Fixed memory issues where we retained the `YKFManagerDelegate`.
- Fixed issue where failing to unlock a key with passcode before sending an OATH command got the session in a non recoverable state.
- Improved control over the messages displayed in the NFC dialog.
- Added `- (void)dispatchBlockOnCommunicationQueue:(YKFConnectionControllerCommunicationQueueBlock)block` to `YKFConnectionControllerProtocol` that will run a block after all enqueued commands has finished.
- Improved error handling in OATH session.
- More robust algorithm comparison in PIV session.
- Fixed bug where an auth required error was sent instead of touch timeout in OATH session.
- Fixed bug where the number of OATH accounts you could read was limited to around 8
- Added new optional connection delegate method that will signal if the NFC dialog was cancelled by the user or timed out.
- Swift package manager header files exluded from Cocoapod distribution.
- Various array out of bounds checks
- Improved error checks
- Fixes bug where authenticateWithManagementKey in the YKFPIVSession would fail to call its completion handler upon failure.
- Fixes broken implementation of non truncated OATH codes

## 4.0.0

This release breaks backwards compatibility with previous versions of the SDK. The reason for this is to make the SDK easier to
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Add YubiKit to your [Podfile](https://guides.cocoapods.org/using/the-podfile.htm
```ruby
use_frameworks!

pod 'YubiKit', '~> 4.0.0'
pod 'YubiKit', '~> 4.1.0'

```
If you want to have latest changes, replace the last line with:
Expand Down
2 changes: 1 addition & 1 deletion YubiKit.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'YubiKit'
s.version = '4.0.0'
s.version = '4.1.0'
s.license = 'Apache 2.0'
s.summary = 'YubiKit is an iOS library provided by Yubico to interact with YubiKeys on iOS devices.'
s.homepage = 'https://github.com/Yubico/yubikit-ios'
Expand Down
48 changes: 20 additions & 28 deletions YubiKit/YubiKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ typedef void (^YKFAccessoryConnectionStateChangeBlock)(YKFAccessoryConnectionSta

@interface YKFAccessoryConnection()

@property (nonatomic, readonly) YKFAccessoryConnectionState state;
@property(nonatomic, weak) id<YKFAccessoryConnectionDelegate> _Nullable delegate;

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ - (instancetype)initWithAccessoryManager:(id<YKFEAAccessoryManagerProtocol>)acce
return self;
}

- (YKFAccessoryConnectionState)state {
return _connectionState;
}

- (YKFSmartCardInterface *)smartCardInterface {
if (!self.connectionController) {
return nil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
#import "YKFSessionError+Private.h"
#import "YKFAPDU+Private.h"

typedef void (^YKFConnectionControllerCommunicationQueueBlock)(NSOperation *operation);

@interface YKFAccessoryConnectionController()

@property (nonatomic) NSOperationQueue *communicationQueue;
Expand Down Expand Up @@ -310,43 +308,6 @@ - (void)execute:(YKFAPDU *)command timeout:(NSTimeInterval)timeout completion:(Y
}];
}

- (void)dispatchOnSequentialQueue:(YKFConnectionControllerCompletionBlock)block delay:(NSTimeInterval)delay {
dispatch_queue_t sharedDispatchQueue = self.communicationQueue.underlyingQueue;

YKFParameterAssertReturn(sharedDispatchQueue);
YKFParameterAssertReturn(block);

block = [block copy]; // heap block

if (delay == 0) {
dispatch_async(sharedDispatchQueue, block);
} else {
NSString *blockId = [NSUUID UUID].UUIDString;

ykf_weak_self();
dispatch_block_t delayedBlock = dispatch_block_create(0, ^{
ykf_safe_strong_self();
dispatch_block_t blockReference = strongSelf.delayedDispatches[blockId];
strongSelf.delayedDispatches[blockId] = nil;

// In case the block started already to run.
if (blockReference && dispatch_block_testcancel(blockReference)) {
return;
}

block();
});

self.delayedDispatches[blockId] = delayedBlock;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), sharedDispatchQueue, delayedBlock);
}
}

- (void)dispatchOnSequentialQueue:(YKFConnectionControllerCompletionBlock)block {
YKFParameterAssertReturn(block);
[self dispatchOnSequentialQueue:block delay:0];
}

- (void)cancelAllCommands {
self.communicationQueue.suspended = YES;
dispatch_suspend(self.communicationQueue.underlyingQueue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@

- (void)didConnectNFC:(YKFNFCConnection *_Nonnull)connection;
- (void)didDisconnectNFC:(YKFNFCConnection *_Nonnull)connection error:(NSError *_Nullable)error;
- (void)didFailConnectingNFC:(NSError *_Nonnull)error;

@end

@interface YKFNFCConnection()

@property (nonatomic, readonly) YKFNFCConnectionState state;
@property(nonatomic, weak) id<YKFNFCConnectionDelegate> _Nullable delegate;

@end
Expand Down
16 changes: 16 additions & 0 deletions YubiKit/YubiKit/Connections/NFCConnection/YKFNFCConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ typedef NS_ENUM(NSUInteger, YKFNFCConnectionState) {
*/
- (void)stop API_AVAILABLE(ios(13.0));

/*!
@method stopWithMessage:
@abstract
Closes the communication with the key and display a message.
*/
- (void)stopWithMessage:(NSString *)message API_AVAILABLE(ios(13.0));

/*!
@method stopWithErrorMessage:
@abstract
Closes the communication with the key and display an error message.
*/
- (void)stopWithErrorMessage:(NSString *)errorMessage API_AVAILABLE(ios(13.0));

/*!
@method cancelCommands
Expand Down
52 changes: 43 additions & 9 deletions YubiKit/YubiKit/Connections/NFCConnection/YKFNFCConnection.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ - (instancetype)init {
return self;
}

- (YKFNFCConnectionState)state {
return _nfcConnectionState;
}

- (YKFSmartCardInterface *)smartCardInterface {
if (!self.connectionController) {
return nil;
Expand Down Expand Up @@ -162,9 +166,29 @@ - (void)stop API_AVAILABLE(ios(13.0)) {
}

[self setAlertMessage:YubiKitExternalLocalization.nfcScanSuccessAlertMessage];
[self updateServicesForSession:self.nfcTagReaderSession tag:nil state:YKFNFCConnectionStateClosed];
[self updateServicesForSession:self.nfcTagReaderSession tag:nil state:YKFNFCConnectionStateClosed errorMessage:nil];
}

- (void)stopWithMessage:(NSString *)message API_AVAILABLE(ios(13.0)) {
if (!self.nfcTagReaderSession) {
YKFLogInfo(@"NFC session already stopped. Ignoring stop request.");
return;
}

[self setAlertMessage:message];
[self updateServicesForSession:self.nfcTagReaderSession tag:nil state:YKFNFCConnectionStateClosed errorMessage:nil];
}

- (void)stopWithErrorMessage:(NSString *)errorMessage API_AVAILABLE(ios(13.0)) {
if (!self.nfcTagReaderSession) {
YKFLogInfo(@"NFC session already stopped. Ignoring stop request.");
return;
}

[self updateServicesForSession:self.nfcTagReaderSession tag:nil state:YKFNFCConnectionStateClosed errorMessage:errorMessage];
}


- (void)cancelCommands API_AVAILABLE(ios(13.0)) {
[self.connectionController cancelAllCommands];
}
Expand Down Expand Up @@ -204,7 +228,7 @@ - (void)tagReaderSession:(NFCTagReaderSession *)session didInvalidateWithError:(
- (void)tagReaderSessionDidBecomeActive:(NFCTagReaderSession *)session API_AVAILABLE(ios(13.0)) {
YKFLogInfo(@"NFC session did become active.");
self.nfcTagReaderSession = session;
[self updateServicesForSession:session tag:nil state:YKFNFCConnectionStatePolling];
[self updateServicesForSession:session tag:nil state:YKFNFCConnectionStatePolling errorMessage:nil];
}

- (void)tagReaderSession:(NFCTagReaderSession *)session didDetectTags:(NSArray<__kindof id<NFCTag>> *)tags API_AVAILABLE(ios(13.0)) {
Expand Down Expand Up @@ -240,7 +264,7 @@ - (void)tagReaderSession:(NFCTagReaderSession *)session didDetectTags:(NSArray<_
}

YKFLogInfo(@"NFC session did connect to tag.");
[strongSelf updateServicesForSession:session tag:activeTag state:YKFNFCConnectionStateOpen];
[strongSelf updateServicesForSession:session tag:activeTag state:YKFNFCConnectionStateOpen errorMessage:nil];
}];
}

Expand All @@ -258,27 +282,38 @@ - (void)updateServicesForSession:(NFCTagReaderSession *)session error:(NSError *

self.nfcConnectionError = error;
[self.nfcTagReaderSession invalidateSessionWithErrorMessage:error.localizedDescription];
[self updateServicesForSession:session tag:nil state:YKFNFCConnectionStateClosed];
[self updateServicesForSession:session tag:nil state:YKFNFCConnectionStateClosed errorMessage:nil];
}

- (void)updateServicesForSession:(NFCTagReaderSession *)session tag:(id<NFCISO7816Tag>)tag state:(YKFNFCConnectionState)state API_AVAILABLE(ios(13.0)) {
- (void)updateServicesForSession:(NFCTagReaderSession *)session tag:(id<NFCISO7816Tag>)tag state:(YKFNFCConnectionState)state errorMessage:(NSString *)errorMessage API_AVAILABLE(ios(13.0)) {
if (self.nfcConnectionState == state) {
return;
}
if (self.nfcTagReaderSession != session) {
return;
}

YKFNFCConnectionState previousState = self.nfcConnectionState;
self.nfcConnectionState = state;

switch (state) {
case YKFNFCConnectionStateClosed:
[self.delegate didDisconnectNFC:self error:self.nfcConnectionError];
if (previousState == YKFNFCConnectionStateOpen) {
[self.delegate didDisconnectNFC:self error:self.nfcConnectionError];
} else {
[self.delegate didFailConnectingNFC:self.nfcConnectionError];
}
self.connectionController = nil;
self.tagDescription = nil;

[self unobserveIso7816TagAvailability];

// invalidating session closes nfc reading sheet
[self.nfcTagReaderSession invalidateSession];
if (errorMessage) {
[self.nfcTagReaderSession invalidateSessionWithErrorMessage:errorMessage];
} else {
[self.nfcTagReaderSession invalidateSession];
}
self.nfcTagReaderSession = nil;
break;

Expand All @@ -301,7 +336,6 @@ - (void)updateServicesForSession:(NFCTagReaderSession *)session tag:(id<NFCISO78
break;
}

self.nfcConnectionState = state;
}

#pragma mark - Tag availability observation
Expand All @@ -318,7 +352,7 @@ - (void)observeIso7816TagAvailability API_AVAILABLE(ios(13.0)) {
} else {
YKFLogInfo(@"NFC tag is no longer available.");
// moving from state of open back to polling/waiting for new tag
[strongSelf updateServicesForSession:strongSelf.nfcTagReaderSession tag:nil state:YKFNFCConnectionStatePolling];
[strongSelf updateServicesForSession:strongSelf.nfcTagReaderSession tag:nil state:YKFNFCConnectionStatePolling errorMessage:nil];
}
}];
[[NSRunLoop mainRunLoop] addTimer:self.iso7816NfcTagAvailabilityTimer forMode:NSDefaultRunLoopMode];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
#import "YKFNSDataAdditions+Private.h"
#import "YKFAPDU+Private.h"

typedef void (^YKFConnectionControllerCommunicationQueueBlock)(NSOperation *operation);

static NSTimeInterval const YKFNFCConnectionDefaultTimeout = 10.0;

@interface YKFNFCConnectionController()
Expand Down Expand Up @@ -126,43 +124,6 @@ - (void)closeConnectionWithCompletion:(nonnull YKFConnectionControllerCompletion
completion();
}

- (void)dispatchOnSequentialQueue:(YKFConnectionControllerCompletionBlock)block delay:(NSTimeInterval)delay {
dispatch_queue_t sharedDispatchQueue = self.communicationQueue.underlyingQueue;

YKFParameterAssertReturn(sharedDispatchQueue);
YKFParameterAssertReturn(block);

block = [block copy]; // heap block

if (delay == 0) {
dispatch_async(sharedDispatchQueue, block);
} else {
NSString *blockId = [NSUUID UUID].UUIDString;

ykf_weak_self();
dispatch_block_t delayedBlock = dispatch_block_create(0, ^{
ykf_safe_strong_self();
dispatch_block_t blockReference = strongSelf.delayedDispatches[blockId];
strongSelf.delayedDispatches[blockId] = nil;

// In case the block started already to run.
if (blockReference && dispatch_block_testcancel(blockReference)) {
return;
}

block();
});

self.delayedDispatches[blockId] = delayedBlock;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), sharedDispatchQueue, delayedBlock);
}
}

- (void)dispatchOnSequentialQueue:(YKFConnectionControllerCompletionBlock)block {
YKFParameterAssertReturn(block);
[self dispatchOnSequentialQueue:block delay:0];
}

- (void)cancelAllCommands {
self.communicationQueue.suspended = YES;
dispatch_suspend(self.communicationQueue.underlyingQueue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,6 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArra

AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
[self.delegate qrCodeScanViewController:self didScanPayload:payload];

YKFLogInfo(@"QR code scanned with value: %@", payload);
}

#pragma mark - UI Setup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import "YKFManagementWriteAPDU.h"
#import "YKFAPDUCommandInstruction.h"
#import "YKFManagementInterfaceConfiguration+Private.h"
#import "YKFManagementDeviceInfo+Private.h"
#import "YKFNSMutableDataAdditions.h"
#import "YKFAssert.h"

Expand All @@ -21,11 +22,11 @@ - (instancetype)initWithConfiguration:(nonnull YKFManagementInterfaceConfigurati

NSMutableData *configData = [[NSMutableData alloc] init];
if (configuration.usbMaskChanged) {
[configData ykf_appendShortWithTag:YKFManagementReadConfigurationTagsUsbEnabled data:configuration.usbEnabledMask];
[configData ykf_appendShortWithTag:YKFManagementTagUSBEnabled data:configuration.usbEnabledMask];
}

if (configuration.nfcMaskChanged) {
[configData ykf_appendShortWithTag:YKFManagementReadConfigurationTagsNfcEnabled data:configuration.nfcEnabledMask];
[configData ykf_appendShortWithTag:YKFManagementTagNFCEnabled data:configuration.nfcEnabledMask];
}

if (reboot) {
Expand Down
21 changes: 12 additions & 9 deletions YubiKit/YubiKit/Connections/Shared/APDU/U2F/YKFU2FRegisterAPDU.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,8 @@
*/
static NSString* const U2FClientDataTypeRegistration = @"navigator.id.finishEnrollment";

/*
Client data as defined in FIDO U2F Raw Message Format
https://fidoalliance.org/specs/u2f-specs-1.0-bt-nfc-id-amendment/fido-u2f-raw-message-formats.html
Note: The "cid_pubkey" is missing in this case since the TLS stack on iOS does not support channel id.
*/
static NSString* const U2FClientDataTemplate = @"\{\"typ\":\"%@\",\"challenge\":\"%@\",\"origin\":\"%@\"}";

static const UInt8 YKFU2FRegisterAPDUEnforceUserPresenceAndSign = 0x03;


@interface YKFU2FRegisterAPDU()

@property (nonatomic, readwrite) NSString *clientData;
Expand All @@ -46,7 +38,18 @@ - (nullable instancetype)initWithChallenge:(NSString *)challenge appId:(NSString
YKFAssertAbortInit(challenge);
YKFAssertAbortInit(appId);

self.clientData = [[NSString alloc] initWithFormat:U2FClientDataTemplate, U2FClientDataTypeRegistration, challenge, appId];
/*
Client data as defined in FIDO U2F Raw Message Format
https://fidoalliance.org/specs/u2f-specs-1.0-bt-nfc-id-amendment/fido-u2f-raw-message-formats.html
Note: The "cid_pubkey" is missing in this case since the TLS stack on iOS does not support channel id.
*/
NSDictionary *jsonDictionary = @{@"type": U2FClientDataTypeRegistration,
@"challenge": challenge,
@"origin": appId};
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:0 error:&error];
self.clientData = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
YKFAssertAbortInit(self.clientData)

NSData *challengeSHA256 = [[self.clientData dataUsingEncoding:NSUTF8StringEncoding] ykf_SHA256];
YKFAssertAbortInit(challengeSHA256);
Expand Down
Loading

0 comments on commit 7e75fe8

Please sign in to comment.