diff --git a/Samples/OATHSample/OATHSample/OATHListModel.swift b/Samples/OATHSample/OATHSample/OATHListModel.swift index c2909a7..ed649a4 100644 --- a/Samples/OATHSample/OATHSample/OATHListModel.swift +++ b/Samples/OATHSample/OATHSample/OATHListModel.swift @@ -30,7 +30,6 @@ class OATHListModel: ObservableObject { } @MainActor func startWiredConnection() { - print("startWiredConnection()") wiredConnectionTask?.cancel() wiredConnectionTask = Task { do { diff --git a/YubiKit/YubiKit.xcodeproj/project.pbxproj b/YubiKit/YubiKit.xcodeproj/project.pbxproj index be20929..584ad81 100644 --- a/YubiKit/YubiKit.xcodeproj/project.pbxproj +++ b/YubiKit/YubiKit.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 51BBE3EB273D1A3800DA47CC /* YubiKit.docc in Sources */ = {isa = PBXBuildFile; fileRef = 51BBE3EA273D1A3800DA47CC /* YubiKit.docc */; }; 51BBE3F1273D1A3800DA47CC /* YubiKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51BBE3E6273D1A3800DA47CC /* YubiKit.framework */; }; 51BBE3F7273D1A3800DA47CC /* YubiKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 51BBE3E9273D1A3800DA47CC /* YubiKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B401F7762B17B8DD00C541D1 /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B401F7752B17B8DD00C541D1 /* Logger+Extensions.swift */; }; B40528332987C31E00FC33AB /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40528322987C31E00FC33AB /* DeviceInfo.swift */; }; B405283729894E7600FC33AB /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B405283629894E7600FC33AB /* DeviceConfig.swift */; }; B408BA8F2948FA2100001B2F /* Stream+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B408BA8E2948FA2100001B2F /* Stream+Extensions.swift */; }; @@ -53,6 +54,7 @@ 51BBE3E9273D1A3800DA47CC /* YubiKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YubiKit.h; sourceTree = ""; }; 51BBE3EA273D1A3800DA47CC /* YubiKit.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = YubiKit.docc; sourceTree = ""; }; 51BBE3F0273D1A3800DA47CC /* YubiKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YubiKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B401F7752B17B8DD00C541D1 /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = ""; }; B40528322987C31E00FC33AB /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = ""; }; B405283629894E7600FC33AB /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = ""; }; B408BA8E2948FA2100001B2F /* Stream+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stream+Extensions.swift"; sourceTree = ""; }; @@ -170,6 +172,7 @@ B40BCB7C29422721009F39BD /* Connection+Extensions.swift */, B408BA8E2948FA2100001B2F /* Stream+Extensions.swift */, B4F10D4E2AFD39D600F0AFCA /* String+Extensions.swift */, + B401F7752B17B8DD00C541D1 /* Logger+Extensions.swift */, ); path = Utilities; sourceTree = ""; @@ -301,6 +304,7 @@ B456E21B274FCA26004471DE /* ManagementSession.swift in Sources */, B47FDD992939FAD100AFF70A /* ConnectionHelper.swift in Sources */, B4451F00275FD2B5002690BB /* APDU.swift in Sources */, + B401F7762B17B8DD00C541D1 /* Logger+Extensions.swift in Sources */, B47FDD9B293A15AE00AFF70A /* NSLock+Extensions.swift in Sources */, B456E213274D2403004471DE /* NFCConnection.swift in Sources */, B408BA8F2948FA2100001B2F /* Stream+Extensions.swift in Sources */, diff --git a/YubiKit/YubiKit/APDU.swift b/YubiKit/YubiKit/APDU.swift index 1b9317b..8c9ec09 100644 --- a/YubiKit/YubiKit/APDU.swift +++ b/YubiKit/YubiKit/APDU.swift @@ -16,7 +16,11 @@ import Foundation /// Data model for encapsulating an APDU command, as defined by the ISO/IEC 7816-4 standard. -public struct APDU { +public struct APDU: CustomStringConvertible { + + public var description: String { + return "APDU(cla: \(cla.hexValue), ins: \(ins.hexValue), p1: \(p1.hexValue), p2: \(p2.hexValue), command: \(data.hexEncodedString), type: \(String(describing: type))" + } public enum ApduType { case short diff --git a/YubiKit/YubiKit/LightningConnection.swift b/YubiKit/YubiKit/LightningConnection.swift index d0934a0..43103b5 100644 --- a/YubiKit/YubiKit/LightningConnection.swift +++ b/YubiKit/YubiKit/LightningConnection.swift @@ -16,6 +16,7 @@ import Foundation import ExternalAccessory +import OSLog /// A connection to the YubiKey utilizing the Lightning port and External Accessory framework. @available(iOS 16.0, *) @@ -45,11 +46,12 @@ public final actor LightningConnection: Connection, InternalConnection { // Starts lightning and wait for a connection public static func connection() async throws -> Connection { - print("⚡️ LightningConnection, connection() called") + Logger.lightning.debug(#function) return try await manager.connection() } public func close(error: Error?) async { + Logger.lightning.debug(#function) closingHandler?() closingContinuations.forEach { continuation in continuation.resume(returning: error) @@ -68,22 +70,17 @@ public final actor LightningConnection: Connection, InternalConnection { public func connectionDidClose() async -> Error? { if accessoryConnection == nil { - print("⚡️ LightningConnection, connectionDidClose() but no session so bailing out.") return nil } return await withCheckedContinuation { continuation in - print("⚡️ LightningConnection, connectionDidClose() append closing continuation.") closingContinuations.append(continuation) } } internal func send(apdu: APDU) async throws -> Response { - print("⚡️ LightningConnection, send() \(apdu).") guard let accessoryConnection, let outputStream = accessoryConnection.session.outputStream, let inputStream = accessoryConnection.session.inputStream else { throw "No current session" } var data = Data([0x00]) // YLP iAP2 Signal data.append(apdu.data) - print("\(outputStream.streamStatus)") - print("\(inputStream.streamStatus)") try outputStream.writeToYubiKey(data: data) while true { @@ -91,7 +88,6 @@ public final actor LightningConnection: Connection, InternalConnection { let result = try inputStream.readFromYubiKey() if result.isEmpty { throw "Empty result" } let status = Response.StatusCode(data: result.subdata(in: result.count-2..? func connection() async throws -> LightningConnection { let task = Task { [connectionTask] in - connectionTask?.cancel() // Cancel any previous request for a connection + if let connectionTask { + Logger.lightning.debug("A call to connection() is already awaiting a connection, cancel it before proceeding.") + connectionTask.cancel() + } return try await self._connection() } connectionTask = task @@ -127,11 +126,11 @@ fileprivate actor LightningConnectionManager { } onCancel: { task.cancel() } + Logger.lightning.debug("returned: \(String(describing: value))") return value } private func _connection() async throws -> LightningConnection { - print("⚡️ LightningConnectionManager, _connection()") if let currentConnection { await currentConnection.close(error: nil) @@ -145,8 +144,6 @@ fileprivate actor LightningConnectionManager { return } accessoryWrapper.connection { result in - print("⚡️ LightningConnectionManager, _connection() got result: \(result), pass it to continuation: \(String(describing: continuation))") - print("⚡️ LightningConnectionManager, Task.isCancelled = \(Task.isCancelled)") switch result { case .success(let accessoryConnection): let connection = LightningConnection(connection: accessoryConnection, closingHandler: { [weak self] in @@ -161,12 +158,10 @@ fileprivate actor LightningConnectionManager { } case .failure(let error): continuation.resume(throwing: error) - print("⚡️ LightningConnectionManager, remove \(String(describing: continuation)) after failure") } } } } onCancel: { - print("⚡️ LightningConnectionManager, onCancel called") accessoryWrapper.stop() } } @@ -223,9 +218,7 @@ fileprivate class EAAccessoryWrapper: NSObject, StreamDelegate { private var closingHandler: ((Error?) -> Void)? internal func connection(completion handler: @escaping (Result) -> Void) { - print("⚡️ EAAccessoryWrapper, schedule connection()") queue.async { - print("⚡️ EAAccessoryWrapper, connection()") // Signal closure and cancel previous connection handlers // Swap out old handlers to the new ones self.closingHandler?("Closed by new call to connection()") @@ -248,16 +241,14 @@ fileprivate class EAAccessoryWrapper: NSObject, StreamDelegate { } func stream(_ aStream: Stream, handle eventCode: Stream.Event) { - print("⚡️ EAAccessoryWrapper, Got stream event: \(eventCode) on stream: \(aStream)") + Logger.lightning.debug("EAAccessoryWrapper, Got stream event: \(String(describing: eventCode)) on stream: \(aStream)") } private func connectToKey(with accessory: EAAccessory) -> AccessoryConnection? { - print("⚡️ EAAccessoryWrapper, connectToKey()") guard accessory.isYubiKey else { return nil } guard let session = EASession(accessory: accessory, forProtocol: "com.yubico.ylp") else { return nil } let connection = AccessoryConnection(accessory: accessory, session: session) - print("⚡️ EAAccessoryWrapper, connected to: \(session)") - + Logger.lightning.debug("EAAccessoryWrapper, connected to: \(session)") connection.open() connection.session.outputStream!.delegate = self connection.session.inputStream!.delegate = self @@ -267,25 +258,22 @@ fileprivate class EAAccessoryWrapper: NSObject, StreamDelegate { internal func connectionDidClose(completion handler: @escaping (Error?) -> Void) { queue.async { - print("⚡️ EAAccessoryWrapper, connectionDidClose()") assert(self.closingHandler == nil, "Closing completion already registered.") self.closingHandler = handler } } private func start() { - print("⚡️ EAAccessoryWrapper, start()") self.queue.async { NotificationCenter.default.addObserver(forName: .EAAccessoryDidConnect, object: self.manager, queue: nil) { [weak self] notification in self?.queue.async { - print("⚡️ EAAccessoryWrapper, EAAccessoryDidConnect") guard self?.state == .scanning, self?.connectingHandler != nil, let accessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory, let connection = self?.connectToKey(with: accessory) else { return } - print("⚡️ EAAccessoryWrapper, did connect to key") + Logger.lightning.debug("EAAccessoryWrapper, connected to: \(accessory)") self?.state = .connected(connection) self?.connectingHandler?(.success(connection)) self?.connectingHandler = nil @@ -312,9 +300,7 @@ fileprivate class EAAccessoryWrapper: NSObject, StreamDelegate { } internal func stop() { - print("⚡️ EAAccessoryWrapper, scheduled stop() with state = \(self.state) in \(Thread.current)") queue.async { - print("⚡️ EAAccessoryWrapper, stop() with state = \(self.state) in \(Thread.current)") switch self.state { case .ready: break; diff --git a/YubiKit/YubiKit/Management/ManagementSession.swift b/YubiKit/YubiKit/Management/ManagementSession.swift index 7d7d74c..b828ee7 100644 --- a/YubiKit/YubiKit/Management/ManagementSession.swift +++ b/YubiKit/YubiKit/Management/ManagementSession.swift @@ -14,6 +14,7 @@ import Foundation import CryptoTokenKit +import OSLog public enum ManagementSessionError: Error { /// Application is not supported on this YubiKey. @@ -44,7 +45,7 @@ public final actor ManagementSession: Session, InternalSession { public nonisolated let version: Version private init(connection: Connection) async throws { - let result = try await connection.selectApplication(application: .management) + let result = try await connection.selectApplication(.management) guard let version = Version(withManagementResult: result) else { throw ManagementSessionError.unexpectedData } self.version = version self._connection = connection @@ -53,6 +54,7 @@ public final actor ManagementSession: Session, InternalSession { } public static func session(withConnection connection: Connection) async throws -> ManagementSession { + Logger.management.debug(#function) // Close active session if there is one let internalConnection = connection as? InternalConnection let currentSession = await internalConnection?.session() @@ -63,6 +65,7 @@ public final actor ManagementSession: Session, InternalSession { } public func end() async { + Logger.management.debug(#function) let internalConnection = await internalConnection() await internalConnection?.setSession(nil) await setConnection(nil) @@ -70,6 +73,7 @@ public final actor ManagementSession: Session, InternalSession { /// Returns the DeviceInfo for the connected YubiKey. public func getDeviceInfo() async throws -> DeviceInfo { + Logger.management.debug(#function) guard let connection = _connection else { throw SessionError.noConnection } let apdu = APDU(cla: 0, ins: 0x1d, p1: 0, p2: 0) let data = try await connection.send(apdu: apdu) @@ -78,18 +82,21 @@ public final actor ManagementSession: Session, InternalSession { /// Check whether an application is supported over the specified transport. public func isApplicationSupported(_ application: ApplicationType, overTransport transport: DeviceTransport) async throws -> Bool { + Logger.management.debug(#function) let deviceInfo = try await getDeviceInfo() return deviceInfo.isApplicationSupported(application, overTransport: transport) } /// Check whether an application is enabled over the specified transport. public func isApplicationEnabled(_ application: ApplicationType, overTransport transport: DeviceTransport) async throws -> Bool { + Logger.management.debug(#function) let deviceInfo = try await getDeviceInfo() return deviceInfo.config.isApplicationEnabled(application, overTransport: transport) } /// Enable or disable an application over the specified transport. public func setEnabled(_ enabled: Bool, application: ApplicationType, overTransport transport: DeviceTransport, reboot: Bool = false) async throws { + Logger.management.debug("setEnabled: \(enabled), application: \(String(describing: application)), overTransport: \(String(describing: transport)), reboot: \(reboot)") guard let connection = _connection else { throw SessionError.noConnection } let deviceInfo = try await getDeviceInfo() guard enabled != deviceInfo.config.isApplicationEnabled(application, overTransport: transport) else { return } @@ -121,16 +128,18 @@ public final actor ManagementSession: Session, InternalSession { /// Disable an application over the specified transport. public func disableApplication(_ application: ApplicationType, overTransport transport: DeviceTransport, reboot: Bool = false) async throws { + Logger.management.debug(#function) try await setEnabled(false, application: application, overTransport: transport, reboot: reboot) } /// Enable an application over the specified transport. public func enableApplication(_ application: ApplicationType, overTransport transport: DeviceTransport, reboot: Bool = false) async throws { + Logger.management.debug(#function) try await setEnabled(true, application: application, overTransport: transport, reboot: reboot) } deinit { - print("deinit ManagementSession") + Logger.management.debug("deinit") } } diff --git a/YubiKit/YubiKit/NFCConnection.swift b/YubiKit/YubiKit/NFCConnection.swift index 373dc94..24eee8d 100644 --- a/YubiKit/YubiKit/NFCConnection.swift +++ b/YubiKit/YubiKit/NFCConnection.swift @@ -15,6 +15,7 @@ #if os(iOS) import Foundation import CoreNFC +import OSLog /// A NFC connection to the YubiKey. /// @@ -50,26 +51,28 @@ public final actor NFCConnection: Connection, InternalConnection { } public static func connection() async throws -> Connection { - print("🛜 NFCConnection, connection() on \(Thread.current)") + Logger.nfc.debug(#function) return try await manager.connection(alertMessage: nil) } /// The same function as ``connection()`` but with the option to set a message that will be displayed to the /// user in the iOS NFC alert.' public static func connection(alertMessage message: String?) async throws -> Connection { - print("🛜 NFCConnection, connection(alertMessage: \(message) on \(Thread.current)") + Logger.nfc.debug(#function) return try await manager.connection(alertMessage: message) } /// Before creating a new NFCConnection you can supply a string that will be displayed to the user in the iOS NFC alert. Use this /// to inform the user what the purpose of scanning the YubiKey is. For example: "Scan YubiKey to calculate OATH accounts.". public nonisolated func setAlertMessage(_ message: String) { + Logger.nfc.debug(#function) Task { await tagReaderSession?.session.alertMessage = message } } public func close(error: Error?) async { + Logger.nfc.debug(#function) if let error { await close(result: .failure(error)) } else { @@ -80,6 +83,7 @@ public final actor NFCConnection: Connection, InternalConnection { /// NFCConnection can be closed with an optional message in addition to the ``Connection/close(error:)`` method defined in the Connection protocol. /// The message will be displayed on the iOS NFC alert when it dismisses. public func close(message: String?) async { + Logger.nfc.debug(#function) if let message { await close(result: .success(message)) } else { @@ -88,19 +92,15 @@ public final actor NFCConnection: Connection, InternalConnection { } public func connectionDidClose() async -> Error? { - print("🛜 NFCConnection, await connectionDidClose() called in thread \(Thread.current)") if tag == nil { - print("🛜 NFCConnection, await connectionDidClose() baling out since connection is already closed") return nil } return await withCheckedContinuation { continuation in - print("🛜 NFCConnection, append closingContinuation in thread \(Thread.current)") closingContinuations.append(continuation) } } internal func send(apdu: APDU) async throws -> Response { - print("🛜 NFCConnection, send(apdu: \(apdu))") guard let tag else { throw "No NFC tag" } guard let apdu = apdu.nfcIso7816Apdu else { throw "Malformed APDU data" } let result: (Data, UInt8, UInt8) = try await tag.sendCommand(apdu: apdu) @@ -108,18 +108,16 @@ public final actor NFCConnection: Connection, InternalConnection { } private func close(result: Result?) async { - print("🛜 NFCConnection, close in thread \(Thread.current)") closingHandler?(result) tagReaderSession = nil closingContinuations.forEach { continuation in continuation.resume(returning: result?.error) } - print("🛜 NFCConnection, messaged all continuations, let remove them in thread \(Thread.current)") closingContinuations.removeAll() } deinit { - print("🛜 deinit NFCConnection") + Logger.nfc.debug("deinit") } } @@ -133,9 +131,9 @@ fileprivate actor NFCConnectionManager { func connection(alertMessage message: String?) async throws -> NFCConnection { let task = Task { [connectionTask] in if let connectionTask { - print("🛜 NFCManager, cancel previous task.") + Logger.smartCard.debug("A call to connection() is already awaiting a connection, cancel it before proceeding.") + connectionTask.cancel() } - connectionTask?.cancel() // Cancel any previous request for a connection return try await self._connection(alertMessage: message) } connectionTask = task @@ -144,12 +142,12 @@ fileprivate actor NFCConnectionManager { } onCancel: { task.cancel() } + Logger.nfc.debug("returned: \(String(describing: value))") return value } // Only allow one connect() at a time private func _connection(alertMessage message: String?) async throws -> NFCConnection { - print("🛜 NFCManager, _connection()") if let currentConnection { await currentConnection.close(error: nil) @@ -162,9 +160,7 @@ fileprivate actor NFCConnectionManager { continuation.resume(throwing: CancellationError()) return } - print("🛜 NFCManager, will call nfcWrapper.connection(), Task.isCancelled = \(Task.isCancelled)") nfcWrapper.connection(alertMessage: message) { result in - print("🛜 NFCManager, _connection() got result \(result) pass it to \(String(describing: continuation))") switch result { case .success(let tagReaderSession): let connection = NFCConnection(tagReaderSession: tagReaderSession, closingHandler: { [weak self] result in @@ -174,25 +170,19 @@ fileprivate actor NFCConnectionManager { continuation.resume(returning: connection) case .failure(let error): continuation.resume(throwing: error) - print("🛜 NFCManager, remove \(String(describing: continuation)) after failure") } } } } onCancel: { - print("🛜 NFCManager onCancel: called on \(Thread.current)") nfcWrapper.endSession(result: nil) } } func didDisconnect() async -> Error? { - return await withTaskCancellationHandler { - return await withCheckedContinuation { (continuation: CheckedContinuation) in // try to remove variable definition in the future - nfcWrapper.connectionDidClose { error in - continuation.resume(returning: error) - } + return await withCheckedContinuation { (continuation: CheckedContinuation) in // try to remove variable definition in the future + nfcWrapper.connectionDidClose { error in + continuation.resume(returning: error) } - } onCancel: { - print("NFCManagerActor didDisconnect(), onCancel: called on \(Thread.current)") } } } @@ -217,7 +207,6 @@ fileprivate class NFCTagWrapper: NSObject, NFCTagReaderSessionDelegate { internal func connection(alertMessage message: String?, completion handler: @escaping (Result) -> Void) { queue.async { - print("🛜 NFCWrapper, connection()") self.closingHandler?("Closed by new call to connection()") self.closingHandler = nil self.connectingHandler?(.failure("Cancelled by new call to connection()")) @@ -242,7 +231,6 @@ fileprivate class NFCTagWrapper: NSObject, NFCTagReaderSessionDelegate { internal func connectionDidClose(completion handler: @escaping (Error?) -> Void) { queue.async { - print("🪪 NFCWrapper, connectionDidClose()") assert(self.closingHandler == nil, "Closing completion already registered.") self.closingHandler = handler } @@ -288,15 +276,18 @@ fileprivate class NFCTagWrapper: NSObject, NFCTagReaderSessionDelegate { guard let tag = tags.first else { return } self.tagSession?.connect(to: tag) { error in if let error { + Logger.nfc.debug("Failed connecting to tag with error: \(error)") self.connectingHandler?(.failure(error)) self.connectingHandler = nil return } if case let NFCTag.iso7816(tag) = tag { + Logger.nfc.debug("Connected to tag: \(String(describing: tag))") let session = TagReaderSession(session: self.tagSession!, tag: tag) self.state = .connected(session) self.connectingHandler?(.success(session)) } else { + Logger.nfc.debug("Failed connecting to \(String(describing: tag)) since it's not a iso7816 tag.") self.connectingHandler?(.failure("Not an iso7816 tag!")) } self.connectingHandler = nil diff --git a/YubiKit/YubiKit/OATH/OATHSession+Extensions.swift b/YubiKit/YubiKit/OATH/OATHSession+Extensions.swift index bf4b883..4f21da7 100644 --- a/YubiKit/YubiKit/OATH/OATHSession+Extensions.swift +++ b/YubiKit/YubiKit/OATH/OATHSession+Extensions.swift @@ -291,13 +291,13 @@ extension OATHSession { self.requiresTouch = requiresTouch } - internal let type: CredentialType - internal let algorithm: HashAlgorithm - internal let secret: Data - internal let issuer: String? - internal let name: String - internal let digits: UInt8 - internal let requiresTouch: Bool + public let type: CredentialType + public let algorithm: HashAlgorithm + public let secret: Data + public let issuer: String? + public let name: String + public let digits: UInt8 + public let requiresTouch: Bool } } diff --git a/YubiKit/YubiKit/OATH/OATHSession.swift b/YubiKit/YubiKit/OATH/OATHSession.swift index a06fd1e..47c413e 100644 --- a/YubiKit/YubiKit/OATH/OATHSession.swift +++ b/YubiKit/YubiKit/OATH/OATHSession.swift @@ -16,6 +16,7 @@ import Foundation import CryptoKit import CryptoTokenKit import CommonCrypto +import OSLog fileprivate let tagVersion: TKTLVTag = 0x79 fileprivate let tagName: TKTLVTag = 0x71 @@ -64,7 +65,7 @@ public final actor OATHSession: Session, InternalSession { private var selectResponse: SelectResponse? private init(connection: Connection) async throws { - print("⚡️ init OATHSession") + Logger.oath.debug(#function) self.selectResponse = try await Self.selectApplication(withConnection: connection) self._connection = connection let internalConnection = await internalConnection() @@ -72,7 +73,7 @@ public final actor OATHSession: Session, InternalSession { } private static func selectApplication(withConnection connection: Connection) async throws -> SelectResponse { - let data: Data = try await connection.selectApplication(application: .oath) + let data: Data = try await connection.selectApplication(.oath) guard let result = TKBERTLVRecord.dictionaryOfData(from: data) else { throw OATHSessionError.responseDataNotTLVFormatted } let challenge = result[tagChallenge] @@ -89,6 +90,7 @@ public final actor OATHSession: Session, InternalSession { } public static func session(withConnection connection: Connection) async throws -> OATHSession { + Logger.oath.debug(#function) // Close active session if there is one let internalConnection = connection as! InternalConnection let currentSession = await internalConnection.session() @@ -99,6 +101,7 @@ public final actor OATHSession: Session, InternalSession { } public func end() async { + Logger.oath.debug(#function) self.selectResponse = nil let internalConnection = await internalConnection() await internalConnection?.setSession(nil) @@ -106,8 +109,8 @@ public final actor OATHSession: Session, InternalSession { } public func reset() async throws { + Logger.oath.debug(#function) guard let connection = _connection else { throw SessionError.noConnection } - print("Reset OATH application") let apdu = APDU(cla: 0, ins: 0x04, p1: 0xde, p2: 0xad) try await connection.send(apdu: apdu) selectResponse = try await Self.selectApplication(withConnection: connection) @@ -124,6 +127,7 @@ public final actor OATHSession: Session, InternalSession { /// - Returns: The newly added credential. @discardableResult public func addCredential(template: CredentialTemplate) async throws -> Credential { + Logger.oath.debug(#function) guard let connection = _connection, let selectResponse else { throw SessionError.noConnection } guard let nameData = template.identifier.data(using: .utf8) else { throw OATHSessionError.unexpectedData } let nameTlv = TKBERTLVRecord(tag: 0x71, value: nameData) @@ -152,6 +156,7 @@ public final actor OATHSession: Session, InternalSession { /// Deletes an existing Credential from the YubiKey. /// - Parameter credential: The credential that will be deleted from the YubiKey. public func deleteCredential(_ credential: Credential) async throws { + Logger.oath.debug(#function) guard let connection = _connection else { throw SessionError.noConnection } let deleteTlv = TKBERTLVRecord(tag: 0x71, value: credential.id) let apdu = APDU(cla: 0, ins: 0x02, p1: 0, p2: 0, command: deleteTlv.data) @@ -161,6 +166,7 @@ public final actor OATHSession: Session, InternalSession { /// List credentials on YubiKey. /// - Returns: An array of Credentials. public func listCredentials() async throws -> [Credential] { + Logger.oath.debug(#function) guard let connection = _connection, let selectResponse else { throw SessionError.noConnection } let apdu = APDU(cla: 0, ins: 0xa1, p1: 0, p2: 0) let data = try await connection.send(apdu: apdu) @@ -191,6 +197,7 @@ public final actor OATHSession: Session, InternalSession { /// - timestamp: The timestamp which is used as start point for TOTP, this is ignored for HOTP. /// - Returns: Calculated code. public func calculateCode(credential: Credential, timestamp: Date = Date()) async throws -> Code { + Logger.oath.debug(#function) guard let connection = _connection, let selectResponse else { throw SessionError.noConnection } guard credential.deviceId == selectResponse.deviceId else { throw OATHSessionError.credentialNotPresentOnCurrentYubiKey } @@ -228,6 +235,7 @@ public final actor OATHSession: Session, InternalSession { /// - challenge: The input to the HMAC operation. /// - Returns: The calculated response. public func calculateResponse(credentialId: Data, challenge: Data) async throws -> Data { + Logger.oath.debug(#function) guard let connection = _connection else { throw SessionError.noConnection } var data = Data() data.append(TKBERTLVRecord(tag: tagName, value: credentialId).data) @@ -246,7 +254,7 @@ public final actor OATHSession: Session, InternalSession { /// - Parameter timestamp: The timestamp which is used as start point for TOTP, this is ignored for HOTP. /// - Returns: An array of tuples containing a ``Credential`` and an optional ``Code``. public func calculateCodes(timestamp: Date = Date()) async throws -> [(Credential, Code?)] { - print("Start OATH calculateCodes") + Logger.oath.debug(#function) let time = timestamp.timeIntervalSince1970 let challenge = UInt64(time / 30) let bigChallenge = CFSwapInt64HostToBig(challenge) @@ -290,6 +298,7 @@ public final actor OATHSession: Session, InternalSession { /// require the application to be unlocked via one of the unlock functions. Also see ``setAccessKey(_:)``. /// - Parameter password: The user-supplied password to set. public func setPassword(_ password: String) async throws { + Logger.oath.debug(#function) let derivedKey = try deriveAccessKey(from: password) try await self.setAccessKey(derivedKey) } @@ -297,6 +306,7 @@ public final actor OATHSession: Session, InternalSession { /// Unlock with password. /// - Parameter password: The user-supplied password used to unlock the application. public func unlockWithPassword(_ password: String) async throws { + Logger.oath.debug(#function) let derivedKey = try deriveAccessKey(from: password) try await self.unlockWithAccessKey(derivedKey) } @@ -310,6 +320,7 @@ public final actor OATHSession: Session, InternalSession { /// sets the raw 16 byte key. /// - Parameter accessKey: The shared secret key used to unlock access to the application. public func setAccessKey(_ accessKey: Data) async throws { + Logger.oath.debug(#function) guard let connection = _connection else { throw SessionError.noConnection } let header = CredentialType.TOTP().code | HashAlgorithm.SHA1.rawValue var data = Data([header]) @@ -333,6 +344,7 @@ public final actor OATHSession: Session, InternalSession { /// See the [YKOATH protocol specification](https://developers.yubico.com/OATH/) for further details. /// - Parameter accessKey: The shared access key. public func unlockWithAccessKey(_ accessKey: Data) async throws { + Logger.oath.debug(#function) guard let connection = _connection, let responseChallenge = self.selectResponse?.challenge else { throw SessionError.noConnection } let reponseTlv = TKBERTLVRecord(tag: tagResponse, value: responseChallenge.hmacSha1(usingKey: accessKey)) let challenge = Data.random(length: 8) @@ -364,7 +376,7 @@ public final actor OATHSession: Session, InternalSession { } deinit { - print("deinit OATHSession") + Logger.oath.debug(#function) } } diff --git a/YubiKit/YubiKit/SmartCardConnection.swift b/YubiKit/YubiKit/SmartCardConnection.swift index 9d6ed79..a6275fa 100644 --- a/YubiKit/YubiKit/SmartCardConnection.swift +++ b/YubiKit/YubiKit/SmartCardConnection.swift @@ -14,6 +14,7 @@ import Foundation import CryptoTokenKit +import OSLog /// A connection to the YubiKey utilizing the USB-C port and the TKSmartCard implementation from /// the CryptoTokenKit framework. @@ -23,7 +24,7 @@ public final actor SmartCardConnection: Connection, InternalConnection { private static let manager = SmartCardManager() public static func connection() async throws -> Connection { - print("🪪 SmartCardConnection, connection() on \(Thread.current)") + Logger.smartCard.debug(#function) return try await manager.connection() } @@ -47,14 +48,12 @@ public final actor SmartCardConnection: Connection, InternalConnection { } public func close(error: Error?) async { - print("🪪 SmartCardConnection, close in thread \(Thread.current)") + Logger.smartCard.debug(#function) closingHandler?() closingContinuations.forEach { continuation in continuation.resume(returning: error) } - print("🪪 SmartCardConnection, messaged all continuations, let remove them in thread \(Thread.current)") closingContinuations.removeAll() - print("🪪 SmartCardConnection, endSession() for \(String(describing: smartCard))") smartCard = nil } @@ -68,26 +67,22 @@ public final actor SmartCardConnection: Connection, InternalConnection { // Wait for the connection to close public func connectionDidClose() async -> Error? { - print("🪪 SmartCardConnection, await connectionDidClose() called in thread \(Thread.current)") if smartCard == nil { - print("🪪 SmartCardConnection, await connectionDidClose() baling out since connection is already closed") return nil } return await withCheckedContinuation { continuation in - print("🪪 SmartCardConnection, append closingContinuation in thread \(Thread.current)") closingContinuations.append(continuation) } } internal func send(apdu: APDU) async throws -> Response { - print("🪪 SmartCardConnection, send(apdu: \(apdu))") guard let smartCard else { throw "No SmartCard connection" } let data = try await smartCard.transmit(apdu.data) return Response(rawData: data) } deinit { - print("🪪 deinit SmartCardConnection") + Logger.smartCard.debug(#function) } } @@ -101,9 +96,9 @@ fileprivate actor SmartCardManager { func connection() async throws -> SmartCardConnection { let task = Task { [connectionTask] in if let connectionTask { - print("🪪 SmartCardManager, cancel previous task.") + Logger.smartCard.debug("A call to connection() is already awaiting a connection, cancel it before proceeding.") + connectionTask.cancel() } - connectionTask?.cancel() // Cancel any previous request for a connection return try await self._connection() } connectionTask = task @@ -112,12 +107,12 @@ fileprivate actor SmartCardManager { } onCancel: { task.cancel() } + Logger.smartCard.debug("returned: \(String(describing: value))") return value } // Only allow one connect() at a time private func _connection() async throws -> SmartCardConnection { - print("🪪 SmartCardManager, _connection()") if let currentConnection { await currentConnection.close(error: nil) @@ -130,9 +125,7 @@ fileprivate actor SmartCardManager { continuation.resume(throwing: CancellationError()) return } - print("🪪 SmartCardManager, will call manager.connectSmartCard() Task.isCancelled = \(Task.isCancelled)") smartCardWrapper.connection { result in - print("🪪 SmartCardManager, _connection() got result \(result) pass it to \(String(describing: continuation))") switch result { case .success(let smartCard): let connection = SmartCardConnection(smartCard: smartCard, closingHandler: { [weak self] in @@ -147,27 +140,19 @@ fileprivate actor SmartCardManager { } case .failure(let error): continuation.resume(throwing: error) - print("🪪 SmartCardManager, remove \(String(describing: continuation)) after failure") } } } } onCancel: { - print("🪪 SmartCardManager onCancel: called on \(Thread.current)") smartCardWrapper.stop() } } func didDisconnect() async -> Error? { - print("🪪 SmartCardManager didDisconnect(): called on \(Thread.current)") - - return await withTaskCancellationHandler { - return await withCheckedContinuation { (continuation: CheckedContinuation) in // try to remove variable definition in the future - smartCardWrapper.connectionDidClose { error in - continuation.resume(returning: error) - } + return await withCheckedContinuation { (continuation: CheckedContinuation) in // try to remove variable definition in the future + smartCardWrapper.connectionDidClose { error in + continuation.resume(returning: error) } - } onCancel: { - print("SmartCardManagerActor didDisconnect(), onCancel: called on \(Thread.current)") } } } @@ -191,7 +176,6 @@ fileprivate class TKSmartCardWrapper { internal func connection(completion handler: @escaping (Result) -> Void) { queue.async { - print("🪪 TKSmartCardWrapper, connection()") // Signal closure and cancel previous connection handlers // Swap out old handlers to the new ones self.closingHandler?("Closed by new call to connection()") @@ -202,19 +186,15 @@ fileprivate class TKSmartCardWrapper { // If we're currently not monitoring or initating a session switch self.state { case .ready: - print("🪪 TKSmartCardWrapper, state == .ready ") break case .connected(let smartCard): smartCard.endSession() - print("🪪 TKSmartCardWrapper, state == .connected. Call endSession()") case .monitoring, .initatingSession: - print("🪪 TKSmartCardWrapper, manager is already monitoring or initiating a new session. Any results will be sent to the new closingHandler.") return } assert(self.manager != nil, "🪪 No default TKSmartCardSlotManager, check entitlements for com.apple.smartcard.") if let slotName = self.manager?.slotNames.first, let slot = self.manager?.slotNamed(slotName), slot.state == .validCard { - print("🪪 TKSmartCardWrapper, a smartcard was already connected, start session with slot \(slot)") self.state = .monitoring self.beginSession(withSlot: slot) } else { @@ -226,7 +206,6 @@ fileprivate class TKSmartCardWrapper { internal func connectionDidClose(completion handler: @escaping (Error?) -> Void) { queue.async { - print("🪪 TKSmartCardWrapper, connectionDidClose()") assert(self.closingHandler == nil, "Closing completion already registered.") self.closingHandler = handler } @@ -234,15 +213,12 @@ fileprivate class TKSmartCardWrapper { private func observeManagerChanges() { self.queue.async { - print("🪪 TKSmartCardWrapper, Start observing changes in slot manager") self.state = .monitoring self.managerObservation = self.manager?.observe(\.slotNames) { [weak self] manager, value in guard let self else { return } self.queue.async { - print("🪪 TKSmartCardWrapper, Got changes in slotNames: \(manager.slotNames)") // If slotNames is empty the TKSmartCard did disconnect if manager.slotNames.isEmpty { - print("🪪 TKSmartCardWrapper, TKSmartCardSlot removed") self.closingHandler?(nil) self.closingHandler = nil self.state = .ready @@ -261,10 +237,8 @@ fileprivate class TKSmartCardWrapper { if slot.state == .validCard { self.beginSession(withSlot: slot) } else { - print("🪪 TKSmartCardWrapper, Start observing changes to the current TKSmartCardSlot") self.slotObservation = slot.observe(\TKSmartCardSlot.state) { [weak self] slot, value in self?.queue.async { - print("🪪 TKSmartCardWrapper, TKSmartCardSlot.state changed to: \(slot.state)") if slot.state == .validCard { self?.beginSession(withSlot: slot) } @@ -282,10 +256,8 @@ fileprivate class TKSmartCardWrapper { self.managerObservation = self.manager?.observe(\.slotNames) { [weak self] manager, value in guard let self else { return } self.queue.async { - print("🪪 TKSmartCardWrapper, Got changes in slotNames: \(value)") // If slotNames is empty the TKSmartCard did disconnect if manager.slotNames.isEmpty { - print("🪪 TKSmartCardWrapper, TKSmartCardSlot removed") self.closingHandler?(nil) // should we signal an error? self.closingHandler = nil self.state = .ready @@ -299,11 +271,10 @@ fileprivate class TKSmartCardWrapper { queue.async { guard self.state != .initatingSession else { return } self.state = .initatingSession - print("🪪 TKSmartCardWrapper, beginSession \(slot), state = \(self.state)") + Logger.smartCard.debug("TKSmartCardWrapper, initiatingSession for slot: \(slot)") if let smartCard = slot.makeSmartCard() { smartCard.beginSession { [weak self] success, error in self?.queue.async { - print("🪪 TKSmartCardWrapper, beginSession returned \(success), error = \(String(describing: error))") guard self?.state == .initatingSession else { // If state is not .initiatingSession stop() has been called. smartCard.endSession() return @@ -327,7 +298,6 @@ fileprivate class TKSmartCardWrapper { // Stop monitoring and return to ready state and if connected end the TKSmartCard session. internal func stop() { queue.async { - print("🪪 TKSmartCardWrapper, stop() with state = \(self.state) in \(Thread.current)") switch self.state { case .ready: break; diff --git a/YubiKit/YubiKit/Utilities/Base32.swift b/YubiKit/YubiKit/Utilities/Base32.swift index 89f7dc6..748c77a 100644 --- a/YubiKit/YubiKit/Utilities/Base32.swift +++ b/YubiKit/YubiKit/Utilities/Base32.swift @@ -298,7 +298,6 @@ private func base32decode(_ string: String, _ table: [UInt8]) -> [UInt8]? { let pos = string.unicodeScalars.distance(from: string.unicodeScalars.startIndex, to: index) // if pos points padding "=", it's valid. if pos != length - leastPaddingLength { - print("string contains some invalid characters.") return nil } } @@ -313,7 +312,6 @@ private func base32decode(_ string: String, _ table: [UInt8]) -> [UInt8]? { case 5: additionalBytes = 3 case 7: additionalBytes = 4 default: - print("string length is invalid.") return nil } diff --git a/YubiKit/YubiKit/Utilities/Connection+Extensions.swift b/YubiKit/YubiKit/Utilities/Connection+Extensions.swift index 0a22ae5..4828160 100644 --- a/YubiKit/YubiKit/Utilities/Connection+Extensions.swift +++ b/YubiKit/YubiKit/Utilities/Connection+Extensions.swift @@ -13,6 +13,7 @@ // limitations under the License. import Foundation +import OSLog public struct ResponseError: Error { let statusCode: Response.StatusCode @@ -37,10 +38,12 @@ enum Application { extension Connection { public func send(apdu: APDU) async throws -> Data { + Logger.connection.debug("send(): \(apdu)") return try await sendRecursive(apdu: apdu) } - func selectApplication(application: Application) async throws -> Data { + func selectApplication(_ application: Application) async throws -> Data { + Logger.connection.debug("selectApplication(\(String(describing: application)))") do { return try await send(apdu: application.selectApplicationAPDU) } catch { @@ -55,6 +58,10 @@ extension Connection { } private func sendRecursive(apdu: APDU, data: Data = Data(), readMoreData: Bool = false) async throws -> Data { + if data.count > 0 { + Logger.connection.debug("sendRecursive() accumulated data: \(data))") + } + let response: Response let ins: UInt8 @@ -75,6 +82,7 @@ extension Connection { } guard response.statusCode == .ok || response.statusCode == .moreData else { + Logger.connection.error("send() failed with statusCode: \(response.statusCode.rawValue.data.hexEncodedString)") throw ResponseError(statusCode: response.statusCode) } @@ -82,6 +90,7 @@ extension Connection { if response.statusCode == .moreData { return try await sendRecursive(apdu: apdu, data: newData, readMoreData: true) } else { + Logger.connection.debug("send() response: \(newData.hexEncodedString)") return newData } } diff --git a/YubiKit/YubiKit/Utilities/Logger+Extensions.swift b/YubiKit/YubiKit/Utilities/Logger+Extensions.swift new file mode 100644 index 0000000..b504349 --- /dev/null +++ b/YubiKit/YubiKit/Utilities/Logger+Extensions.swift @@ -0,0 +1,28 @@ +// Copyright Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import OSLog + +extension Logger { + private static var subsystem = "com.yubico.YubiKit" + + static let connection = Logger(subsystem: subsystem, category: "Connection") + + static let nfc = Logger(subsystem: subsystem, category: "NFC") + static let lightning = Logger(subsystem: subsystem, category: "Lightning") + static let smartCard = Logger(subsystem: subsystem, category: "SmartCard") + + static let oath = Logger(subsystem: subsystem, category: "OATH") + static let management = Logger(subsystem: subsystem, category: "Management") +} diff --git a/YubiKit/YubiKit/Utilities/Stream+Extensions.swift b/YubiKit/YubiKit/Utilities/Stream+Extensions.swift index 047f420..6de41bc 100644 --- a/YubiKit/YubiKit/Utilities/Stream+Extensions.swift +++ b/YubiKit/YubiKit/Utilities/Stream+Extensions.swift @@ -29,11 +29,8 @@ fileprivate struct YubiKeyConstants { extension OutputStream { internal func writeToYubiKey(data: Data) throws { - print("⚡️ Stream+Extensions about to write \(data.hexEncodedString) to \(self)") - print("⚡️ Stream+Extensions, OutputStream is open: \(self.streamStatus == .open)") var timer = 0.0 while !self.hasSpaceAvailable { - print("⚡️ waiting for hasSpaceAvailable") Thread.sleep(forTimeInterval: YubiKeyConstants.probeTime) timer += YubiKeyConstants.probeTime if timer > YubiKeyConstants.timeout { throw StreamError.timeout } @@ -43,10 +40,8 @@ extension OutputStream { while !remaining.isEmpty { let bytesWritten = remaining.withUnsafeBytes { buffer in let length = min(remaining.count, YubiKeyConstants.bufferSize) - print("⚡️ Stream+Extensions, write chunk \(length): \(remaining.subdata(in: 0.. 0 else { throw self.streamError ?? StreamError.writeError } remaining = remaining.dropFirst(bytesWritten) if !remaining.isEmpty { Thread.sleep(forTimeInterval: YubiKeyConstants.probeTime) } @@ -74,11 +69,9 @@ extension InputStream { } while self.hasBytesAvailable { let read = self.read(buffer, maxLength: YubiKeyConstants.bufferSize) - print("⚡️ Stream+Extensions, read \(read) bytes") guard read > 0 else { throw self.streamError ?? StreamError.readError } data.append(buffer, count: read) - print("⚡️ Stream+Extensions, data: \(data.hexEncodedString)") if self.hasBytesAvailable { Thread.sleep(forTimeInterval: YubiKeyConstants.probeTime) } timer += YubiKeyConstants.probeTime