Skip to content

Commit 2159171

Browse files
committed
Merge branch 'jens/raw-commands'
2 parents 178fba5 + d557f8c commit 2159171

File tree

9 files changed

+124
-6
lines changed

9 files changed

+124
-6
lines changed

Changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# YubiKit Changelog
22

3+
## 4.5.0
4+
5+
- Add support for sending and returning raw commands to the `YKFConnectionProtocol`.
6+
37
## 4.4.1
48

59
- Removed deprecated PCSCLayer.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Add YubiKit to your [Podfile](https://guides.cocoapods.org/using/the-podfile.htm
5151
```ruby
5252
use_frameworks!
5353

54-
pod 'YubiKit', '~> 4.4.1'
54+
pod 'YubiKit', '~> 4.5.0'
5555

5656
```
5757
If you want to have latest changes, replace the last line with:

YubiKit.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'YubiKit'
3-
s.version = '4.4.1'
3+
s.version = '4.5.0'
44
s.license = 'Apache 2.0'
55
s.summary = 'YubiKit is an iOS library provided by Yubico to interact with YubiKeys on iOS devices.'
66
s.homepage = 'https://github.com/Yubico/yubikit-ios'

YubiKit/YubiKit/Connections/AccessoryConnection/YKFAccessoryConnection.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,22 @@ - (void)managementSession:(YKFManagementSessionCompletion _Nonnull)callback {
182182
}];
183183
}
184184

185+
- (void)executeRawCommand:(NSData *)data completion:(YKFRawComandCompletion)completion {
186+
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
187+
[self.connectionController execute:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error, NSTimeInterval executionTime) {
188+
completion(data, error);
189+
}];
190+
}
191+
192+
- (void)executeRawCommand:(NSData *)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion)completion {
193+
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
194+
[self.connectionController execute:apdu
195+
timeout:timeout
196+
completion:^(NSData * _Nullable response, NSError * _Nullable error, NSTimeInterval executionTime) {
197+
completion(response, error);
198+
}];
199+
}
200+
185201
- (void)dealloc {
186202
self.observeAccessoryConnection = NO;
187203
self.observeApplicationState = NO;

YubiKit/YubiKit/Connections/NFCConnection/YKFNFCConnection.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,22 @@ - (void)managementSession:(YKFManagementSessionCompletion _Nonnull)callback {
138138
}];
139139
}
140140

141+
- (void)executeRawCommand:(NSData *)data completion:(YKFRawComandCompletion)completion {
142+
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
143+
[self.connectionController execute:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error, NSTimeInterval executionTime) {
144+
completion(data, error);
145+
}];
146+
}
147+
148+
- (void)executeRawCommand:(NSData *)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion)completion {
149+
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
150+
[self.connectionController execute:apdu
151+
timeout:timeout
152+
completion:^(NSData * _Nullable response, NSError * _Nullable error, NSTimeInterval executionTime) {
153+
completion(response, error);
154+
}];
155+
}
156+
141157
- (void)dealloc {
142158
if (@available(iOS 13.0, *)) {
143159
[self unobserveIso7816TagAvailability];

YubiKit/YubiKit/Connections/SmartCardConnection/YKFSmartCardConnection.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,20 @@ - (void)u2fSession:(YKFU2FSessionCompletionBlock _Nonnull)completion {
169169
userInfo:@{NSLocalizedDescriptionKey: @"U2F session not supported by YKFSmartCardConnection."}]);
170170
}
171171

172+
- (void)executeRawCommand:(NSData *)data completion:(YKFRawComandCompletion)completion {
173+
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
174+
[self.connectionController execute:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error, NSTimeInterval executionTime) {
175+
completion(data, error);
176+
}];
177+
}
178+
179+
- (void)executeRawCommand:(NSData *)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion)completion {
180+
YKFAPDU *apdu = [[YKFAPDU alloc] initWithData:data];
181+
[self.connectionController execute:apdu
182+
timeout:timeout
183+
completion:^(NSData * _Nullable response, NSError * _Nullable error, NSTimeInterval executionTime) {
184+
completion(response, error);
185+
}];
186+
}
187+
172188
@end

YubiKit/YubiKit/Connections/YKFConnectionProtocol.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#ifndef YKFConnectionProtocol_h
1616
#define YKFConnectionProtocol_h
1717

18-
@class YKFOATHSession, YKFU2FSession, YKFFIDO2Session, YKFPIVSession, YKFChallengeResponseSession, YKFManagementSession, YKFSmartCardInterface;
18+
@class YKFOATHSession, YKFU2FSession, YKFFIDO2Session, YKFPIVSession, YKFChallengeResponseSession, YKFManagementSession, YKFSmartCardInterface, YKFAPDU;
1919

2020
@protocol YKFConnectionProtocol<NSObject>
2121

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

