From 08a4be8bbe93c64ec9b1fb8264ed7e238b81b451 Mon Sep 17 00:00:00 2001 From: Ross Schulman Date: Tue, 1 Oct 2024 16:55:58 -0400 Subject: [PATCH] Adjust Mdl presentation code to follow new APIs from mobile-sdk-rs (#36) * Adjust Mdl presentation code to follow new APIs from mobile-sdk-rs --- Package.resolved | 4 +- Package.swift | 3 +- Sources/MobileSdk/Credentials.swift | 6 +- Sources/MobileSdk/IsoMdlPresentation.swift | 165 +++++++++++++++++++ Sources/MobileSdk/MDoc.swift | 159 ------------------ Sources/MobileSdk/MDocHolderBLECentral.swift | 4 +- 6 files changed, 173 insertions(+), 168 deletions(-) create mode 100644 Sources/MobileSdk/IsoMdlPresentation.swift diff --git a/Package.resolved b/Package.resolved index 54421ad..99ad63c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/spruceid/mobile-sdk-rs.git", "state" : { - "revision" : "b1e729b2adc97415019925c873b976648a2331cb", - "version" : "0.0.30" + "revision" : "06e0cfcbfe01bccf0e014688be4e0b6275638bd5", + "version" : "0.0.32" } }, { diff --git a/Package.swift b/Package.swift index 091d560..525764b 100644 --- a/Package.swift +++ b/Package.swift @@ -14,8 +14,7 @@ let package = Package( targets: ["SpruceIDMobileSdk"]) ], dependencies: [ - .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.31"), - // .package(path: "../mobile-sdk-rs"), + .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.32"), .package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0") ], targets: [ diff --git a/Sources/MobileSdk/Credentials.swift b/Sources/MobileSdk/Credentials.swift index 62fece5..ecde683 100644 --- a/Sources/MobileSdk/Credentials.swift +++ b/Sources/MobileSdk/Credentials.swift @@ -12,12 +12,12 @@ public class CredentialStore { callback: BLESessionStateDelegate, useL2CAP: Bool = true // , trustedReaders: TrustedReaders - ) -> BLESessionManager? { + ) async -> IsoMdlPresentation? { if let firstMdoc = self.credentials.first(where: {$0 is MDoc}) { - return BLESessionManager(mdoc: firstMdoc as! MDoc, + return await IsoMdlPresentation(mdoc: firstMdoc as! MDoc, engagement: DeviceEngagement.QRCode, callback: callback, - useL2CAP: useL2CAP) + useL2CAP: useL2CAP) } else { return nil } diff --git a/Sources/MobileSdk/IsoMdlPresentation.swift b/Sources/MobileSdk/IsoMdlPresentation.swift new file mode 100644 index 0000000..f21d389 --- /dev/null +++ b/Sources/MobileSdk/IsoMdlPresentation.swift @@ -0,0 +1,165 @@ +import CoreBluetooth +import Foundation +import SpruceIDMobileSdkRs + +public enum DeviceEngagement { + case QRCode +} + +/// To be implemented by the consumer to update the UI +public protocol BLESessionStateDelegate: AnyObject { + func update(state: BLESessionState) +} + +public class IsoMdlPresentation { + var callback: BLESessionStateDelegate + var uuid: UUID + var session: MdlPresentationSession + var mdoc: MDoc + var bleManager: MDocHolderBLECentral! + var useL2CAP: Bool + + init?( + mdoc: MDoc, engagement: DeviceEngagement, + callback: BLESessionStateDelegate, useL2CAP: Bool + ) async { + self.callback = callback + self.uuid = UUID() + self.mdoc = mdoc + self.useL2CAP = useL2CAP + do { + self.session = + try await SpruceIDMobileSdkRs.initializeMdlPresentationFromBytes( + mdoc: mdoc.inner, uuid: self.uuid.uuidString) + bleManager = MDocHolderBLECentral( + callback: self, + serviceUuid: CBUUID(nsuuid: self.uuid), + useL2CAP: useL2CAP) + self.callback.update( + state: .engagingQRCode( + session.getQrCodeUri().data(using: .ascii)!)) + } catch { + print("\(error)") + return nil + } + } + + // Cancel the request mid-transaction and gracefully clean up the BLE stack. + public func cancel() { + bleManager.disconnectFromDevice(session: self.session) + } + + public func submitNamespaces(items: [String: [String: [String]]]) { + do { + let payload = try session.generateResponse(permittedItems: items) + let query = + [ + kSecClass: kSecClassKey, + kSecAttrApplicationLabel: self.mdoc.keyAlias, + kSecReturnRef: true + ] as [String: Any] + + // Find and cast the result as a SecKey instance. + var item: CFTypeRef? + var secKey: SecKey + switch SecItemCopyMatching(query as CFDictionary, &item) { + case errSecSuccess: + // swiftlint:disable force_cast + secKey = item as! SecKey + // swiftlint:enable force_cast + case errSecItemNotFound: + self.callback.update(state: .error(.generic("Key not found"))) + self.cancel() + return + case let status: + self.callback.update( + state: .error(.generic("Keychain read failed: \(status)"))) + self.cancel() + return + } + var error: Unmanaged? + guard + let derSignature = SecKeyCreateSignature( + secKey, + .ecdsaSignatureMessageX962SHA256, + payload as CFData, + &error) as Data? + else { + self.callback.update( + state: .error( + .generic( + "Failed to sign message: \(error.debugDescription)") + )) + self.cancel() + return + } + let response = try session.submitResponse( + derSignature: derSignature) + self.bleManager.writeOutgoingValue(data: response) + } catch { + self.callback.update(state: .error(.generic("\(error)"))) + self.cancel() + } + } +} + +extension IsoMdlPresentation: MDocBLEDelegate { + func callback(message: MDocBLECallback) { + switch message { + case .done: + self.callback.update(state: .success) + case .connected: + self.callback.update(state: .connected) + case .uploadProgress(let value, let total): + self.callback.update(state: .uploadProgress(value, total)) + case .message(let data): + do { + let itemsRequests = try session.handleRequest(request: data) + self.callback.update(state: .selectNamespaces(itemsRequests)) + } catch { + self.callback.update(state: .error(.generic("\(error)"))) + self.cancel() + } + case .error(let error): + self.callback.update( + state: .error(BleSessionError(holderBleError: error))) + self.cancel() + } + } +} + +public enum BleSessionError { + /// When discovery or communication with the peripheral fails + case peripheral(String) + /// When Bluetooth is unusable (e.g. unauthorized). + case bluetooth(CBCentralManager) + /// Generic unrecoverable error + case generic(String) + + init(holderBleError: MdocHolderBleError) { + switch holderBleError { + case .peripheral(let string): + self = .peripheral(string) + case .bluetooth(let string): + self = .bluetooth(string) + } + } +} + +public enum BLESessionState { + /// App should display the error message + case error(BleSessionError) + /// App should display the QR code + case engagingQRCode(Data) + /// App should indicate to the user that BLE connection has been made + case connected + /// App should display an interactive page for the user to chose which values to reveal + case selectNamespaces([ItemsRequest]) + /// App should display the fact that a certain percentage of data has been sent + /// - Parameters: + /// - 0: The number of chunks sent to far + /// - 1: The total number of chunks to be sent + case uploadProgress(Int, Int) + /// App should display a success message and offer to close the page + case success +} diff --git a/Sources/MobileSdk/MDoc.swift b/Sources/MobileSdk/MDoc.swift index 0d9df7c..af4f2ac 100644 --- a/Sources/MobileSdk/MDoc.swift +++ b/Sources/MobileSdk/MDoc.swift @@ -1,4 +1,3 @@ -import CoreBluetooth import CryptoKit import Foundation import SpruceIDMobileSdkRs @@ -30,161 +29,3 @@ public class MDoc: Credential { super.init(id: inner.id()) } } - -public enum DeviceEngagement { - case QRCode -} - -/// To be implemented by the consumer to update the UI -public protocol BLESessionStateDelegate: AnyObject { - func update(state: BLESessionState) -} - -public class BLESessionManager { - var callback: BLESessionStateDelegate - var uuid: UUID - var state: SessionManagerEngaged - var sessionManager: SessionManager? - var mdoc: MDoc - var bleManager: MDocHolderBLECentral! - var useL2CAP: Bool - - init?(mdoc: MDoc, engagement: DeviceEngagement, callback: BLESessionStateDelegate, useL2CAP: Bool) { - self.callback = callback - self.uuid = UUID() - self.mdoc = mdoc - self.useL2CAP = useL2CAP - do { - let sessionData = try SpruceIDMobileSdkRs.initialiseSession( - document: mdoc.inner, - uuid: self.uuid.uuidString) - self.state = sessionData.state - bleManager = MDocHolderBLECentral( - callback: self, - serviceUuid: CBUUID(nsuuid: self.uuid), - useL2CAP: useL2CAP) - self.callback.update(state: .engagingQRCode(sessionData.qrCodeUri.data(using: .ascii)!)) - } catch { - print("\(error)") - return nil - } - } - - // Cancel the request mid-transaction and gracefully clean up the BLE stack. - public func cancel() { - bleManager.disconnectFromDevice() - } - - public func submitNamespaces(items: [String: [String: [String]]]) { - do { - let payload = try SpruceIDMobileSdkRs.submitResponse( - sessionManager: sessionManager!, - permittedItems: items) - let query = - [ - kSecClass: kSecClassKey, - kSecAttrApplicationLabel: self.mdoc.keyAlias, - kSecReturnRef: true - ] as [String: Any] - - // Find and cast the result as a SecKey instance. - var item: CFTypeRef? - var secKey: SecKey - switch SecItemCopyMatching(query as CFDictionary, &item) { - case errSecSuccess: - // swiftlint:disable force_cast - secKey = item as! SecKey - // swiftlint:enable force_cast - case errSecItemNotFound: - self.callback.update(state: .error(.generic("Key not found"))) - self.cancel() - return - case let status: - self.callback.update(state: .error(.generic("Keychain read failed: \(status)"))) - self.cancel() - return - } - var error: Unmanaged? - guard - let derSignature = SecKeyCreateSignature( - secKey, - .ecdsaSignatureMessageX962SHA256, - payload as CFData, - &error) as Data? - else { - self.callback.update( - state: .error(.generic("Failed to sign message: \(error.debugDescription)"))) - self.cancel() - return - } - let response = try SpruceIDMobileSdkRs.submitSignature( - sessionManager: sessionManager!, - derSignature: derSignature) - self.bleManager.writeOutgoingValue(data: response) - } catch { - self.callback.update(state: .error(.generic("\(error)"))) - self.cancel() - } - } -} - -extension BLESessionManager: MDocBLEDelegate { - func callback(message: MDocBLECallback) { - switch message { - case .done: - self.callback.update(state: .success) - case .connected: - self.callback.update(state: .connected) - case .uploadProgress(let value, let total): - self.callback.update(state: .uploadProgress(value, total)) - case .message(let data): - do { - let requestData = try SpruceIDMobileSdkRs.handleRequest(state: self.state, request: data) - self.sessionManager = requestData.sessionManager - self.callback.update(state: .selectNamespaces(requestData.itemsRequests)) - } catch { - self.callback.update(state: .error(.generic("\(error)"))) - self.cancel() - } - case .error(let error): - self.callback.update(state: .error(BleSessionError(holderBleError: error))) - self.cancel() - } - } -} - -public enum BleSessionError { - /// When discovery or communication with the peripheral fails - case peripheral(String) - /// When Bluetooth is unusable (e.g. unauthorized). - case bluetooth(CBCentralManager) - /// Generic unrecoverable error - case generic(String) - - init(holderBleError: MdocHolderBleError) { - switch holderBleError { - case .peripheral(let string): - self = .peripheral(string) - case .bluetooth(let string): - self = .bluetooth(string) - } - } -} - -public enum BLESessionState { - /// App should display the error message - case error(BleSessionError) - /// App should display the QR code - case engagingQRCode(Data) - /// App should indicate to the user that BLE connection has been made - case connected - /// App should display an interactive page for the user to chose which values to reveal - case selectNamespaces([ItemsRequest]) - /// App should display the fact that a certain percentage of data has been sent - /// - Parameters: - /// - 0: The number of chunks sent to far - /// - 1: The total number of chunks to be sent - case uploadProgress(Int, Int) - /// App should display a success message and offer to close the page - case success -} diff --git a/Sources/MobileSdk/MDocHolderBLECentral.swift b/Sources/MobileSdk/MDocHolderBLECentral.swift index 66ca533..628eb25 100644 --- a/Sources/MobileSdk/MDocHolderBLECentral.swift +++ b/Sources/MobileSdk/MDocHolderBLECentral.swift @@ -187,10 +187,10 @@ class MDocHolderBLECentral: NSObject { } } - func disconnectFromDevice() { + func disconnectFromDevice(session: MdlPresentationSession) { let message: Data do { - message = try terminateSession() + message = try session.terminateSession() } catch { print("\(error)") message = Data([0x02])