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 b588780f..4f507cc5 100644 --- a/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m +++ b/YubiKit/YubiKit/Connections/Shared/APDU/MGMT/YKFManagementWriteAPDU.m @@ -12,12 +12,13 @@ #import "YKFManagementDeviceInfo+Private.h" #import "YKFNSMutableDataAdditions.h" #import "YKFAssert.h" +#import "YKFNSDataAdditions+Private.h" @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]; @@ -35,6 +36,26 @@ - (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]; + } + + 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 f7eec557..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,16 @@ #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; @end diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h b/YubiKit/YubiKit/Connections/Shared/Sessions/MGMT/YKFManagementInterfaceConfiguration.h index e233f19b..e5445d9c 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 }; @@ -32,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 9f95a547..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; } @@ -98,4 +109,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/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 d2c2611f..5ddbdcd8 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,26 +61,65 @@ - (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] initWithTLVRecords: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; + } }]; } -- (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 3b6efac7..0e72da4d 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo+Private.h @@ -28,18 +28,22 @@ 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; +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 +@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:(NSData *)data 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.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 f326f9cc..2c27b70a 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/YKFManagementDeviceInfo.m @@ -24,61 +24,97 @@ @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 @implementation YKFManagementDeviceInfo -- (nullable instancetype)initWithResponseData:(nonnull NSData *)data defaultVersion:(nonnull YKFVersion *)defaultVersion { - YKFAssertAbortInit(data.length); +- (nullable instancetype)initWithTLVRecords:(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)]]; - - self.isLocked = [[records ykfTLVRecordWithTag:YKFManagementTagConfigLocked].value ykf_integerValue] == 1; + if (self) { + self.isConfigurationLocked = [[records ykfTLVRecordWithTag:YKFManagementTagConfigLocked].value ykf_integerValue] == 1; self.serialNumber = [[records ykfTLVRecordWithTag:YKFManagementTagSerialNumber].value ykf_integerValue]; - NSData *versionData = [records ykfTLVRecordWithTag:YKFManagementTagFirmwareVersion].value; - if (versionData != nil) { - self.version = [[YKFVersion alloc] initWithData:versionData]; - } else { - self.version = defaultVersion; - } - NSUInteger reportedFormFactor = [[records ykfTLVRecordWithTag:YKFManagementTagFormfactor].value ykf_integerValue]; - switch (reportedFormFactor & 0xf) { + self.isFips = (reportedFormFactor & 0x80) != 0; + self.isSky = (reportedFormFactor & 0x40) != 0; + + switch (reportedFormFactor & 0x0f) { case YKFFormFactorUSBAKeychain: self.formFactor = YKFFormFactorUSBAKeychain; break; + case YKFFormFactorUSBANano: + self.formFactor = YKFFormFactorUSBANano; + break; case YKFFormFactorUSBCKeychain: self.formFactor = YKFFormFactorUSBCKeychain; break; + case YKFFormFactorUSBCNano: + self.formFactor = YKFFormFactorUSBCNano; + break; case YKFFormFactorUSBCLightning: self.formFactor = YKFFormFactorUSBCLightning; break; + case YKFFormFactorUSBABio: + self.formFactor = YKFFormFactorUSBABio; + break; + case YKFFormFactorUSBCBio: + self.formFactor = YKFFormFactorUSBCBio; + break; default: self.formFactor = YKFFormFactorUnknown; } - 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]; + self.isFIPSCapable = [YKFManagementInterfaceConfiguration translateFipsMask:[[records ykfTLVRecordWithTag:YKFManagementTagFIPSCapable].value ykf_integerValue]]; + self.isFIPSApproved = [YKFManagementInterfaceConfiguration translateFipsMask:[[records ykfTLVRecordWithTag:YKFManagementTagFIPSApproved].value ykf_integerValue]]; + + self.pinComplexity = [[records ykfTLVRecordWithTag:YKFManagementTagPINComplexity].value ykf_integerValue] == 1; + self.isResetBlocked = [[records ykfTLVRecordWithTag:YKFManagementTagResetBlocked].value ykf_integerValue]; + + NSData *versionData = [records ykfTLVRecordWithTag:YKFManagementTagFirmwareVersion].value; + if (versionData != nil) { + self.version = [[YKFVersion alloc] initWithData:versionData]; + } else { + self.version = defaultVersion; + } + + NSData *fpsVersionData = [records ykfTLVRecordWithTag:YKFManagementTagFPSVersion].value; + if (fpsVersionData) { + YKFVersion *version = [[YKFVersion alloc] initWithData:fpsVersionData]; + if (version && [version compare:[[YKFVersion alloc] initWithString:@"0.0.0"]] != NSOrderedSame) { + self.fpsVersion = version; + } + } + NSData *stmVersionData = [records ykfTLVRecordWithTag:YKFManagementTagSTMVersion].value; + if (stmVersionData) { + YKFVersion *version = [[YKFVersion alloc] initWithData:stmVersionData]; + if (version && [version compare:[[YKFVersion alloc] initWithString:@"0.0.0"]] != NSOrderedSame) { + self.stmVersion = version; + } + } + self.partNumber = [[NSString alloc] initWithData:[records ykfTLVRecordWithTag:YKFManagementTagPartNumber].value encoding:NSUTF8StringEncoding]; + if (self.partNumber.length == 0) { + self.partNumber = nil; + } - self.configuration = [[YKFManagementInterfaceConfiguration alloc] initWithDeviceInfo:self]; + self.configuration = [[YKFManagementInterfaceConfiguration alloc] initWithTLVRecords:records]; } return self; } diff --git a/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.h b/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.h index 258d34b4..04c7786d 100644 --- a/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.h +++ b/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.h @@ -41,6 +41,16 @@ Appends [tag] + 0x02 + 2 bytes to the mutable data buffer. */ - (void)ykf_appendEntryWithTag:(UInt8)tag headerBytes:(NSArray *)headerBytes data:(NSData *)data; +/* + Appends the UInt8 value to the mutable data + */ +- (void)ykf_appendUInt8EntryWithTag:(UInt8)tag value:(UInt8)value; + +/* + Appends the UInt16 value to the mutable data after converting it to a big endian represetation (key representation). + */ +- (void)ykf_appendUInt16EntryWithTag:(UInt8)tag value:(UInt16)value; + /* Appends the UInt32 value to the mutable data after converting it to a big endian represetation (key representation). */ diff --git a/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.m b/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.m index c591fe34..5c64ffee 100644 --- a/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.m +++ b/YubiKit/YubiKit/Helpers/Additions/YKFNSMutableDataAdditions.m @@ -56,6 +56,23 @@ - (void)ykf_appendEntryWithTag:(UInt8)tag headerBytes:(NSArray *)headerBytes dat [self ykf_appendEntryWithTag:tag data:buffer]; } +- (void)ykf_appendUInt8EntryWithTag:(UInt8)tag value:(UInt8)value { + YKFParameterAssertReturn(tag > 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 97177ad6..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 @@ -81,7 +84,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() } @@ -95,15 +98,66 @@ 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 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 + 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 { @@ -113,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)} }