Skip to content

Commit

Permalink
Merge branch 'jens/management-fixes'
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed Jun 5, 2024
1 parent 80771d6 commit b474825
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 24 deletions.
30 changes: 30 additions & 0 deletions FullStackTests/Tests/ManagementFullStackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import YubiKit

@testable import FullStackTests

fileprivate let lockCode = Data(hexEncodedString: "01020304050607080102030405060708")!
fileprivate let clearLockCode = Data(hexEncodedString: "00000000000000000000000000000000")!

class ManagementFullStackTests: XCTestCase {

func testReadKeyVersion() throws {
Expand Down Expand Up @@ -119,6 +122,30 @@ class ManagementFullStackTests: XCTestCase {
}
}

func testLockCode() throws {
runManagementTest { connection, session, transport in
let config = try await session.getDeviceInfo().config
do {
try await session.updateDeviceConfig(config, reboot: false, newLockCode: lockCode)
print("✅ Lock code set to: \(lockCode.hexEncodedString)")
} catch {
XCTFail("Failed setting new lock code")
}
do {
try await session.updateDeviceConfig(config.deviceConfig(enabling: false, application: .OATH, overTransport: .usb)!, reboot: false)
XCTFail("Successfully updated config although no lock code was supplied and it should have been enabled.")
} catch {
print("✅ Failed updating device config (as expected) without using lock code.")
}
do {
try await session.updateDeviceConfig(config.deviceConfig(enabling: false, application: .OATH, overTransport: .usb)!, reboot: false, lockCode: lockCode)
print("✅ Succesfully updated device config using lock code.")
} catch {
XCTFail("Failed to update device config even though lock code was supplied.")
}
}
}

// 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
Expand Down Expand Up @@ -169,6 +196,9 @@ extension XCTestCase {
#endif

let session = try await ManagementSession.session(withConnection: connection)
let config = try await session.getDeviceInfo().config
// Try removing the lock code.
try? await session.updateDeviceConfig(config, reboot: false, lockCode: lockCode, newLockCode: clearLockCode)
try await test(connection, session, transport)
}
}
Expand Down
14 changes: 7 additions & 7 deletions YubiKit/YubiKit/Management/Capability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ public enum Capability: UInt {

extension Capability {
internal static func translateMaskFrom(fipsMask: UInt) -> UInt {
var capabilities: UInt = 0;
var capabilities: UInt = 0
if fipsMask & 0b00000001 != 0 {
capabilities |= Capability.FIDO2.bit;
capabilities |= Capability.FIDO2.bit
}
if fipsMask & 0b00000010 != 0 {
capabilities |= Capability.PIV.bit;
capabilities |= Capability.PIV.bit
}
if fipsMask & 0b00000100 != 0 {
capabilities |= Capability.OPENPGP.bit;
capabilities |= Capability.OPENPGP.bit
}
if fipsMask & 0b00001000 != 0 {
capabilities |= Capability.OATH.bit;
capabilities |= Capability.OATH.bit
}
if fipsMask & 0b00010000 != 0 {
capabilities |= Capability.HSMAUTH.bit;
capabilities |= Capability.HSMAUTH.bit
}
return capabilities;
return capabilities
}
}
18 changes: 14 additions & 4 deletions YubiKit/YubiKit/Management/DeviceConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,20 @@ import CryptoTokenKit
/// Describes the configuration of a YubiKey which can be altered via the Management application.
public struct DeviceConfig {

/// The timeout used when in CCID-only mode with flag eject enabled.
public let autoEjectTimeout: TimeInterval?

/// The timeout value used by the YubiOTP application when waiting for a user presence check (physical touch).
public let challengeResponseTimeout: TimeInterval?

/// The device flags that are set.
public let deviceFlags: UInt8?

/// The currently enabled capabilities for a given ``DeviceTransport``. The enabled capabilities are represented as
/// ``Capability`` bits being set (1) or not (0).
///
///>Note: This method will return null if the given transport is not supported by the YubiKey, OR if the enabled
/// capabilities state isn't readable. The YubiKey 4 series, for example, does not return enabled-status for USB
public let enabledCapabilities: [DeviceTransport: UInt]
public let isNFCRestricted: Bool?

Expand Down Expand Up @@ -110,6 +121,9 @@ public struct DeviceConfig {
if let lockCode {
data.append(TKBERTLVRecord(tag: tagUnlock, value: lockCode).data)
}
if let newLockCode {
data.append(TKBERTLVRecord(tag: tagConfigurationLock, value: newLockCode).data)
}
if let usbEnabled = enabledCapabilities[.usb] {
data.append(TKBERTLVRecord(tag: tagUSBEnabled, value: UInt16(usbEnabled).bigEndian.data).data)
}
Expand All @@ -126,10 +140,6 @@ public struct DeviceConfig {
if let deviceFlags {
data.append(TKBERTLVRecord(tag: tagDeviceFlags, value: deviceFlags.data).data)
}
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)
}
Expand Down
23 changes: 11 additions & 12 deletions YubiKit/YubiKit/Management/DeviceInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Enabled capabilities: \(config.enabledCapabilities)
isConfigLocked: \(isConfigLocked)
isFips: \(isFips)
isSky: \(isSky)
partNumber: \(partNumber)
partNumber: \(String(describing: partNumber))
isFipsCapable: \(isFIPSCapable)
isFipsApproved: \(isFIPSApproved)
pinComplexity: \(pinComplexity)
Expand All @@ -61,26 +61,25 @@ stmVersion: \(String(describing: stmVersion))
"""
}

/// Returns the serial number of the YubiKey, if available.
/// 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
/// is configured with the SERIAL_API_VISIBLE flag.
public let serialNumber: UInt
/// Returns the version number of the YubiKey firmware.
/// The version number of the YubiKey firmware.
public let version: Version
/// Returns the form factor of the YubiKey.
/// the form factor of the YubiKey.
public let formFactor: FormFactor

/// The part number of the YubiKey.
public let partNumber: String?

/// FIPS capable flags.
public let isFIPSCapable: UInt

/// FIPS approved flags.
public let isFIPSApproved: UInt

/// The FPS version.
public let fpsVersion: Version?

/// The STM 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.
Expand All @@ -93,7 +92,7 @@ stmVersion: \(String(describing: stmVersion))
public let config: DeviceConfig
/// PIN complexity
public let pinComplexity: Bool

/// The reset blocked flag.
public let isResetBlocked: UInt

internal let tagUSBSupported: TKTLVTag = 0x01
Expand Down Expand Up @@ -124,7 +123,7 @@ stmVersion: \(String(describing: stmVersion))
if let rawFormFactor = tlvs[tagFormFactor]?.uint8 {
self.isFips = (rawFormFactor & 0x80) != 0
self.isSky = (rawFormFactor & 0x40) != 0
if let formFactor = FormFactor(rawValue: rawFormFactor) {
if let formFactor = FormFactor(rawValue: rawFormFactor & 0x0f) {
self.formFactor = formFactor
} else {
self.formFactor = .unknown
Expand Down
12 changes: 12 additions & 0 deletions YubiKit/YubiKit/Management/ManagementSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public final actor ManagementSession: Session, InternalSession {
}

/// Returns the DeviceInfo for the connected YubiKey.
///
/// >Note: This functionality requires support for device info available on YubiKey 4.1 or later.
public func getDeviceInfo() async throws -> DeviceInfo {
Logger.management.debug("\(String(describing: self).lastComponent), \(#function)")
guard self.supports(ManagementFeature.deviceInfo) else { throw SessionError.notSupported }
Expand All @@ -100,6 +102,16 @@ public final actor ManagementSession: Session, InternalSession {
return try DeviceInfo(withTlvs: result, fallbackVersion: version)
}

/// Write device config to a YubiKey 5 or later.
///
/// >Note: This functionality requires support for device config, available on YubiKey 5 or later.
///
/// - Parameters:
/// - config: The device configuration to write.
/// - reboot: If true cause the YubiKey to immediately reboot, applying the new configuration.
/// - lockCode: The current lock code. Required if a configuration lock code is set.
/// - newLockCode: Changes or removes (if 16 byte all-zero) the configuration lock code.
///
public func updateDeviceConfig(_ config: DeviceConfig, reboot: Bool, lockCode: Data? = nil, newLockCode: Data? = nil) async throws {
Logger.management.debug("\(String(describing: self).lastComponent), \(#function)")
guard self.supports(ManagementFeature.deviceConfig) else { throw SessionError.notSupported }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
### Running commands in the Management application

- ``getDeviceInfo()``
- ``updateDeviceConfig(_:reboot:lockCode:newLockCode:)``
- ``isApplicationSupported(_:overTransport:)``
- ``isApplicationEnabled(_:overTransport:)``
- ``setEnabled(_:application:overTransport:reboot:)``
Expand All @@ -27,7 +28,7 @@

### Enumerations

- ``ApplicationType``
- ``Capability``
- ``DeviceTransport``
- ``FormFactor``

Expand Down

0 comments on commit b474825

Please sign in to comment.