Skip to content

Commit

Permalink
Merge branch 'jens/raw-commands'
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed Mar 28, 2024
2 parents 178fba5 + d557f8c commit 2159171
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 6 deletions.
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# YubiKit Changelog

## 4.5.0

- Add support for sending and returning raw commands to the `YKFConnectionProtocol`.

## 4.4.1

- Removed deprecated PCSCLayer.
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.4.1'
pod 'YubiKit', '~> 4.5.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.4.1'
s.version = '4.5.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
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,22 @@ - (void)managementSession:(YKFManagementSessionCompletion _Nonnull)callback {
}];
}

- (void)executeRawCommand:(NSData *)data completion:(YKFRawComandCompletion)completion {
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
[self.connectionController execute:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error, NSTimeInterval executionTime) {
completion(data, error);
}];
}

- (void)executeRawCommand:(NSData *)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion)completion {
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
[self.connectionController execute:apdu
timeout:timeout
completion:^(NSData * _Nullable response, NSError * _Nullable error, NSTimeInterval executionTime) {
completion(response, error);
}];
}

- (void)dealloc {
self.observeAccessoryConnection = NO;
self.observeApplicationState = NO;
Expand Down
16 changes: 16 additions & 0 deletions YubiKit/YubiKit/Connections/NFCConnection/YKFNFCConnection.m
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ - (void)managementSession:(YKFManagementSessionCompletion _Nonnull)callback {
}];
}

- (void)executeRawCommand:(NSData *)data completion:(YKFRawComandCompletion)completion {
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
[self.connectionController execute:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error, NSTimeInterval executionTime) {
completion(data, error);
}];
}

- (void)executeRawCommand:(NSData *)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion)completion {
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
[self.connectionController execute:apdu
timeout:timeout
completion:^(NSData * _Nullable response, NSError * _Nullable error, NSTimeInterval executionTime) {
completion(response, error);
}];
}

- (void)dealloc {
if (@available(iOS 13.0, *)) {
[self unobserveIso7816TagAvailability];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,20 @@ - (void)u2fSession:(YKFU2FSessionCompletionBlock _Nonnull)completion {
userInfo:@{NSLocalizedDescriptionKey: @"U2F session not supported by YKFSmartCardConnection."}]);
}

- (void)executeRawCommand:(NSData *)data completion:(YKFRawComandCompletion)completion {
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
[self.connectionController execute:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error, NSTimeInterval executionTime) {
completion(data, error);
}];
}

- (void)executeRawCommand:(NSData *)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion)completion {
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
[self.connectionController execute:apdu
timeout:timeout
completion:^(NSData * _Nullable response, NSError * _Nullable error, NSTimeInterval executionTime) {
completion(response, error);
}];
}

@end
19 changes: 18 additions & 1 deletion YubiKit/YubiKit/Connections/YKFConnectionProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#ifndef YKFConnectionProtocol_h
#define YKFConnectionProtocol_h

@class YKFOATHSession, YKFU2FSession, YKFFIDO2Session, YKFPIVSession, YKFChallengeResponseSession, YKFManagementSession, YKFSmartCardInterface;
@class YKFOATHSession, YKFU2FSession, YKFFIDO2Session, YKFPIVSession, YKFChallengeResponseSession, YKFManagementSession, YKFSmartCardInterface, YKFAPDU;

@protocol YKFConnectionProtocol<NSObject>

Expand Down Expand Up @@ -66,6 +66,23 @@ typedef void (^YKFManagementSessionCompletion)(YKFManagementSession *_Nullable,
/// when none of the supplied sessions can be used.
@property (nonatomic, readonly) YKFSmartCardInterface *_Nullable smartCardInterface;

typedef void (^YKFRawComandCompletion)(NSData *_Nullable, NSError *_Nullable);

/// @abstract Send a APDU and get the unparsed result as an NSData from the YubiKey.
/// @param apdu The APDU to send to the YubiKey.
/// @param completion The unparsed result from the YubiKey or an error.
/// @discussion Use this for communicating with the YubiKey by sending APDUs to the it. Only use this
/// when the `SmartCardInterface` or any of the supplied sessions can not be used.
- (void)executeRawCommand:(NSData *_Nonnull)apdu completion:(YKFRawComandCompletion _Nonnull)completion;

/// @abstract Send command as NSData and get the unparsed result as an NSData from the YubiKey.
/// @param data The NSData to send to the YubiKey.
/// @param timeout The timeout to wait before cancelling the command sent to the YubiKey.
/// @param completion The unparsed result from the YubiKey or an error.
/// @discussion Use this for communicating with the YubiKey by sending APDUs to the it. Only use this
/// when the `SmartCardInterface` or any of the supplied sessions can not be used.
- (void)executeRawCommand:(NSData *_Nonnull)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion _Nonnull)completion;

