From f59d42cdc8917ca95f9c26dcd0fa07b7eb04c418 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Thu, 15 Aug 2024 11:39:54 +0200 Subject: [PATCH 1/4] Fixed broken FIDO2 demo app. --- .../Other/Demos/FIDO2DemoViewController.swift | 244 +++++------------- 1 file changed, 63 insertions(+), 181 deletions(-) diff --git a/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/FIDO2DemoViewController.swift b/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/FIDO2DemoViewController.swift index 597107a2..3f67d04d 100644 --- a/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/FIDO2DemoViewController.swift +++ b/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/FIDO2DemoViewController.swift @@ -37,48 +37,37 @@ class FIDO2DemoViewController: OtherDemoRootViewController { private var selectedOperation: DemoName? private var pin: String? private var newPin: String? - // MARK: - Actions - private func runDemo() {} - /* - let keyPluggedIn = YubiKitManager.shared.accessorySession.connectionState == .open - let fido2Service: YKFFIDO2SessionProtocol - if YubiKitDeviceCapabilities.supportsISO7816NFCTags && !keyPluggedIn { - guard #available(iOS 13.0, *) else { - fatalError() - } - guard let service = YubiKitManager.shared.nfcSession.fido2Service else { - log(message: "The session with the key is closed. Plugin the key or tap over NFC reader.") - YubiKitManager.shared.nfcSession.start() - return - } - fido2Service = service - } else { - guard let service = YubiKitManager.shared.accessorySession.fido2Service else { - setDemoButtons(enabled: true) - log(message: "The session with the key is closed. Plugin the key before running the demo.") - return - } - fido2Service = service - } + // MARK: - Actions + private func runDemo() { - switch selectedOperation { - case .GetInfo: - runGetInfoDemo(fido2Service: fido2Service) - case .EccDemo: - runECCDemo(fido2Service: fido2Service) - case .EdDSADemo: - runEdDSADemo(fido2Service: fido2Service) - case .Reset: - runApplicationReset(fido2Service: fido2Service) - case .PinVerify: - verify(fido2Service: fido2Service, pin: pin!) - case .PinSet: - set(fido2Service: fido2Service, pin: pin!) - case .PinChange: - change(fido2Service: fido2Service, to: newPin!, oldPin: pin!) - default: - break + self.connection { connection in + connection.fido2Session { session, error in + guard let session else { + self.log(message: "Failed to establish FIDO2 session: \((error! as NSError).code) - \(error!.localizedDescription)") + self.setDemoButtons(enabled: true) + return + } + + switch self.selectedOperation { + case .GetInfo: + self.runGetInfoDemo(fido2Session: session) + case .EccDemo: + self.runECCDemo(fido2Session: session) + case .EdDSADemo: + self.runEdDSADemo(fido2Session: session) + case .Reset: + self.runApplicationReset(fido2Session: session) + case .PinVerify: + self.verify(fido2Session: session, pin: self.pin!) + case .PinSet: + self.set(fido2Session: session, pin: self.pin!) + case .PinChange: + self.change(fido2Session: session, to: self.newPin!, oldPin: self.pin!) + default: + break + } + } } } @@ -86,7 +75,7 @@ class FIDO2DemoViewController: OtherDemoRootViewController { // Stop the session to dismiss the Core NFC system UI. if #available(iOS 13.0, *) { - YubiKitManager.shared.nfcSession.stop() + YubiKitManager.shared.stopNFCConnection() } self.setDemoButtons(enabled: true) @@ -199,11 +188,8 @@ class FIDO2DemoViewController: OtherDemoRootViewController { } } - private func verify(fido2Service: YKFFIDO2SessionProtocol, pin: String) { - fido2Service.executeGetPinRetries { [weak self] (retries, error) in - guard let self = self else { - return - } + private func verify(fido2Session: YKFFIDO2Session, pin: String) { + fido2Session.getPinRetries { retries, error in guard error == nil else { self.log(message: "Error while executing Get PIN Retries: \((error! as NSError).code) - \(error!.localizedDescription)") self.setDemoButtons(enabled: true) @@ -215,21 +201,14 @@ class FIDO2DemoViewController: OtherDemoRootViewController { return } self.log(message: "PIN retries left: \(retries)") - - guard let verifyPinRequest = YKFFIDO2VerifyPinRequest(pin: pin) else { - return - } - fido2Service.execute(verifyPinRequest) { [weak self] (error) in - guard let self = self else { - return - } - self.finishDemo() + fido2Session.verifyPin(pin) { error in guard error == nil else { self.log(message: "Error while executing Verify PIN request: \((error! as NSError).code) - \(error!.localizedDescription)") + self.setDemoButtons(enabled: true) return } - + self.setDemoButtons(enabled: true) self.log(message: "Verify PIN request was successful.") } } @@ -259,15 +238,8 @@ class FIDO2DemoViewController: OtherDemoRootViewController { } } - private func set(fido2Service: YKFFIDO2SessionProtocol, pin: String) { - guard let setPinRequest = YKFFIDO2SetPinRequest(pin: pin) else { - return - } - fido2Service.execute(setPinRequest) { [weak self] (error) in - guard let self = self else { - return - } - + private func set(fido2Session: YKFFIDO2Session, pin: String) { + fido2Session.setPin(pin) { error in self.finishDemo() guard error == nil else { @@ -305,16 +277,10 @@ class FIDO2DemoViewController: OtherDemoRootViewController { } } - private func change(fido2Service: YKFFIDO2SessionProtocol, to newPin: String, oldPin: String) { + private func change(fido2Session: YKFFIDO2Session, to newPin: String, oldPin: String) { setDemoButtons(enabled: false) - guard let changePinRequest = YKFFIDO2ChangePinRequest(newPin: newPin, oldPin: oldPin) else { - return - } - fido2Service.execute(changePinRequest) { [weak self] (error) in - guard let self = self else { - return - } + fido2Session.changePin(oldPin, to: newPin) { error in self.finishDemo() @@ -329,14 +295,12 @@ class FIDO2DemoViewController: OtherDemoRootViewController { // MARK: - GetInfo Demo - private func runGetInfoDemo(fido2Service: YKFFIDO2SessionProtocol) { + private func runGetInfoDemo(fido2Session: YKFFIDO2Session) { setDemoButtons(enabled: false) log(message: "Executing Get Info request...") - fido2Service.executeGetInfoRequest { [weak self] (response, error) in - guard let self = self else { - return - } + + fido2Session.getInfoWithCompletion { response, error in self.finishDemo() @@ -352,71 +316,59 @@ class FIDO2DemoViewController: OtherDemoRootViewController { // MARK: - ES256, EdDSA Demos - private func runECCDemo(fido2Service: YKFFIDO2SessionProtocol) { + private func runECCDemo(fido2Session: YKFFIDO2Session) { setDemoButtons(enabled: false) log(message: "Executing ECC Demo...") log(message: "(!) Touch the key when the LEDs are blinking slowly.") // Not a resident key (stored on the authenticator) and no PIN required. - let makeOptions = [YKFFIDO2MakeCredentialRequestOptionRK: false] + let makeOptions = [YKFFIDO2OptionRK: false] // User presence required (touch) but not user verification (PIN). - let assertionOptions = [YKFFIDO2GetAssertionRequestOptionUP: true] + let assertionOptions = [YKFFIDO2OptionUP: true] - makeFIDO2CredentialWith(fido2Service: fido2Service, algorithm:YKFFIDO2PublicKeyAlgorithmES256, makeOptions: makeOptions, assertionOptions: assertionOptions) + makeFIDO2CredentialWith(fido2Session: fido2Session, algorithm:YKFFIDO2PublicKeyAlgorithmES256, makeOptions: makeOptions, assertionOptions: assertionOptions) } - private func runEdDSADemo(fido2Service: YKFFIDO2SessionProtocol) { + private func runEdDSADemo(fido2Session: YKFFIDO2Session) { setDemoButtons(enabled: false) log(message: "Executing EdDSA (Ed25519) Demo...") log(message: "(!) Touch the key when the LEDs are blinking slowly.") // Resident key (stored on the authenticator) and no PIN required. - let makeOptions = [YKFFIDO2MakeCredentialRequestOptionRK: true] + let makeOptions = [YKFFIDO2OptionRK: true] // User presence and verification disabled (silent authentication). - let assertionOptions = [YKFFIDO2GetAssertionRequestOptionUP: false] + let assertionOptions = [YKFFIDO2OptionUP: false] - makeFIDO2CredentialWith(fido2Service: fido2Service, algorithm:YKFFIDO2PublicKeyAlgorithmEdDSA, makeOptions: makeOptions, assertionOptions: assertionOptions) + makeFIDO2CredentialWith(fido2Session: fido2Session, algorithm:YKFFIDO2PublicKeyAlgorithmEdDSA, makeOptions: makeOptions, assertionOptions: assertionOptions) } - private func makeFIDO2CredentialWith(fido2Service: YKFFIDO2SessionProtocol, algorithm: NSInteger, makeOptions: [String: Bool], assertionOptions: [String: Bool]) { + private func makeFIDO2CredentialWith(fido2Session: YKFFIDO2Session, algorithm: NSInteger, makeOptions: [String: Bool], assertionOptions: [String: Bool]) { /* 1. Setup the Make Credential request. */ - let makeCredentialRequest = YKFFIDO2MakeCredentialRequest() - let data = Data(repeating: 0, count: 32) - makeCredentialRequest.clientDataHash = data let rp = YKFFIDO2PublicKeyCredentialRpEntity() rp.rpId = "yubico.com" rp.rpName = "Yubico" - makeCredentialRequest.rp = rp let user = YKFFIDO2PublicKeyCredentialUserEntity() user.userId = data user.userName = "johnpsmith@yubico.com" user.userDisplayName = "John P. Smith" - makeCredentialRequest.user = user let param = YKFFIDO2PublicKeyCredentialParam() param.alg = algorithm - makeCredentialRequest.pubKeyCredParams = [param] - - makeCredentialRequest.options = makeOptions /* 2. Create the credential. */ - - fido2Service.execute(makeCredentialRequest) { [weak self] (response, error) in - guard let self = self else { - return - } + fido2Session.makeCredential(withClientDataHash: data, rp: rp, user: user, pubKeyCredParams: [param], excludeList: nil, options: makeOptions) { response, error in guard error == nil else { self.log(message: "Error while executing Make Credential request: \((error! as NSError).code) - \(error!.localizedDescription)") @@ -435,50 +387,33 @@ class FIDO2DemoViewController: OtherDemoRootViewController { 3. Setup the Get Assertion request. */ - let getAssertionRequest = YKFFIDO2GetAssertionRequest() - - getAssertionRequest.rpId = "yubico.com" - getAssertionRequest.clientDataHash = data - getAssertionRequest.options = assertionOptions - let credentialDescriptor = YKFFIDO2PublicKeyCredentialDescriptor() credentialDescriptor.credentialId = authenticatorData.credentialId! let credType = YKFFIDO2PublicKeyCredentialType() credType.name = "public-key" credentialDescriptor.credentialType = credType - getAssertionRequest.allowList = [credentialDescriptor] /* 4. Get the assertion (signature). */ - self.getAssertionWith(fido2Service: fido2Service, request: getAssertionRequest) - } - } - - private func getAssertionWith(fido2Service: YKFFIDO2SessionProtocol, request: YKFFIDO2GetAssertionRequest) { - - fido2Service.execute(request) { [weak self] (response, error) in - guard let self = self else { - return - } - - self.finishDemo() + fido2Session.getAssertionWithClientDataHash(data, rpId: "yubico.com", allowList: [credentialDescriptor], options: assertionOptions) { response, error in + self.finishDemo() - guard error == nil else { - self.log(message: "Error while executing Get Assertion request: \((error! as NSError).code) - \(error!.localizedDescription)") - return + guard error == nil else { + self.log(message: "Error while executing Get Assertion request: \((error! as NSError).code) - \(error!.localizedDescription)") + return + } + + self.log(message: "Get Assertion was successful.\n") + self.logFIDO2GetAssertion(response: response!) } - - self.log(message: "Get Assertion was successful.\n") - self.logFIDO2GetAssertion(response: response!) - } } // MARK: - FIDO2 Application Reset - private func runApplicationReset(fido2Service: YKFFIDO2SessionProtocol) { + private func runApplicationReset(fido2Session: YKFFIDO2Session) { setDemoButtons(enabled: false) log(message: "(!) The Reset operation must be executed within 5 seconds after the key was powered up. Otherwise the key will return an error.") @@ -486,10 +421,7 @@ class FIDO2DemoViewController: OtherDemoRootViewController { log(message: "Executing Reset request...") log(message: "(!) Touch the key when the LEDs are blinking slowly.") - fido2Service.executeResetRequest { [weak self] (error) in - guard let self = self else { - return - } + fido2Session.reset { error in self.finishDemo() @@ -502,55 +434,6 @@ class FIDO2DemoViewController: OtherDemoRootViewController { } } - // MARK: - Session State Updates - - override func accessorySessionStateDidChange() { - let sessionState = YubiKitManager.shared.accessorySession.connectionState - if sessionState == .closed { - logTextView.text = nil - setDemoButtons(enabled: true) - } else if sessionState == .open { - if YubiKitDeviceCapabilities.supportsISO7816NFCTags { - guard #available(iOS 13.0, *) else { - fatalError() - } - - DispatchQueue.global(qos: .default).async { [weak self] in - // if NFC UI is visible we consider the button is pressed - // and we run demo as soon as 5ci connected - if (YubiKitManager.shared.nfcSession.nfcConnectionState != .closed) { - guard let self = self else { - return - } - YubiKitManager.shared.nfcSession.stop() - self.runDemo() - } - } - } - } - } - - @available(iOS 13.0, *) - override func nfcSessionStateDidChange() { - // Execute the request after the key(tag) is connected. - switch YubiKitManager.shared.nfcSession.nfcConnectionState { - case .open: - DispatchQueue.global(qos: .default).async { [weak self] in - guard let self = self else { - return - } - - // NOTE: session can be closed during the execution of demo on background thread, - // so we need to make sure that we handle case when service for nfcSession is nil - self.runDemo() - } - case .closed: - self.setDemoButtons(enabled: true) - default: - break - } - } - // MARK: - FIDO2 Response Log Helpers private func logFIDO2GetInfo(response: YKFFIDO2GetInfoResponse) { @@ -647,5 +530,4 @@ class FIDO2DemoViewController: OtherDemoRootViewController { private func log(tag: String, value: String) { log(message: "\n* \(tag):\n\(value)") } - */ } From 83b0ad5605787be3bcb0c7c2a3fa519bd2d4c179 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Thu, 22 Aug 2024 07:40:39 +0200 Subject: [PATCH 2/4] Changed select application to use chaining. Fixed issue where send remaining apdu was malformed. --- .../YKFSmartCardInterface.m | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/YubiKit/YubiKit/Connections/SmartCardInterface/YKFSmartCardInterface.m b/YubiKit/YubiKit/Connections/SmartCardInterface/YKFSmartCardInterface.m index df071de1..f67358e8 100644 --- a/YubiKit/YubiKit/Connections/SmartCardInterface/YKFSmartCardInterface.m +++ b/YubiKit/YubiKit/Connections/SmartCardInterface/YKFSmartCardInterface.m @@ -58,22 +58,23 @@ -(instancetype)initWithConnectionController:(id } - (void)selectApplication:(YKFSelectApplicationAPDU *)apdu completion:(YKFSmartCardInterfaceResponseBlock)completion { - [self.connectionController execute:apdu completion:^(NSData *response, NSError *error, NSTimeInterval executionTime) { + [self executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { if (error) { - completion(nil, error); - return; - } - UInt16 statusCode = [self statusCodeFromKeyResponse:response]; - NSData *data = [self dataFromKeyResponse:response]; - if (statusCode == YKFAPDUErrorCodeNoError) { - completion(data, nil); - } else if (statusCode == YKFAPDUErrorCodeMissingFile || statusCode == YKFAPDUErrorCodeInsNotSupported) { - NSError *error = [YKFSessionError errorWithCode:YKFSessionErrorMissingApplicationCode]; - completion(nil, error); + if ([error isKindOfClass:[YKFSessionError class]]) { + UInt16 statusCode = error.code; + if (statusCode == YKFAPDUErrorCodeMissingFile || statusCode == YKFAPDUErrorCodeInsNotSupported) { + NSError *error = [YKFSessionError errorWithCode:YKFSessionErrorMissingApplicationCode]; + completion(nil, error); + } else { + NSAssert(TRUE, @"The key returned an unexpected SW when selecting application"); + NSError *error = [YKFSessionError errorWithCode:YKFSessionErrorUnexpectedStatusCode]; + completion(nil, error); + } + } else { + completion(nil, error); + } } else { - NSAssert(TRUE, @"The key returned an unexpected SW when selecting application"); - NSError *error = [YKFSessionError errorWithCode:YKFSessionErrorUnexpectedStatusCode]; - completion(nil, error); + completion(data, nil); } }]; } @@ -101,7 +102,7 @@ - (void)executeCommand:(YKFAPDU *)apdu sendRemainingIns:(YKFSmartCardInterfaceSe ins = 0xA5; break; } - YKFAPDU *sendRemainingApdu = [[YKFAPDU alloc] initWithData:[NSData dataWithBytes:(unsigned char[]){0x00, ins, 0x00, 0x00} length:4]]; + YKFAPDU *sendRemainingApdu = [[YKFAPDU alloc] initWithData:[NSData dataWithBytes:(unsigned char[]){0x00, ins, 0x00, 0x00, 0x00} length:5]]; // Queue a new request recursively [self executeCommand:sendRemainingApdu sendRemainingIns:sendRemainingIns timeout:timeout data:data completion:completion]; return; From d0ef22bbb526f05312dc823244c9522e090879f3 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Thu, 22 Aug 2024 07:50:12 +0200 Subject: [PATCH 3/4] Remove deprecated NFC entitlement from demo app. --- YubiKitDemo/YubiKitDemo/YubiKitDemo.entitlements | 1 - 1 file changed, 1 deletion(-) diff --git a/YubiKitDemo/YubiKitDemo/YubiKitDemo.entitlements b/YubiKitDemo/YubiKitDemo/YubiKitDemo.entitlements index 91c98721..2bb4dee1 100644 --- a/YubiKitDemo/YubiKitDemo/YubiKitDemo.entitlements +++ b/YubiKitDemo/YubiKitDemo/YubiKitDemo.entitlements @@ -4,7 +4,6 @@ com.apple.developer.nfc.readersession.formats - NDEF TAG From d019c73f472450e407de7f2a8a3354685dc407a7 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Thu, 22 Aug 2024 07:50:55 +0200 Subject: [PATCH 4/4] Add USB-C support to OATH demo. --- .../Demos/OATH/CredentialsProvider.swift | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/OATH/CredentialsProvider.swift b/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/OATH/CredentialsProvider.swift index 6b99d811..5339b5bb 100644 --- a/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/OATH/CredentialsProvider.swift +++ b/YubiKitDemo/YubiKitDemo/Demos/Other/Demos/OATH/CredentialsProvider.swift @@ -21,7 +21,12 @@ class CredentialsProvider: NSObject, ObservableObject, YKFManagerDelegate { override init() { super.init() YubiKitManager.shared.delegate = self - YubiKitManager.shared.startAccessoryConnection() + if YubiKitDeviceCapabilities.supportsMFIAccessoryKey { + YubiKitManager.shared.startAccessoryConnection() + } + if YubiKitDeviceCapabilities.supportsSmartCardOverUSBC { + YubiKitManager.shared.startSmartCardConnection() + } } var nfcConnection: YKFNFCConnection? @@ -51,6 +56,19 @@ class CredentialsProvider: NSObject, ObservableObject, YKFManagerDelegate { credentials.removeAll() } + var smartCardConnection: YKFSmartCardConnection? + + func didConnectSmartCard(_ connection: YKFSmartCardConnection) { + smartCardConnection = connection + refresh() + } + + func didDisconnectSmartCard(_ connection: YKFSmartCardConnection, error: (any Error)?) { + smartCardConnection = nil + session = nil + credentials.removeAll() + } + var connectionCallback: ((_ connection: YKFConnectionProtocol) -> Void)? func connection(completion: @escaping (_ connection: YKFConnectionProtocol) -> Void) {