From 7939b4d7102e494d8466069caf143a78e3509d6b Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Mon, 27 May 2024 16:17:33 +0200 Subject: [PATCH 1/4] Support for paged reading of device info and config data. --- .../Sessions/MGMT/YKFManagementSession.m | 40 +++++++++++++++++-- .../YKFManagementDeviceInfo+Private.h | 3 +- .../Shared/Sessions/YKFManagementDeviceInfo.m | 13 ++---- YubiKitTests/Tests/ManagementTests.swift | 2 +- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m index d2c2611f..b609c727 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m @@ -21,6 +21,8 @@ #import "YKFSmartCardInterface.h" #import "YKFSelectApplicationAPDU.h" #import "YKFFeature.h" +#import "NSArray+YKFTLVRecord.h" +#import "YKFTLVRecord.h" NSString* const YKFManagementErrorDomain = @"com.yubico.management"; @@ -59,10 +61,42 @@ - (void)getDeviceInfoWithCompletion:(YKFManagementSessionGetDeviceInfoBlock)comp completion(nil, [[NSError alloc] initWithDomain:YKFManagementErrorDomain code:YKFManagementErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Device info not supported by this YubiKey."}]); return; } - YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0x00 ins:0x1D p1:0x00 p2:0x00 data:[NSData data] type:YKFAPDUTypeShort]; - [self.smartCardInterface executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { - YKFManagementDeviceInfo *deviceInfo = [[YKFManagementDeviceInfo alloc] initWithResponseData:data defaultVersion:self.version]; + [self readPagedDeviceInfoWithCompletion:^(NSMutableArray *result, NSError * _Nullable error) { + if (error) { + completion(nil, error); + return; + } + YKFManagementDeviceInfo *deviceInfo = [[YKFManagementDeviceInfo alloc] initWithResponseData:result defaultVersion:self.version]; completion(deviceInfo, error); + return; + } result: [NSMutableArray new] page: 0]; +} + +typedef void (^YKFManagementSessionReadPagedDeviceInfoBlock) + (NSMutableArray* result, NSError* _Nullable error); + +- (void)readPagedDeviceInfoWithCompletion:(YKFManagementSessionReadPagedDeviceInfoBlock)completion result:(NSMutableArray* _Nonnull)result page:(UInt8)page { + YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0x00 ins:0x1D p1:page p2:0x00 data:[NSData data] type:YKFAPDUTypeShort]; + [self.smartCardInterface executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { + if (error) { + completion(nil, error); + return; + } + const char* bytes = (const char*)[data bytes]; + int length = bytes[0] & 0xff; + if (length != data.length - 1) { + completion(result, nil); + return; + } + NSArray *records = [YKFTLVRecord sequenceOfRecordsFromData:[data subdataWithRange:NSMakeRange(1, data.length - 1)]]; + [result addObjectsFromArray:records]; + if ([records ykfTLVRecordWithTag:0x10] != nil) { + [self readPagedDeviceInfoWithCompletion:completion result:result page:page + 1]; + return; + } else { + completion(result, nil); + return; + } }]; } diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h index 3b6efac7..ffa6a97c 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h @@ -31,6 +31,7 @@ static const NSUInteger YKFManagementTagConfigLocked = 0x0a; NS_ASSUME_NONNULL_BEGIN +@class YKFTLVRecord; @interface YKFManagementDeviceInfo() @property (nonatomic, readwrite) NSUInteger usbSupportedMask; @@ -39,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readwrite) NSUInteger usbEnabledMask; @property (nonatomic, readwrite) NSUInteger nfcEnabledMask; -- (nullable instancetype)initWithResponseData:(NSData *)data defaultVersion:(YKFVersion *)defaultVersion NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithResponseData:(NSMutableArray *)records defaultVersion:(YKFVersion *)defaultVersion NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m index f326f9cc..8af6698e 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m @@ -35,18 +35,11 @@ @interface YKFManagementDeviceInfo() @implementation YKFManagementDeviceInfo -- (nullable instancetype)initWithResponseData:(nonnull NSData *)data defaultVersion:(nonnull YKFVersion *)defaultVersion { - YKFAssertAbortInit(data.length); +- (nullable instancetype)initWithResponseData:(nonnull NSMutableArray *)records defaultVersion:(nonnull YKFVersion *)defaultVersion { + YKFAssertAbortInit(records.count > 0); YKFAssertAbortInit(defaultVersion) self = [super init]; - if (self) { - const char* bytes = (const char*)[data bytes]; - int length = bytes[0] & 0xff; - if (length != data.length - 1) { - return nil; - } - NSArray *records = [YKFTLVRecord sequenceOfRecordsFromData:[data subdataWithRange:NSMakeRange(1, data.length - 1)]]; - + if (self) { self.isLocked = [[records ykfTLVRecordWithTag:YKFManagementTagConfigLocked].value ykf_integerValue] == 1; self.serialNumber = [[records ykfTLVRecordWithTag:YKFManagementTagSerialNumber].value ykf_integerValue]; diff --git a/YubiKitTests/Tests/ManagementTests.swift b/YubiKitTests/Tests/ManagementTests.swift index 97177ad6..e26589e5 100644 --- a/YubiKitTests/Tests/ManagementTests.swift +++ b/YubiKitTests/Tests/ManagementTests.swift @@ -81,7 +81,7 @@ class ManagementTests: XCTestCase { connection.managementSessionAndDeviceInfo { session, deviceInfo in // Only assert major and minor version XCTAssert(deviceInfo.version.major == 5) - XCTAssert(deviceInfo.version.minor == 2 || deviceInfo.version.minor == 3 || deviceInfo.version.minor == 4) + XCTAssert(deviceInfo.version.minor == 2 || deviceInfo.version.minor == 3 || deviceInfo.version.minor == 4 || deviceInfo.version.minor == 7) print("✅ Got version: \(deviceInfo.version)") completion() } From 8e19aee76bcf26366c8a051c1a42c1f06b131922 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Tue, 28 May 2024 16:55:50 +0200 Subject: [PATCH 2/4] Implemented new features in YKFManagementDeviceInfo. --- ...ManagementInterfaceConfiguration+Private.h | 2 + .../YKFManagementInterfaceConfiguration.h | 1 + .../YKFManagementInterfaceConfiguration.m | 21 ++++++ .../YKFManagementDeviceInfo+Private.h | 8 +++ .../Shared/Sessions/YKFManagementDeviceInfo.h | 19 ++++- .../Shared/Sessions/YKFManagementDeviceInfo.m | 72 +++++++++++++++---- 6 files changed, 110 insertions(+), 13 deletions(-) diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h index f7eec557..1f66b936 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h @@ -31,6 +31,8 @@ - (nullable instancetype)initWithDeviceInfo:(nonnull YKFManagementDeviceInfo *)response NS_DESIGNATED_INITIALIZER; ++ (NSUInteger)translateFipsMask:(NSUInteger)mask; + @end #endif /* YKFManagementInterfaceConfiguration_Private_h */ diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h index e233f19b..cf13e2ad 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h @@ -21,6 +21,7 @@ typedef NS_ENUM(NSUInteger, YKFManagementApplicationType) { YKFManagementApplicationTypeOPGP = 0x08, YKFManagementApplicationTypePIV = 0x10, YKFManagementApplicationTypeOATH = 0x20, + YKFManagementApplicationTypeHSMAUTH = 0x0100, YKFManagementApplicationTypeCTAP2 = 0x0200 }; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m index 9f95a547..447327bd 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m @@ -98,4 +98,25 @@ - (void) setEnabled: (BOOL)newValue application:(YKFManagementApplicationType)ap } } ++ (NSUInteger)translateFipsMask:(NSUInteger)fipsMask { + NSUInteger capabilities = 0; + if ((fipsMask & 0b00000001) != 0) { + capabilities |= YKFManagementApplicationTypeOTP; + } + if ((fipsMask & 0b00000010) != 0) { + capabilities |= YKFManagementApplicationTypePIV; + } + if ((fipsMask & 0b00000100) != 0) { + capabilities |= YKFManagementApplicationTypeOPGP; + } + if ((fipsMask & 0b00001000) != 0) { + capabilities |= YKFManagementApplicationTypeOATH; + } + if ((fipsMask & 0b00010000) != 0) { + capabilities |= YKFManagementApplicationTypeHSMAUTH; + } + return capabilities; +} + + @end diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h index ffa6a97c..6d223136 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h @@ -28,6 +28,14 @@ static const NSUInteger YKFManagementTagDeviceFlags = 0x08; static const NSUInteger YKFManagementTagNFCSupported = 0x0d; static const NSUInteger YKFManagementTagNFCEnabled = 0x0e; static const NSUInteger YKFManagementTagConfigLocked = 0x0a; +static const NSUInteger YKFManagementTagPartNumber = 0x13; +static const NSUInteger YKFManagementTagFIPSCapable = 0x14; +static const NSUInteger YKFManagementTagFIPSApproved = 0x15; +static const NSUInteger YKFManagementTagPINComplexity = 0x16; +static const NSUInteger YKFManagementTagNFCRestricted = 0x17; +static const NSUInteger YKFManagementTagResetBlocked = 0x18; +static const NSUInteger YKFManagementTagFPSVersion = 0x20; +static const NSUInteger YKFManagementTagSTMVersion = 0x21; NS_ASSUME_NONNULL_BEGIN diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.h b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.h index eac984c8..af097852 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.h @@ -23,10 +23,18 @@ typedef NS_ENUM(NSUInteger, YKFFormFactor) { YKFFormFactorUnknown = 0x00, /// A keychain-sized YubiKey with a USB-A connector. YKFFormFactorUSBAKeychain = 0x01, + /// A nano-sized YubiKey with a USB-A connector. + YKFFormFactorUSBANano = 0x02, /// A keychain-sized YubiKey with a USB-C connector. YKFFormFactorUSBCKeychain = 0x03, + /// A nano-sized YubiKey with a USB-C connector. + YKFFormFactorUSBCNano = 0x04, /// A keychain-sized YubiKey with both USB-C and Lightning connectors. YKFFormFactorUSBCLightning = 0x05, + /// A keychain-sized YubiKey with fingerprint sensor and USB-A connector. + YKFFormFactorUSBABio = 0x06, + /// A keychain-sized YubiKey with fingerprint sensor and USB-C connector. + YKFFormFactorUSBCBio = 0x07, }; NS_ASSUME_NONNULL_BEGIN @@ -35,10 +43,19 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, nullable) YKFManagementInterfaceConfiguration* configuration; +@property (nonatomic, readonly) NSUInteger serialNumber; @property (nonatomic, readonly) YKFVersion *version; @property (nonatomic, readonly) YKFFormFactor formFactor; -@property (nonatomic, readonly) NSUInteger serialNumber; +@property (nonatomic, readonly, nullable) NSString* partNumber; +@property (nonatomic, readonly) NSUInteger isFIPSCapable; +@property (nonatomic, readonly) NSUInteger isFIPSApproved; +@property (nonatomic, readonly, nullable) YKFVersion *fpsVersion; +@property (nonatomic, readonly, nullable) YKFVersion *stmVersion; @property (nonatomic, readonly) bool isConfigurationLocked; +@property (nonatomic, readonly) bool isFips; +@property (nonatomic, readonly) bool isSky; +@property (nonatomic, readonly) bool pinComplexity; +@property (nonatomic, readonly) NSUInteger isResetBlocked; @end diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m index 8af6698e..03893afa 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m @@ -24,11 +24,19 @@ @interface YKFManagementDeviceInfo() +@property (nonatomic, readwrite) NSUInteger serialNumber; @property (nonatomic, readwrite) YKFVersion *version; @property (nonatomic, readwrite) YKFFormFactor formFactor; -@property (nonatomic, readwrite) NSUInteger serialNumber; -@property (nonatomic, readwrite) bool isLocked; - +@property (nonatomic, readwrite, nullable) NSString* partNumber; +@property (nonatomic, readwrite) NSUInteger isFIPSCapable; +@property (nonatomic, readwrite) NSUInteger isFIPSApproved; +@property (nonatomic, readwrite, nullable) YKFVersion *fpsVersion; +@property (nonatomic, readwrite, nullable) YKFVersion *stmVersion; +@property (nonatomic, readwrite) bool isConfigurationLocked; +@property (nonatomic, readwrite) bool isFips; +@property (nonatomic, readwrite) bool isSky; +@property (nonatomic, readwrite) bool pinComplexity; +@property (nonatomic, readwrite) NSUInteger isResetBlocked; @property (nonatomic, readwrite) YKFManagementInterfaceConfiguration *configuration; @end @@ -40,32 +48,72 @@ - (nullable instancetype)initWithResponseData:(nonnull NSMutableArray Date: Tue, 4 Jun 2024 13:31:43 +0200 Subject: [PATCH 3/4] Restructured device info and config. Implemented nfc restricted, challenge response timeout and auto eject timeout. --- .../Shared/APDU/MGMT/YKFManagementWriteAPDU.m | 13 +++++++ ...ManagementInterfaceConfiguration+Private.h | 10 +---- .../YKFManagementInterfaceConfiguration.h | 8 ++++ .../YKFManagementInterfaceConfiguration.m | 37 +++++++++++------- .../Sessions/MGMT/YKFManagementSession.m | 2 +- .../YKFManagementDeviceInfo+Private.h | 8 +--- .../Shared/Sessions/YKFManagementDeviceInfo.m | 15 +++----- .../Additions/YKFNSMutableDataAdditions.h | 10 +++++ .../Additions/YKFNSMutableDataAdditions.m | 17 +++++++++ YubiKitTests/Tests/ManagementTests.swift | 38 +++++++++++++++++-- 10 files changed, 115 insertions(+), 43 deletions(-) diff --git a/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m b/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m index b588780f..0ffd8350 100644 --- a/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m +++ b/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m @@ -12,6 +12,7 @@ #import "YKFManagementDeviceInfo+Private.h" #import "YKFNSMutableDataAdditions.h" #import "YKFAssert.h" +#import "YKFNSDataAdditions+Private.h" @implementation YKFManagementWriteAPDU @@ -35,6 +36,18 @@ - (instancetype)initWithConfiguration:(nonnull YKFManagementInterfaceConfigurati [configData ykf_appendByte:0]; } + if (configuration.autoEjectTimeout != 0) { + [configData ykf_appendUInt16EntryWithTag:YKFManagementTagAutoEjectTimeout value:configuration.autoEjectTimeout]; + } + + if (configuration.challengeResponseTimeout != 0) { + [configData ykf_appendUInt8EntryWithTag:YKFManagementTagChallengeResponseTimeout value:configuration.challengeResponseTimeout]; + } + + if (configuration.isNFCRestricted) { + [configData ykf_appendShortWithTag:YKFManagementTagNFCRestricted data:0x01]; + } + NSMutableData *rawRequest = [[NSMutableData alloc] init]; [rawRequest ykf_appendByte:configData.length]; [rawRequest appendData:configData]; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h index 1f66b936..4c24071e 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration+Private.h @@ -16,20 +16,14 @@ #import "YKFManagementInterfaceConfiguration.h" -@class YKFManagementDeviceInfo; +@class YKFManagementDeviceInfo, YKFTLVRecord; @interface YKFManagementInterfaceConfiguration() -@property (nonatomic, readonly) NSUInteger usbSupportedMask; -@property (nonatomic, readonly) NSUInteger nfcSupportedMask; - -@property (nonatomic, readonly) NSUInteger usbEnabledMask; -@property (nonatomic, readonly) NSUInteger nfcEnabledMask; - @property (nonatomic, readonly) BOOL usbMaskChanged; @property (nonatomic, readonly) BOOL nfcMaskChanged; -- (nullable instancetype)initWithDeviceInfo:(nonnull YKFManagementDeviceInfo *)response NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithTLVRecords:(nonnull NSMutableArray *)records NS_DESIGNATED_INITIALIZER; + (NSUInteger)translateFipsMask:(NSUInteger)mask; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h index cf13e2ad..e5445d9c 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h @@ -33,6 +33,14 @@ typedef NS_ENUM(NSUInteger, YKFManagementTransportType) { @interface YKFManagementInterfaceConfiguration : NSObject @property (nonatomic, readonly) BOOL isConfigurationLocked; +@property (nonatomic, readwrite) NSTimeInterval autoEjectTimeout; +@property (nonatomic, readwrite) NSTimeInterval challengeResponseTimeout; +@property (nonatomic, readwrite) BOOL isNFCRestricted; + +@property (nonatomic, readonly) NSUInteger usbSupportedMask; +@property (nonatomic, readonly) NSUInteger nfcSupportedMask; +@property (nonatomic, readwrite) NSUInteger usbEnabledMask; +@property (nonatomic, readwrite) NSUInteger nfcEnabledMask; - (BOOL)isEnabled:(YKFManagementApplicationType)application overTransport:(YKFManagementTransportType)transport; - (BOOL)isSupported:(YKFManagementApplicationType)application overTransport:(YKFManagementTransportType)transport; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m index 447327bd..6e0d1ef5 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.m @@ -15,17 +15,15 @@ #import "YKFManagementDeviceInfo+Private.h" #import "YKFManagementDeviceInfo.h" #import "YKFAssert.h" +#import "YKFTLVRecord.h" +#import "NSArray+YKFTLVRecord.h" +#import "YKFNSDataAdditions+Private.h" @interface YKFManagementInterfaceConfiguration() @property (nonatomic, readwrite) BOOL isConfigurationLocked; - @property (nonatomic, readwrite) NSUInteger usbSupportedMask; @property (nonatomic, readwrite) NSUInteger nfcSupportedMask; - -@property (nonatomic, readwrite) NSUInteger usbEnabledMask; -@property (nonatomic, readwrite) NSUInteger nfcEnabledMask; - @property (nonatomic, readwrite) BOOL usbMaskChanged; @property (nonatomic, readwrite) BOOL nfcMaskChanged; @@ -33,16 +31,29 @@ @interface YKFManagementInterfaceConfiguration() @implementation YKFManagementInterfaceConfiguration -- (nullable instancetype)initWithDeviceInfo:(nonnull YKFManagementDeviceInfo *)deviceInfo { - YKFAssertAbortInit(deviceInfo); +- (nullable instancetype)initWithTLVRecords:(nonnull NSMutableArray *)records { self = [super init]; if (self) { - - self.isConfigurationLocked = deviceInfo.isConfigurationLocked; - self.usbSupportedMask = deviceInfo.usbSupportedMask; - self.nfcSupportedMask = deviceInfo.nfcSupportedMask; - self.usbEnabledMask = deviceInfo.usbEnabledMask; - self.nfcEnabledMask = deviceInfo.nfcEnabledMask; + self.isConfigurationLocked = [[records ykfTLVRecordWithTag:YKFManagementTagConfigLocked].value ykf_integerValue] == 1; + self.usbSupportedMask = [[records ykfTLVRecordWithTag:YKFManagementTagUSBSupported].value ykf_integerValue]; + self.usbEnabledMask = [[records ykfTLVRecordWithTag:YKFManagementTagUSBEnabled].value ykf_integerValue]; + self.nfcSupportedMask = [[records ykfTLVRecordWithTag:YKFManagementTagNFCSupported].value ykf_integerValue]; + self.nfcEnabledMask = [[records ykfTLVRecordWithTag:YKFManagementTagNFCEnabled].value ykf_integerValue]; + + NSData *autoEjectTimeoutData = [records ykfTLVRecordWithTag:YKFManagementTagAutoEjectTimeout].value; + if (autoEjectTimeoutData) { + self.autoEjectTimeout = [autoEjectTimeoutData ykf_integerValue]; + } + + NSData *challengeResponseTimeoutData = [records ykfTLVRecordWithTag:YKFManagementTagChallengeResponseTimeout].value; + if (challengeResponseTimeoutData) { + self.challengeResponseTimeout = [challengeResponseTimeoutData ykf_integerValue]; + } + + NSData *isNFCRestrictedData = [records ykfTLVRecordWithTag:YKFManagementTagNFCRestricted].value; + if (isNFCRestrictedData) { + self.isNFCRestricted = [isNFCRestrictedData ykf_integerValue] == 1; + } } return self; } diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m index b609c727..e579ff3d 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m @@ -66,7 +66,7 @@ - (void)getDeviceInfoWithCompletion:(YKFManagementSessionGetDeviceInfoBlock)comp completion(nil, error); return; } - YKFManagementDeviceInfo *deviceInfo = [[YKFManagementDeviceInfo alloc] initWithResponseData:result defaultVersion:self.version]; + YKFManagementDeviceInfo *deviceInfo = [[YKFManagementDeviceInfo alloc] initWithTLVRecords:result defaultVersion:self.version]; completion(deviceInfo, error); return; } result: [NSMutableArray new] page: 0]; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h index 6d223136..53312313 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h @@ -42,13 +42,7 @@ NS_ASSUME_NONNULL_BEGIN @class YKFTLVRecord; @interface YKFManagementDeviceInfo() -@property (nonatomic, readwrite) NSUInteger usbSupportedMask; -@property (nonatomic, readwrite) NSUInteger nfcSupportedMask; - -@property (nonatomic, readwrite) NSUInteger usbEnabledMask; -@property (nonatomic, readwrite) NSUInteger nfcEnabledMask; - -- (nullable instancetype)initWithResponseData:(NSMutableArray *)records defaultVersion:(YKFVersion *)defaultVersion NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithTLVRecords:(NSMutableArray *)records defaultVersion:(YKFVersion *)defaultVersion NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m index 03893afa..2c27b70a 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m @@ -43,7 +43,7 @@ @interface YKFManagementDeviceInfo() @implementation YKFManagementDeviceInfo -- (nullable instancetype)initWithResponseData:(nonnull NSMutableArray *)records defaultVersion:(nonnull YKFVersion *)defaultVersion { +- (nullable instancetype)initWithTLVRecords:(nonnull NSMutableArray *)records defaultVersion:(nonnull YKFVersion *)defaultVersion { YKFAssertAbortInit(records.count > 0); YKFAssertAbortInit(defaultVersion) self = [super init]; @@ -98,28 +98,23 @@ - (nullable instancetype)initWithResponseData:(nonnull NSMutableArray 0); + [self ykf_appendByte:tag]; + [self ykf_appendByte:sizeof(UInt8)]; + [self appendBytes:&value length:sizeof(UInt8)]; +} + +- (void)ykf_appendUInt16EntryWithTag:(UInt8)tag value:(UInt16)value { + YKFParameterAssertReturn(tag > 0); + + UInt16 bigEndianValue = CFSwapInt16HostToBig(value); + + [self ykf_appendByte:tag]; + [self ykf_appendByte:sizeof(UInt16)]; + [self appendBytes:&bigEndianValue length:sizeof(UInt16)]; +} + - (void)ykf_appendUInt32EntryWithTag:(UInt8)tag value:(UInt32)value { YKFParameterAssertReturn(tag > 0); diff --git a/YubiKitTests/Tests/ManagementTests.swift b/YubiKitTests/Tests/ManagementTests.swift index e26589e5..d0648921 100644 --- a/YubiKitTests/Tests/ManagementTests.swift +++ b/YubiKitTests/Tests/ManagementTests.swift @@ -95,15 +95,45 @@ class ManagementTests: XCTestCase { session.getDeviceInfo { deviceInfo, error in guard let deviceInfo = deviceInfo else { XCTFail("Failed to get DeviceInfo: \(error!)"); return } print("✅ Got device info:") - print(" is locked: \(deviceInfo.isConfigurationLocked)") - print(" serial number: \(deviceInfo.serialNumber)") - print(" form factor: \(deviceInfo.formFactor.rawValue)") - print(" firmware version: \(deviceInfo.version)") + print(""" +YubiKey \(deviceInfo.formFactor) \(deviceInfo.version) (#\(deviceInfo.serialNumber)) +Supported capabilities: \(String(describing: deviceInfo.configuration?.nfcSupportedMask)) +Supported capabilities: \(String(describing: deviceInfo.configuration?.usbSupportedMask)) +isConfigLocked: \(deviceInfo.isConfigurationLocked) +isFips: \(deviceInfo.isFips) +isSky: \(deviceInfo.isSky) +partNumber: \(String(describing: deviceInfo.partNumber)) +isFipsCapable: \(deviceInfo.isFIPSCapable) +isFipsApproved: \(deviceInfo.isFIPSApproved) +pinComplexity: \(deviceInfo.pinComplexity) +resetBlocked: \(deviceInfo.isResetBlocked) +fpsVersion: \(String(describing: deviceInfo.fpsVersion)) +stmVersion: \(String(describing: deviceInfo.stmVersion)) +""") completion() } } } } + + func testZEnableNFCRestriction() { + runYubiKitTest { connection, completion in + connection.managementSessionAndDeviceInfo { session, deviceInfo in + guard let config = deviceInfo.configuration else { completion(); return } + config.isNFCRestricted = true + session.write(config, reboot: false) { error in + XCTAssertNil(error) + session.getDeviceInfo { deviceInfo, error in + XCTAssertNil(error) + if let isNFCRestricted = deviceInfo?.configuration?.isNFCRestricted { + XCTAssertTrue(isNFCRestricted) + } + completion() + } + } + } + } + } } extension YKFConnectionProtocol { From 2e802b97ec01a3765ebf6403425f4a4ba44e17b3 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Wed, 5 Jun 2024 14:51:18 +0200 Subject: [PATCH 4/4] Implemented support and tests for lock code in ManagementSession. --- .../Shared/APDU/MGMT/YKFManagementWriteAPDU.h | 2 +- .../Shared/APDU/MGMT/YKFManagementWriteAPDU.m | 10 ++++- .../Sessions/MGMT/YKFManagementSession.h | 45 +++++++++++++++++++ .../Sessions/MGMT/YKFManagementSession.m | 13 ++++-- .../YKFManagementDeviceInfo+Private.h | 1 + YubiKitTests/Tests/ManagementTests.swift | 28 +++++++++++- .../Tests/Utilities/Data+Extensions.swift | 12 +++++ 7 files changed, 105 insertions(+), 6 deletions(-) diff --git a/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.h b/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.h index e34c9cfc..c7da68a2 100644 --- a/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.h +++ b/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface YKFManagementWriteAPDU : YKFAPDU -- (instancetype)initWithConfiguration:(nonnull YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithConfiguration:(nonnull YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot lockCode:(nullable NSData *)lockCode newLockCode:(nullable NSData *)newLockCode NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; @end diff --git a/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m b/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m index 0ffd8350..4f507cc5 100644 --- a/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m +++ b/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m @@ -18,7 +18,7 @@ @implementation YKFManagementWriteAPDU static UInt8 const YKFManagementConfigurationTagsReboot = 0x0c; -- (instancetype)initWithConfiguration:(nonnull YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot { +- (instancetype)initWithConfiguration:(nonnull YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot lockCode:(NSData *)lockCode newLockCode:(NSData *)newLockCode { YKFAssertAbortInit(configuration); NSMutableData *configData = [[NSMutableData alloc] init]; @@ -36,6 +36,14 @@ - (instancetype)initWithConfiguration:(nonnull YKFManagementInterfaceConfigurati [configData ykf_appendByte:0]; } + if (lockCode) { + [configData ykf_appendEntryWithTag:YKFManagementTagUnlock data:lockCode]; + } + + if (newLockCode) { + [configData ykf_appendEntryWithTag:YKFManagementTagConfigLocked data:newLockCode]; + } + if (configuration.autoEjectTimeout != 0) { [configData ykf_appendUInt16EntryWithTag:YKFManagementTagAutoEjectTimeout value:configuration.autoEjectTimeout]; } diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.h b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.h index cf19124e..5a1101e9 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.h @@ -64,6 +64,51 @@ NS_ASSUME_NONNULL_BEGIN /// The method is thread safe and can be invoked from any thread (main or a background thread). - (void)getDeviceInfoWithCompletion:(YKFManagementSessionGetDeviceInfoBlock)completion; +/// @abstract +/// Writes configuration to YubiKey (allos to enable and disable applications on YubiKey) +/// +/// @param configuration +/// The configurations that represent information on which interfaces/applications need to be enabled +/// +/// @param reboot +/// The device reboots after setting configuration. +/// +/// @param lockCode +/// Required if a configuration lock code is set. +/// +/// @param newLockCode +/// changes or removes (if 16 byte all-zero) the configuration lock code. +/// +/// @param completion +/// The response block which is executed after the request was processed by the key. The completion block +/// will be executed on a background thread. +/// +/// @note +/// This method requires support for device config, available in YubiKey 5.0 or later. +/// The method is thread safe and can be invoked from any thread (main or a background thread). +- (void)writeConfiguration:(YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot lockCode:(nullable NSData *)lockCode newLockCode:(nullable NSData *)newLockCode completion:(nonnull YKFManagementSessionWriteCompletionBlock)completion; + +/// @abstract +/// Writes configuration to YubiKey (allos to enable and disable applications on YubiKey) +/// +/// @param configuration +/// The configurations that represent information on which interfaces/applications need to be enabled +/// +/// @param reboot +/// The device reboots after setting configuration. +/// +/// @param lockCode +/// Required if a configuration lock code is set. +/// +/// @param completion +/// The response block which is executed after the request was processed by the key. The completion block +/// will be executed on a background thread. +/// +/// @note +/// This method requires support for device config, available in YubiKey 5.0 or later. +/// The method is thread safe and can be invoked from any thread (main or a background thread). +- (void)writeConfiguration:(YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot lockCode:(nullable NSData *)lockCode completion:(nonnull YKFManagementSessionWriteCompletionBlock)completion; + /// @abstract /// Writes configuration to YubiKey (allos to enable and disable applications on YubiKey) /// diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m index e579ff3d..5ddbdcd8 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementSession.m @@ -100,19 +100,26 @@ - (void)readPagedDeviceInfoWithCompletion:(YKFManagementSessionReadPagedDeviceIn }]; } -- (void)writeConfiguration:(YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot completion:(nonnull YKFManagementSessionWriteCompletionBlock)completion { - YKFParameterAssertReturn(configuration); +- (void)writeConfiguration:(YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot lockCode:(nullable NSData *)lockCode newLockCode:(nullable NSData *)newLockCode completion:(nonnull YKFManagementSessionWriteCompletionBlock)completion { YKFParameterAssertReturn(configuration); if (![self.features.deviceConfig isSupportedBySession:self]) { completion([[NSError alloc] initWithDomain:YKFManagementErrorDomain code:YKFManagementErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Writing device configuration not supported by this YubiKey."}]); return; } - YKFManagementWriteAPDU *apdu = [[YKFManagementWriteAPDU alloc]initWithConfiguration:configuration reboot:reboot]; + YKFManagementWriteAPDU *apdu = [[YKFManagementWriteAPDU alloc]initWithConfiguration:configuration reboot:reboot lockCode:lockCode newLockCode:newLockCode]; [self.smartCardInterface executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { completion(error); }]; } +- (void)writeConfiguration:(YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot lockCode:(nullable NSData *)lockCode completion:(nonnull YKFManagementSessionWriteCompletionBlock)completion { + [self writeConfiguration:configuration reboot:reboot lockCode:lockCode newLockCode:nil completion:completion]; +} + +- (void)writeConfiguration:(YKFManagementInterfaceConfiguration*)configuration reboot:(BOOL)reboot completion:(nonnull YKFManagementSessionWriteCompletionBlock)completion { + [self writeConfiguration:configuration reboot:reboot lockCode:nil newLockCode:nil completion:completion]; +} + // No application side state that needs clearing but this will be called when another // session is replacing the YKFManagementSession. - (void)clearSessionState { diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h index 53312313..0e72da4d 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h @@ -28,6 +28,7 @@ static const NSUInteger YKFManagementTagDeviceFlags = 0x08; static const NSUInteger YKFManagementTagNFCSupported = 0x0d; static const NSUInteger YKFManagementTagNFCEnabled = 0x0e; static const NSUInteger YKFManagementTagConfigLocked = 0x0a; +static const NSUInteger YKFManagementTagUnlock = 0x0b; static const NSUInteger YKFManagementTagPartNumber = 0x13; static const NSUInteger YKFManagementTagFIPSCapable = 0x14; static const NSUInteger YKFManagementTagFIPSApproved = 0x15; diff --git a/YubiKitTests/Tests/ManagementTests.swift b/YubiKitTests/Tests/ManagementTests.swift index d0648921..4c06c22c 100644 --- a/YubiKitTests/Tests/ManagementTests.swift +++ b/YubiKitTests/Tests/ManagementTests.swift @@ -15,6 +15,9 @@ import XCTest import Foundation +fileprivate let lockCode = Data(hexEncodedString: "01020304050607080102030405060708")! +fileprivate let clearLockCode = Data(hexEncodedString: "00000000000000000000000000000000")! + class ManagementTests: XCTestCase { func testDisableOATH() { runYubiKitTest { connection, completion in @@ -116,6 +119,27 @@ stmVersion: \(String(describing: deviceInfo.stmVersion)) } } + func testLockCode() throws { + runYubiKitTest { connection, completion in + connection.managementSessionAndDeviceInfo { session, deviceInfo in + let config = deviceInfo.configuration! + session.write(config, reboot: false, lockCode: nil, newLockCode: lockCode) { error in + guard error == nil else { XCTFail("Failed setting new lock code"); return } + print("✅ Lock code set to: \(lockCode.hexDescription)") + session.write(config, reboot: false, lockCode: nil) { error in + guard error != nil else { XCTFail("Successfully updated config although no lock code was supplied and it should have been enabled."); return } + print("✅ Failed updating device config (as expected) without using lock code.") + session.write(config, reboot: false, lockCode: lockCode) { error in + guard error == nil else { print("Failed to update device config even though lock code was supplied."); return } + print("✅ Succesfully updated device config using lock code.") + completion() + } + } + } + } + } + } + func testZEnableNFCRestriction() { runYubiKitTest { connection, completion in connection.managementSessionAndDeviceInfo { session, deviceInfo in @@ -143,7 +167,9 @@ extension YKFConnectionProtocol { guard let session = session else { XCTAssertTrue(false, "Failed to get Management Session: \(error!)"); return } session.getDeviceInfo { deviceInfo, error in guard let deviceInfo = deviceInfo else { XCTAssertTrue(false, "Failed to read device info: \(error!)"); return } - completion(session, deviceInfo) + session.write(deviceInfo.configuration!, reboot: false, lockCode: lockCode, newLockCode: clearLockCode) { error in + completion(session, deviceInfo) + } } } } diff --git a/YubiKitTests/Tests/Utilities/Data+Extensions.swift b/YubiKitTests/Tests/Utilities/Data+Extensions.swift index d700ca86..29cac4dc 100644 --- a/YubiKitTests/Tests/Utilities/Data+Extensions.swift +++ b/YubiKitTests/Tests/Utilities/Data+Extensions.swift @@ -15,6 +15,18 @@ import Foundation extension Data { + + public init?(hexEncodedString: String) { + let string = hexEncodedString.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: " ", with: "") + guard string.count.isMultiple(of: 2) else { return nil } + let chars = string.map { $0 } + let bytes = stride(from: 0, to: chars.count, by: 2) + .map { String(chars[$0]) + String(chars[$0 + 1]) } + .compactMap { UInt8($0, radix: 16) } + guard string.count / bytes.count == 2 else { return nil } + self.init(bytes) + } + var hexDescription: String { return reduce("") {$0 + String(format: "%02x", $1)} }