69+
typedef void (^YKFRawComandCompletion)(NSData *_Nullable, NSError *_Nullable);
70+
71+
/// @abstract Send a APDU and get the unparsed result as an NSData from the YubiKey.
72+
/// @param apdu The APDU to send to the YubiKey.
73+
/// @param completion The unparsed result from the YubiKey or an error.
74+
/// @discussion Use this for communicating with the YubiKey by sending APDUs to the it. Only use this
75+
/// when the `SmartCardInterface` or any of the supplied sessions can not be used.
76+
- (void)executeRawCommand:(NSData *_Nonnull)apdu completion:(YKFRawComandCompletion _Nonnull)completion;
77+
78+
/// @abstract Send command as NSData and get the unparsed result as an NSData from the YubiKey.
79+
/// @param data The NSData to send to the YubiKey.
80+
/// @param timeout The timeout to wait before cancelling the command sent to the YubiKey.
81+
/// @param completion The unparsed result from the YubiKey or an error.
82+
/// @discussion Use this for communicating with the YubiKey by sending APDUs to the it. Only use this
83+
/// when the `SmartCardInterface` or any of the supplied sessions can not be used.
84+
- (void)executeRawCommand:(NSData *_Nonnull)data timeout:(NSTimeInterval)timeout completion:(YKFRawComandCompletion _Nonnull)completion;
85+
6986
@end
7087

7188
#endif

YubiKit/YubiKit/YubiKit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#import "YKFNFCError.h"
3030
#import "YKFNFCTagDescription.h"
3131

32+
#import "YKFConnectionProtocol.h"
33+
3234
#import "YKFAccessoryConnection.h"
3335
#import "YKFAccessoryDescription.h"
3436

YubiKitTests/Tests/ConnectionTests.swift

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import XCTest
1717
class ConnectionTests: XCTestCase {
1818

1919
func testConnectionDelegate() throws {
20-
YubiKitManager.shared.startAccessoryConnection()
20+
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
21+
YubiKitManager.shared.startAccessoryConnection()
22+
}
2123
YubiKitManager.shared.startSmartCardConnection()
2224
let connectionExpectation = expectation(description: "Get a YubiKey Connection")
2325
let firstConnection = YubiKeyConnectionTester()
@@ -45,7 +47,9 @@ class ConnectionTests: XCTestCase {
4547
func testNFCTimeOutError() throws {
4648
let connectionExpectation = expectation(description: "Get a YubiKey Connection")
4749
let connectionTester = YubiKeyConnectionTester()
48-
YubiKitManager.shared.startAccessoryConnection() // We need to start the accessory connection so we can skip this test if a 5Ci key is inserted
50+
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
51+
YubiKitManager.shared.startAccessoryConnection() // We need to start the accessory connection so we can skip this test if a 5Ci key is inserted
52+
}
4953
YubiKitManager.shared.startSmartCardConnection()
5054

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

@@ -101,6 +107,35 @@ class ConnectionTests: XCTestCase {
101107
}
102108
}
103109
}
110+
111+
func testRawCommands() throws {
112+
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
113+
YubiKitManager.shared.startAccessoryConnection()
114+
}
115+
YubiKitManager.shared.startSmartCardConnection()
116+
let connectionExpectation = expectation(description: "Get a YubiKey Connection")
117+
let firstConnection = YubiKeyConnectionTester()
118+
Thread.sleep(forTimeInterval: 0.5)
119+
firstConnection.connection { connection, error in
120+
// Select Management application
121+
let data = Data([0x00, 0xa4, 0x04, 0x00, 0x08, 0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17])
122+
connection.executeRawCommand(data) { data, error in
123+
guard let data else { XCTFail("Failed with error: \(error!)"); return }
124+
print(data.hexDescription)
125+
XCTAssertEqual(data.statusCode, 0x9000)
126+
connectionExpectation.fulfill()
127+
}
128+
}
129+
130+
waitForExpectations(timeout: 20.0) { error in
131+
// If we get an error then the expectation has timed out and we need to stop all connections
132+
if error != nil {
133+
YubiKitManager.shared.stopNFCConnection()
134+
Thread.sleep(forTimeInterval: 5.0) // In case it was a NFC connection wait for the modal to dismiss
135+
}
136+
}
137+
}
138+
104139
}
105140

106141
class YubiKeyConnectionTester: NSObject {
@@ -188,3 +223,15 @@ extension YubiKeyConnectionTester: YKFManagerDelegate {
188223
smartCardConnection = nil
189224
}
190225
}
226+
227+
extension Data {
228+
/// Returns the SW from a key response.
229+
var statusCode: UInt16 {
230+
get {
231+
guard self.count >= 2 else {
232+
return 0x00
233+
}
234+
return UInt16(self[self.count - 2]) << 8 + UInt16(self[self.count - 1])
235+
}
236+
}
237+
}

0 commit comments

Comments
 (0)