diff --git a/FullStackTests/Tests/ManagementFullStackTests.swift b/FullStackTests/Tests/ManagementFullStackTests.swift index 31de18b..341466f 100644 --- a/FullStackTests/Tests/ManagementFullStackTests.swift +++ b/FullStackTests/Tests/ManagementFullStackTests.swift @@ -32,11 +32,7 @@ class ManagementFullStackTests: XCTestCase { func testGetDeviceInfo() throws { runManagementTest { connection, session, _ in let info = try await session.getDeviceInfo() - print(info) - print("PIV enabled over usb: \(info.config.isApplicationEnabled(.piv, overTransport: .usb))") - print("PIV enabled over nfc: \(info.config.isApplicationEnabled(.piv, overTransport: .nfc))") - print("PIV supported over usb: \(info.isApplicationSupported(.piv, overTransport: .usb))") - print("PIV supported over nfc: \(info.isApplicationSupported(.piv, overTransport: .nfc))") + print("✅ Successfully got device info:\n\(info)") #if os(iOS) await connection.nfcConnection?.close(message: "Test successful!") #endif @@ -60,67 +56,97 @@ class ManagementFullStackTests: XCTestCase { func testDisableAndEnableConfigOATHandPIVoverUSB() throws { runManagementTest { connection, session, transport in let deviceInfo = try await session.getDeviceInfo() - guard let disableConfig = deviceInfo.config.deviceConfig(enabling: false, application: .oath, overTransport: .usb)?.deviceConfig(enabling: false, application: .piv, overTransport: .usb) else { XCTFail(); return } + guard let disableConfig = deviceInfo.config.deviceConfig(enabling: false, application: .OATH, overTransport: .usb)?.deviceConfig(enabling: false, application: .PIV, overTransport: .usb) else { XCTFail(); return } try await session.updateDeviceConfig(disableConfig, reboot: false) let disabledInfo = try await session.getDeviceInfo() - XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.oath, overTransport: .usb)) - XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.piv, overTransport: .usb)) + XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.OATH, overTransport: .usb)) + XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.PIV, overTransport: .usb)) let oathSession = try? await OATHSession.session(withConnection: connection) if transport == .usb { XCTAssert(oathSession == nil) } let managementSession = try await ManagementSession.session(withConnection: connection) - guard let enableConfig = deviceInfo.config.deviceConfig(enabling: true, application: .oath, overTransport: .usb)?.deviceConfig(enabling: true, application: .piv, overTransport: .usb) else { XCTFail(); return } + guard let enableConfig = deviceInfo.config.deviceConfig(enabling: true, application: .OATH, overTransport: .usb)?.deviceConfig(enabling: true, application: .PIV, overTransport: .usb) else { XCTFail(); return } try await managementSession.updateDeviceConfig(enableConfig, reboot: false) + let enabledInfo = try await managementSession.getDeviceInfo() + XCTAssert(enabledInfo.config.isApplicationEnabled(.OATH, overTransport: .usb)) + XCTAssert(enabledInfo.config.isApplicationEnabled(.PIV, overTransport: .usb)) #if os(iOS) await connection.nfcConnection?.close(message: "Test successful!") #endif - let enabledInfo = try await managementSession.getDeviceInfo() - XCTAssert(enabledInfo.config.isApplicationEnabled(.oath, overTransport: .usb)) - XCTAssert(enabledInfo.config.isApplicationEnabled(.piv, overTransport: .usb)) } } func testDisableAndEnableConfigOATHandPIVoverNFC() throws { runManagementTest { connection, session, transport in let deviceInfo = try await session.getDeviceInfo() - guard let disableConfig = deviceInfo.config.deviceConfig(enabling: false, application: .oath, overTransport: .nfc)?.deviceConfig(enabling: false, application: .piv, overTransport: .nfc) else { XCTFail(); return } + guard deviceInfo.hasTransport(.nfc) else { print("⚠️ No NFC YubiKey. Skip test."); return } + guard let disableConfig = deviceInfo.config.deviceConfig(enabling: false, application: .OATH, overTransport: .nfc)?.deviceConfig(enabling: false, application: .PIV, overTransport: .nfc) else { XCTFail(); return } try await session.updateDeviceConfig(disableConfig, reboot: false) let disabledInfo = try await session.getDeviceInfo() - XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.oath, overTransport: .nfc)) - XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.piv, overTransport: .nfc)) + XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.OATH, overTransport: .nfc)) + XCTAssertFalse(disabledInfo.config.isApplicationEnabled(.PIV, overTransport: .nfc)) let oathSession = try? await OATHSession.session(withConnection: connection) if transport == .nfc { XCTAssert(oathSession == nil) } let managementSession = try await ManagementSession.session(withConnection: connection) - guard let enableConfig = deviceInfo.config.deviceConfig(enabling: true, application: .oath, overTransport: .nfc)?.deviceConfig(enabling: true, application: .piv, overTransport: .nfc) else { XCTFail(); return } + guard let enableConfig = deviceInfo.config.deviceConfig(enabling: true, application: .OATH, overTransport: .nfc)?.deviceConfig(enabling: true, application: .PIV, overTransport: .nfc) else { XCTFail(); return } try await managementSession.updateDeviceConfig(enableConfig, reboot: false) + let enabledInfo = try await managementSession.getDeviceInfo() + XCTAssert(enabledInfo.config.isApplicationEnabled(.OATH, overTransport: .nfc)) + XCTAssert(enabledInfo.config.isApplicationEnabled(.PIV, overTransport: .nfc)) #if os(iOS) await connection.nfcConnection?.close(message: "Test successful!") #endif - let enabledInfo = try await managementSession.getDeviceInfo() - XCTAssert(enabledInfo.config.isApplicationEnabled(.oath, overTransport: .nfc)) - XCTAssert(enabledInfo.config.isApplicationEnabled(.piv, overTransport: .nfc)) } } func testDisableAndEnableWithHelperOATH() throws { runManagementTest { connection, session, transport in - try await session.setEnabled(false, application: .oath, overTransport: transport) + try await session.setEnabled(false, application: .OATH, overTransport: transport) var info = try await session.getDeviceInfo() - XCTAssertFalse(info.config.isApplicationEnabled(.oath, overTransport: transport)) + XCTAssertFalse(info.config.isApplicationEnabled(.OATH, overTransport: transport)) let oathSession = try? await OATHSession.session(withConnection: connection) XCTAssert(oathSession == nil) let managementSession = try await ManagementSession.session(withConnection: connection) - try await managementSession.setEnabled(true, application: .oath, overTransport: transport) + try await managementSession.setEnabled(true, application: .OATH, overTransport: transport) info = try await managementSession.getDeviceInfo() + XCTAssert(info.config.isApplicationEnabled(.OATH, overTransport: transport)) #if os(iOS) await connection.nfcConnection?.close(message: "Test successful!") #endif - XCTAssert(info.config.isApplicationEnabled(.oath, overTransport: transport)) } } + + // Tests are run in alphabetical order. If running the tests via NFC this will disable NFC for all the following tests making them fail, hence the Z in the name. + func testZNFCRestricted() throws { + runManagementTest { connection, session, transport in + guard session.version >= Version(withString: "5.7.0")! else { + print("⚠️ YubiKey without support for NFC restricted. Skip test.") + return + } + let info = try await session.getDeviceInfo() + let newConfig = info.config.deviceConfig(nfcRestricted: true) + try await session.updateDeviceConfig(newConfig, reboot: false) + let updatedInfo = try await session.getDeviceInfo() + XCTAssertEqual(updatedInfo.config.isNFCRestricted, true) + if transport == .nfc { + #if os(iOS) + await connection.nfcConnection?.close(message: "NFC is now restriced until this YubiKey has been inserted into a USB port.") + do { + let newConnection = try await ConnectionHelper.anyConnection() + _ = try await ManagementSession.session(withConnection: newConnection) + XCTFail("Got connection even if NFC restriced was turned on!") + } catch { + print("✅ Failed creating ManagementSession as expected.") + } + #endif + } + print("✅ NFC is now restriced until this YubiKey has been inserted into a USB port.") + print("⚠️ Note that no more NFC testing will be possible until NFC restriction has been disabled for this key!") + } + } } extension XCTestCase { diff --git a/YubiKit/YubiKit.xcodeproj/project.pbxproj b/YubiKit/YubiKit.xcodeproj/project.pbxproj index 9197f60..34bad01 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, ); }; }; + B40064302BF728D600CD2FAF /* Capability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B400642F2BF728D600CD2FAF /* Capability.swift */; }; 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 */; }; @@ -64,6 +65,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; }; + B400642F2BF728D600CD2FAF /* Capability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capability.swift; sourceTree = ""; }; 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 = ""; }; @@ -208,6 +210,7 @@ B40528322987C31E00FC33AB /* DeviceInfo.swift */, B405283629894E7600FC33AB /* DeviceConfig.swift */, B49F90C32B9F30A400C10F0B /* ManagementFeature.swift */, + B400642F2BF728D600CD2FAF /* Capability.swift */, ); path = Management; sourceTree = ""; @@ -389,6 +392,7 @@ B41B61842743FC2E004C37BF /* Connection.swift in Sources */, B4BE3AAD292E1C8600CC30CB /* OATHSession.swift in Sources */, B4BE3ABA292E24C700CC30CB /* Base32.swift in Sources */, + B40064302BF728D600CD2FAF /* Capability.swift in Sources */, B4F068B92861CFC300555AFE /* SmartCardConnection.swift in Sources */, B4F937622B51A44E0007D394 /* PIVSession.swift in Sources */, B40CBD6029090C49007D7D23 /* Version.swift in Sources */, diff --git a/YubiKit/YubiKit/Management/Capability.swift b/YubiKit/YubiKit/Management/Capability.swift new file mode 100644 index 0000000..132c37c --- /dev/null +++ b/YubiKit/YubiKit/Management/Capability.swift @@ -0,0 +1,51 @@ +// +// Capability.swift +// YubiKit +// +// Created by Jens Utbult on 2024-05-17. +// + +import Foundation + +/// Identifies a feature (typically an application) on a YubiKey which may or may not be supported, and which can be enabled or disabled. +public enum Capability: UInt { + /// Identifies the YubiOTP application. + case OTP = 0x0001 + /// Identifies the U2F (CTAP1) portion of the FIDO application. + case U2F = 0x0002 + /// Identifies the OpenPGP application, implementing the OpenPGP Card protocol. + case OPENPGP = 0x0008 + /// Identifies the PIV application, implementing the PIV protocol. + case PIV = 0x0010 + /// Identifies the OATH application, implementing the YKOATH protocol. + case OATH = 0x0020 + /// Identifies the HSMAUTH application. + case HSMAUTH = 0x0100 + /// Identifies the FIDO2 (CTAP2) portion of the FIDO application. + case FIDO2 = 0x0200 + + var bit: UInt { self.rawValue } +} + + +extension Capability { + internal static func translateMaskFrom(fipsMask: UInt) -> UInt { + var capabilities: UInt = 0; + if fipsMask & 0b00000001 != 0 { + capabilities |= Capability.FIDO2.bit; + } + if fipsMask & 0b00000010 != 0 { + capabilities |= Capability.PIV.bit; + } + if fipsMask & 0b00000100 != 0 { + capabilities |= Capability.OPENPGP.bit; + } + if fipsMask & 0b00001000 != 0 { + capabilities |= Capability.OATH.bit; + } + if fipsMask & 0b00010000 != 0 { + capabilities |= Capability.HSMAUTH.bit; + } + return capabilities; + } +} diff --git a/YubiKit/YubiKit/Management/DeviceConfig.swift b/YubiKit/YubiKit/Management/DeviceConfig.swift index 6f67a4b..1b7fcff 100644 --- a/YubiKit/YubiKit/Management/DeviceConfig.swift +++ b/YubiKit/YubiKit/Management/DeviceConfig.swift @@ -22,22 +22,66 @@ public struct DeviceConfig { public let challengeResponseTimeout: TimeInterval? public let deviceFlags: UInt8? public let enabledCapabilities: [DeviceTransport: UInt] + public let isNFCRestricted: Bool? internal let tagUSBEnabled: TKTLVTag = 0x03 internal let tagAutoEjectTimeout: TKTLVTag = 0x06 internal let tagChallengeResponseTimeout: TKTLVTag = 0x07 internal let tagDeviceFlags: TKTLVTag = 0x08 + internal let tagNFCSupported: TKTLVTag = 0x0d internal let tagNFCEnabled: TKTLVTag = 0x0e internal let tagConfigurationLock: TKTLVTag = 0x0a internal let tagUnlock: TKTLVTag = 0x0b internal let tagReboot: TKTLVTag = 0x0c + internal let tagNFCRestricted: TKTLVTag = 0x17 - public func isApplicationEnabled(_ application: ApplicationType, overTransport transport: DeviceTransport) -> Bool { + internal init(withTlvs tlvs: [TKTLVTag : Data], version: Version) throws { + if let timeout = tlvs[tagAutoEjectTimeout]?.integer { + self.autoEjectTimeout = TimeInterval(timeout) + } else { + self.autoEjectTimeout = 0 + } + + if let timeout = tlvs[tagChallengeResponseTimeout]?.integer { + self.challengeResponseTimeout = TimeInterval(timeout) + } else { + self.challengeResponseTimeout = 0 + } + + self.deviceFlags = tlvs[tagDeviceFlags]?.uint8 + + var enabledCapabilities = [DeviceTransport: UInt]() + if tlvs[tagUSBEnabled] != nil && version.major != 4 { + // YK4 reports this incorrectly, instead use supportedCapabilities and USB mode. + enabledCapabilities[DeviceTransport.usb] = tlvs[tagUSBEnabled]?.integer ?? 0 + } + + if tlvs[tagNFCSupported] != nil { + enabledCapabilities[DeviceTransport.nfc] = tlvs[tagNFCEnabled]?.integer ?? 0 + } + self.enabledCapabilities = enabledCapabilities + if let isNFCRestricted = tlvs[tagNFCRestricted]?.integer { + self.isNFCRestricted = isNFCRestricted == 1 + } else { + self.isNFCRestricted = nil + } + } + + public func isApplicationEnabled(_ application: Capability, overTransport transport: DeviceTransport) -> Bool { guard let mask = enabledCapabilities[transport] else { return false } return (mask & application.rawValue) == application.rawValue } - public func deviceConfig(enabling: Bool, application: ApplicationType, overTransport transport: DeviceTransport) -> DeviceConfig? { + + private init(autoEjectTimeout: TimeInterval?, challengeResponseTimeout: TimeInterval?, deviceFlags: UInt8?, enabledCapabilities: [DeviceTransport : UInt], isNFCRestricted: Bool?) { + self.autoEjectTimeout = autoEjectTimeout + self.challengeResponseTimeout = challengeResponseTimeout + self.deviceFlags = deviceFlags + self.enabledCapabilities = enabledCapabilities + self.isNFCRestricted = isNFCRestricted + } + + public func deviceConfig(enabling: Bool, application: Capability, overTransport transport: DeviceTransport) -> DeviceConfig? { guard let oldMask = enabledCapabilities[transport] else { return nil } let newMask = enabling ? oldMask | application.rawValue : oldMask & ~application.rawValue var newEnabledCapabilities = enabledCapabilities @@ -46,11 +90,16 @@ public struct DeviceConfig { return DeviceConfig(autoEjectTimeout: autoEjectTimeout, challengeResponseTimeout: challengeResponseTimeout, deviceFlags: deviceFlags, - enabledCapabilities: newEnabledCapabilities) + enabledCapabilities: newEnabledCapabilities, + isNFCRestricted: self.isNFCRestricted) } public func deviceConfig(autoEjectTimeout: TimeInterval, challengeResponseTimeout: TimeInterval) -> DeviceConfig { - return Self.init(autoEjectTimeout: autoEjectTimeout, challengeResponseTimeout: challengeResponseTimeout, deviceFlags: self.deviceFlags, enabledCapabilities: self.enabledCapabilities) + return Self.init(autoEjectTimeout: autoEjectTimeout, challengeResponseTimeout: challengeResponseTimeout, deviceFlags: self.deviceFlags, enabledCapabilities: self.enabledCapabilities, isNFCRestricted: self.isNFCRestricted) + } + + public func deviceConfig(nfcRestricted: Bool) -> DeviceConfig { + return Self.init(autoEjectTimeout: self.autoEjectTimeout, challengeResponseTimeout: self.challengeResponseTimeout, deviceFlags: self.deviceFlags, enabledCapabilities: self.enabledCapabilities, isNFCRestricted: nfcRestricted) } internal func data(reboot: Bool, lockCode: Data?, newLockCode: Data?) throws -> Data { @@ -80,6 +129,11 @@ public struct DeviceConfig { if let newLockCode { data.append(TKBERTLVRecord(tag: tagConfigurationLock, value: newLockCode).data) } + + if let isNFCRestricted, isNFCRestricted { + data.append(TKBERTLVRecord(tag: tagNFCRestricted, value: UInt8(0x01).data).data) + } + guard data.count <= 0xff else { throw ManagementSessionError.configTooLarge } return UInt8(data.count).data + data diff --git a/YubiKit/YubiKit/Management/DeviceInfo.swift b/YubiKit/YubiKit/Management/DeviceInfo.swift index 0317c32..616b301 100644 --- a/YubiKit/YubiKit/Management/DeviceInfo.swift +++ b/YubiKit/YubiKit/Management/DeviceInfo.swift @@ -20,22 +20,6 @@ public enum DeviceTransport { case usb, nfc } -/// Identifies a feature (typically an application) on a YubiKey which may or may not be supported, and which can be enabled or disabled. -public enum ApplicationType: UInt { - /// Identifies the YubiOTP application. - case otp = 0x01 - /// Identifies the U2F (CTAP1) portion of the FIDO application. - case u2f = 0x02 - /// Identifies the OpenPGP application, implementing the OpenPGP Card protocol. - case opgp = 0x08 - /// Identifies the PIV application, implementing the PIV protocol. - case piv = 0x10 - /// Identifies the OATH application, implementing the YKOATH protocol. - case oath = 0x20 - /// Identifies the FIDO2 (CTAP2) portion of the FIDO application. - case ctap2 = 0x0200 -} - /// The physical form factor of a YubiKey. public enum FormFactor: UInt8 { /// Used when information about the YubiKey's form factor isn't available. @@ -57,7 +41,26 @@ public enum FormFactor: UInt8 { } /// Contains metadata, including Device Configuration, of a YubiKey. -public struct DeviceInfo { +public struct DeviceInfo: CustomStringConvertible { + + public var description: String { +""" +YubiKey \(formFactor) \(version) (#\(serialNumber)) +Supported capabilities: \(supportedCapabilities) +Enabled capabilities: \(config.enabledCapabilities) +isConfigLocked: \(isConfigLocked) +isFips: \(isFips) +isSky: \(isSky) +partNumber: \(partNumber) +isFipsCapable: \(isFIPSCapable) +isFipsApproved: \(isFIPSApproved) +pinComplexity: \(pinComplexity) +resetBlocked: \(isResetBlocked) +fpsVersion: \(String(describing: fpsVersion)) +stmVersion: \(String(describing: stmVersion)) +""" + } + /// Returns the serial number of the YubiKey, if available. /// /// The serial number can be read if the YubiKey has a serial number, and one of the YubiOTP slots @@ -67,6 +70,17 @@ public struct DeviceInfo { public let version: Version /// Returns the form factor of the YubiKey. public let formFactor: FormFactor + + public let partNumber: String? + + public let isFIPSCapable: UInt + + public let isFIPSApproved: UInt + + public let fpsVersion: Version? + + public let stmVersion: Version? + /// Returns the supported (not necessarily enabled) capabilities for a given transport. public let supportedCapabilities: [DeviceTransport: UInt] /// Returns whether or not a Configuration Lock is set for the Management application on the YubiKey. @@ -77,32 +91,34 @@ public struct DeviceInfo { public let isSky: Bool /// The mutable configuration of the YubiKey. public let config: DeviceConfig + /// PIN complexity + public let pinComplexity: Bool + + public let isResetBlocked: UInt - internal let tagIsUSBSupported: TKTLVTag = 0x01 + internal let tagUSBSupported: TKTLVTag = 0x01 internal let tagSerialNumber: TKTLVTag = 0x02 - internal let tagIsUSBEnabled: TKTLVTag = 0x03 + internal let tagUSBEnabled: TKTLVTag = 0x03 internal let tagFormFactor: TKTLVTag = 0x04 internal let tagFirmwareVersion: TKTLVTag = 0x05 internal let tagAutoEjectTimeout: TKTLVTag = 0x06 internal let tagChallengeResponseTimeout: TKTLVTag = 0x07 internal let tagDeviceFlags: TKTLVTag = 0x08 - internal let tagIsNFCSupported: TKTLVTag = 0x0d - internal let tagIsNFCEnabled: TKTLVTag = 0x0e - internal let tagIsConfigLocked: TKTLVTag = 0x0a + internal let tagNFCSupported: TKTLVTag = 0x0d + internal let tagNFCEnabled: TKTLVTag = 0x0e + internal let tagConfigLocked: TKTLVTag = 0x0a + internal let tagPartNumber: TKTLVTag = 0x13 + internal let tagFIPSCapable: TKTLVTag = 0x14 + internal let tagFIPSApproved: TKTLVTag = 0x15 + internal let tagPINComplexity: TKTLVTag = 0x16 + internal let tagNFCRestricted: TKTLVTag = 0x17 + internal let tagResetBlocked: TKTLVTag = 0x18 + internal let tagFPSVersion: TKTLVTag = 0x20 + internal let tagSTMVersion: TKTLVTag = 0x21 - internal init(withData data: Data, fallbackVersion: Version) throws { - guard let count = data.bytes.first, count > 0 else { throw ManagementSessionError.missingData } - guard let tlvs = TKBERTLVRecord.dictionaryOfData(from: data.subdata(in: 1.. Bool { + public func isApplicationSupported(_ application: Capability, overTransport transport: DeviceTransport) -> Bool { guard let mask = supportedCapabilities[transport] else { return false } return (mask & application.rawValue) == application.rawValue } diff --git a/YubiKit/YubiKit/Management/ManagementSession.swift b/YubiKit/YubiKit/Management/ManagementSession.swift index 9212ecd..6226ac8 100644 --- a/YubiKit/YubiKit/Management/ManagementSession.swift +++ b/YubiKit/YubiKit/Management/ManagementSession.swift @@ -82,9 +82,22 @@ public final actor ManagementSession: Session, InternalSession { Logger.management.debug("\(String(describing: self).lastComponent), \(#function)") guard self.supports(ManagementFeature.deviceInfo) else { throw SessionError.notSupported } 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) - return try DeviceInfo(withData: data, fallbackVersion: version) + + var page: UInt8 = 0 + var hasMoreData = true + var result = [TKTLVTag : Data]() + while hasMoreData { + let apdu = APDU(cla: 0, ins: 0x1d, p1: page, p2: 0) + let data = try await connection.send(apdu: apdu) + guard let count = data.bytes.first, count > 0 else { throw ManagementSessionError.missingData } + guard let tlvs = TKBERTLVRecord.dictionaryOfData(from: data.subdata(in: 1.. Bool { + public func isApplicationSupported(_ application: Capability, overTransport transport: DeviceTransport) async throws -> Bool { Logger.management.debug("\(String(describing: self).lastComponent), \(#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 { + public func isApplicationEnabled(_ application: Capability, overTransport transport: DeviceTransport) async throws -> Bool { Logger.management.debug("\(String(describing: self).lastComponent), \(#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 { + public func setEnabled(_ enabled: Bool, application: Capability, overTransport transport: DeviceTransport, reboot: Bool = false) async throws { Logger.management.debug("\(String(describing: self).lastComponent), \(#function): \(enabled), application: \(String(describing: application)), overTransport: \(String(describing: transport)), reboot: \(reboot)") let deviceInfo = try await getDeviceInfo() guard enabled != deviceInfo.config.isApplicationEnabled(application, overTransport: transport) else { return } @@ -128,13 +141,13 @@ 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 { + public func disableApplication(_ application: Capability, overTransport transport: DeviceTransport, reboot: Bool = false) async throws { Logger.management.debug("\(String(describing: self).lastComponent), \(#function): \(String(describing: application)), overTransport: \(String(describing: transport)), reboot: \(reboot)") 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 { + public func enableApplication(_ application: Capability, overTransport transport: DeviceTransport, reboot: Bool = false) async throws { Logger.management.debug("\(String(describing: self).lastComponent), \(#function): \(String(describing: application)), overTransport: \(String(describing: transport)), reboot: \(reboot)") try await setEnabled(true, application: application, overTransport: transport, reboot: reboot) } diff --git a/YubiKit/YubiKit/Version.swift b/YubiKit/YubiKit/Version.swift index 0efb40f..e11a3b5 100644 --- a/YubiKit/YubiKit/Version.swift +++ b/YubiKit/YubiKit/Version.swift @@ -29,7 +29,8 @@ public struct Version: Comparable, CustomStringConvertible { micro = bytes[2] } - internal init?(withString string: String) { + /// Create a new Version from a version string, e.g. "5.7.0". + public init?(withString string: String) { let components = string.components(separatedBy: ".") guard components.count == 3, let major = UInt8(components[0]),