@end

#endif
2 changes: 2 additions & 0 deletions YubiKit/YubiKit/YubiKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#import "YKFNFCError.h"
#import "YKFNFCTagDescription.h"

#import "YKFConnectionProtocol.h"

#import "YKFAccessoryConnection.h"
#import "YKFAccessoryDescription.h"

Expand Down
53 changes: 50 additions & 3 deletions YubiKitTests/Tests/ConnectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import XCTest
class ConnectionTests: XCTestCase {

func testConnectionDelegate() throws {
YubiKitManager.shared.startAccessoryConnection()
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
YubiKitManager.shared.startAccessoryConnection()
}
YubiKitManager.shared.startSmartCardConnection()
let connectionExpectation = expectation(description: "Get a YubiKey Connection")
let firstConnection = YubiKeyConnectionTester()
Expand Down Expand Up @@ -45,7 +47,9 @@ class ConnectionTests: XCTestCase {
func testNFCTimeOutError() throws {
let connectionExpectation = expectation(description: "Get a YubiKey Connection")
let connectionTester = YubiKeyConnectionTester()
YubiKitManager.shared.startAccessoryConnection() // We need to start the accessory connection so we can skip this test if a 5Ci key is inserted
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
YubiKitManager.shared.startAccessoryConnection() // We need to start the accessory connection so we can skip this test if a 5Ci key is inserted
}
YubiKitManager.shared.startSmartCardConnection()

Thread.sleep(forTimeInterval: 0.5)
Expand Down Expand Up @@ -76,7 +80,9 @@ class ConnectionTests: XCTestCase {
func testNFCUserCancelError() throws {
let connectionExpectation = expectation(description: "Got a YubiKey failed to connect to NFC error")
let connectionTester = YubiKeyConnectionTester()
YubiKitManager.shared.startAccessoryConnection() // We need to start the accessory connection so we can skip this test if a 5Ci key is inserted
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
YubiKitManager.shared.startAccessoryConnection() // We need to start the accessory connection so we can skip this test if a 5Ci key is inserted
}
YubiKitManager.shared.startSmartCardConnection()
Thread.sleep(forTimeInterval: 0.5)

Expand All @@ -101,6 +107,35 @@ class ConnectionTests: XCTestCase {
}
}
}

func testRawCommands() throws {
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
YubiKitManager.shared.startAccessoryConnection()
}
YubiKitManager.shared.startSmartCardConnection()
let connectionExpectation = expectation(description: "Get a YubiKey Connection")
let firstConnection = YubiKeyConnectionTester()
Thread.sleep(forTimeInterval: 0.5)
firstConnection.connection { connection, error in
// Select Management application
let data = Data([0x00, 0xa4, 0x04, 0x00, 0x08, 0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17])
connection.executeRawCommand(data) { data, error in
guard let data else { XCTFail("Failed with error: \(error!)"); return }
print(data.hexDescription)
XCTAssertEqual(data.statusCode, 0x9000)
connectionExpectation.fulfill()
}
}

waitForExpectations(timeout: 20.0) { error in
// If we get an error then the expectation has timed out and we need to stop all connections
if error != nil {
YubiKitManager.shared.stopNFCConnection()
Thread.sleep(forTimeInterval: 5.0) // In case it was a NFC connection wait for the modal to dismiss
}
}
}

}

class YubiKeyConnectionTester: NSObject {
Expand Down Expand Up @@ -188,3 +223,15 @@ extension YubiKeyConnectionTester: YKFManagerDelegate {
smartCardConnection = nil
}
}

extension Data {
/// Returns the SW from a key response.
var statusCode: UInt16 {
get {
guard self.count >= 2 else {
return 0x00
}
return UInt16(self[self.count - 2]) << 8 + UInt16(self[self.count - 1])
}
}
}

0 comments on commit 2159171

Please sign in to comment.