diff --git a/YubiKit/YubiKit.xcodeproj/project.pbxproj b/YubiKit/YubiKit.xcodeproj/project.pbxproj index c7237cd4..312e2ddb 100644 --- a/YubiKit/YubiKit.xcodeproj/project.pbxproj +++ b/YubiKit/YubiKit.xcodeproj/project.pbxproj @@ -226,6 +226,7 @@ B4C9BBCC2A05547400FFDFD6 /* NSData+GZIP.m in Sources */ = {isa = PBXBuildFile; fileRef = B4C9BBC92A05547400FFDFD6 /* NSData+GZIP.m */; }; B4CFA9BE28AA4D0B0080813A /* YKFSmartCardConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B4CFA9BD28AA4D0B0080813A /* YKFSmartCardConnection.m */; }; B4CFA9C428ABB9BB0080813A /* YKFSmartCardConnectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = B4CFA9C328ABB9BB0080813A /* YKFSmartCardConnectionController.m */; }; + B4E1C3632C12F1140011F0F6 /* YKFPIVSlotMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = B4E1C3622C12F1140011F0F6 /* YKFPIVSlotMetadata.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -641,6 +642,9 @@ B4CFA9BF28AA95B70080813A /* YKFSmartCardConnection+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YKFSmartCardConnection+Private.h"; sourceTree = ""; }; B4CFA9C228ABB9920080813A /* YKFSmartCardConnectionController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YKFSmartCardConnectionController.h; sourceTree = ""; }; B4CFA9C328ABB9BB0080813A /* YKFSmartCardConnectionController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YKFSmartCardConnectionController.m; sourceTree = ""; }; + B4E1C3602C12EB110011F0F6 /* YKFPIVSlotMetadata.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YKFPIVSlotMetadata.h; sourceTree = ""; }; + B4E1C3612C12ED710011F0F6 /* YKFPIVSlotMetadata+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YKFPIVSlotMetadata+Private.h"; sourceTree = ""; }; + B4E1C3622C12F1140011F0F6 /* YKFPIVSlotMetadata.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YKFPIVSlotMetadata.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -685,6 +689,9 @@ 51ACC32825DC01DA0069214B /* YKFPIVSessionFeatures.m */, 51ACC34125E553910069214B /* YKFPIVManagementKeyType.h */, 51ACC33B25E553580069214B /* YKFPIVManagementKeyType.m */, + B4E1C3602C12EB110011F0F6 /* YKFPIVSlotMetadata.h */, + B4E1C3612C12ED710011F0F6 /* YKFPIVSlotMetadata+Private.h */, + B4E1C3622C12F1140011F0F6 /* YKFPIVSlotMetadata.m */, 5110D67425F8FD1500467680 /* YKFPIVManagementKeyMetadata.h */, 5110D68125F8FED500467680 /* YKFPIVManagementKeyMetadata+Private.h */, 5110D67525F8FD2F00467680 /* YKFPIVManagementKeyMetadata.m */, @@ -1558,6 +1565,7 @@ 5110D6992600D9C800467680 /* YKFPIVPadding.m in Sources */, 95B58B8B229C03AE00199F8E /* YKFAccessoryConnection+Debugging.m in Sources */, 95B0CAAD21EF53E1009C6A34 /* YKFFIDO2Error.m in Sources */, + B4E1C3632C12F1140011F0F6 /* YKFPIVSlotMetadata.m in Sources */, 95DD40A72099A8A400363FEE /* YKFAccessoryConnection.m in Sources */, 95DD409D2099A89600363FEE /* YKFU2FRegisterResponse.m in Sources */, B4CFA9BE28AA4D0B0080813A /* YKFSmartCardConnection.m in Sources */, diff --git a/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.h b/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.h index 94772be6..97c891c3 100644 --- a/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.h +++ b/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.h @@ -14,14 +14,6 @@ #import "YKFSessionError.h" -typedef NS_ENUM(NSUInteger, YKFPIVErrorCode) { - - /*! Unexpected reply from the YubiKey. - */ - YKFPIVErrorCodeBadResponse = 0x01, - YKFPIVErrorCodeBadKeyLength = 0x02 -}; - NS_ASSUME_NONNULL_BEGIN /*! diff --git a/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.m b/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.m index 750f4ed4..da2c2bc5 100644 --- a/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.m +++ b/YubiKit/YubiKit/Connections/Shared/Errors/YKFPIVError.m @@ -13,6 +13,7 @@ // limitations under the License. #import "YKFPIVError.h" +#import "YKFPIVSession.h" #import "YKFSessionError+Private.h" @implementation YKFPIVError @@ -20,7 +21,7 @@ @implementation YKFPIVError + (instancetype)errorWithCode:(NSUInteger)code { NSString *message; switch (code) { - case YKFPIVErrorCodeBadResponse: + case YKFPIVErrorCodeInvalidResponse: message = @"Bad response"; break; default: @@ -31,7 +32,7 @@ + (instancetype)errorWithCode:(NSUInteger)code { } + (instancetype)errorUnpackingTLVExpected:(NSUInteger)expected got:(NSUInteger)got { - return [[self alloc] initWithCode:YKFPIVErrorCodeBadResponse message:[[NSString alloc] initWithFormat:@"Exptected tag: %02lx, got %02lx", (unsigned long)expected, (unsigned long)got]]; + return [[self alloc] initWithCode:YKFPIVErrorCodeInvalidResponse message:[[NSString alloc] initWithFormat:@"Exptected tag: %02lx, got %02lx", (unsigned long)expected, (unsigned long)got]]; } @end diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h index d8450a5a..264cb637 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h @@ -48,18 +48,19 @@ typedef NS_ENUM(NSUInteger, YKFPIVSlot) { extern NSString* _Nonnull const YKFPIVFErrorDomain; /// PIV error codes. -typedef NS_ENUM(NSUInteger, YKFPIVFErrorCode) { - YKFPIVFErrorCodeInvalidCipherTextLength = 1, - YKFPIVFErrorCodeUnsupportedOperation = 2, - YKFPIVFErrorCodeDataParseError = 3, - YKFPIVFErrorCodeUnknownKeyType = 4, - YKFPIVFErrorCodeInvalidPin = 5, - YKFPIVFErrorCodePinLocked = 6, - YKFPIVFErrorCodeInvalidResponse = 7, - YKFPIVFErrorCodeAuthenticationFailed = 8 +typedef NS_ENUM(NSUInteger, YKFPIVErrorCode) { + YKFPIVErrorCodeInvalidCipherTextLength = 1, + YKFPIVErrorCodeUnsupportedOperation = 2, + YKFPIVErrorCodeDataParseError = 3, + YKFPIVErrorCodeUnknownKeyType = 4, + YKFPIVErrorCodeInvalidPin = 5, + YKFPIVErrorCodePinLocked = 6, + YKFPIVErrorCodeInvalidResponse = 7, + YKFPIVErrorCodeAuthenticationFailed = 8, + YKFPIVErrorCodeIllegalArgument = 9 }; -@class YKFPIVSessionFeatures, YKFPIVManagementKeyType, YKFPIVManagementKeyMetadata; +@class YKFPIVSessionFeatures, YKFPIVManagementKeyType, YKFPIVManagementKeyMetadata, YKFPIVSlotMetadata; NS_ASSUME_NONNULL_BEGIN @@ -140,6 +141,10 @@ typedef void (^YKFPIVSessionPinPukMetadataCompletionBlock) typedef void (^YKFPIVSessionPinAttemptsCompletionBlock) (int retriesRemaining, NSError* _Nullable error); + +typedef void (^YKFPIVSessionSlotMetadataCompletionBlock) + (YKFPIVSlotMetadata* _Nullable metaData, NSError* _Nullable error); + /// @abstract Response block for [getManagementKeyMetadata:completion:] which provides the management key metadata or an error. /// @param metaData The management key metadata. /// @param error An error object that indicates why the request failed, or nil if the request was successful. @@ -262,6 +267,20 @@ typedef void (^YKFPIVSessionManagementKeyMetadataCompletionBlock) - (void)putKey:(SecKeyRef)key inSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionPutKeyCompletionBlock)completion NS_SWIFT_NAME(putKey(_:inSlot:completion:)); +/// @abstract Move key from one slot to another. The source slot must not be the attestation slot and the +/// destination slot must be empty. This method requires authentication with the management key. +/// @discussion This method requires authentication. +/// @param sourceSlot Slot to move the key from. +/// @param destinationSlot Slot to move the key to. +/// @param completion The completion handler that gets called once the YubiKey has finished processing the +/// request. This handler is executed on a background queue. +- (void)moveKey:(YKFPIVSlot)sourceSlot destinationSlot:(YKFPIVSlot)destinationSlot completion:(nonnull YKFPIVSessionGenericCompletionBlock)completion; + +/// @abstract Delete key from slot. This method requires authentication with the management key. +/// +/// @param slot Slot to delete the key from. +- (void)deleteKeyInSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionGenericCompletionBlock)completion; + /// @abstract Writes an X.509 certificate to a slot on the YubiKey. /// @discussion This method requires authentication. /// @param certificate Certificate to write. @@ -374,6 +393,14 @@ typedef void (^YKFPIVSessionManagementKeyMetadataCompletionBlock) /// @note: This method is thread safe and can be invoked from any thread (main or a background thread). - (void)getSerialNumberWithCompletion:(nonnull YKFPIVSessionSerialNumberCompletionBlock)completion; +/// @abstract Reads metadata about the private key stored in a slot. +/// @param slot The slot to read metadata about. +/// @param completion The completion handler that gets called once the YubiKey has finished processing the request. +/// This handler is executed on a background queue. +/// @note This functionality requires support for feature metadata, available on YubiKey 5.3 or later. +/// @note: This method is thread safe and can be invoked from any thread (main or a background thread). +- (void)getMetadataForSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionSlotMetadataCompletionBlock)completion; + /// @abstract Reads metadata about the card management key. /// @param completion The completion handler that gets called once the YubiKey has finished processing the request. /// This handler is executed on a background queue. diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m index 0de30063..49e36701 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m @@ -29,6 +29,7 @@ #import "YKFAPDU+Private.h" #import "YKFPIVError.h" #import "YKFSessionError+Private.h" +#import "YKFPIVSlotMetadata+Private.h" #import "YKFPIVManagementKeyMetadata+Private.h" #import "YKFPIVPadding+Private.h" #import "TKTLVRecordAdditions+Private.h" @@ -56,12 +57,15 @@ static const NSUInteger YKFPIVInsSetPinPukAttempts = 0xfa; static const NSUInteger YKFPIVInsGenerateAsymetric = 0x47; static const NSUInteger YKFPIVInsAttest = 0xf9; +static const NSUInteger YKFPIVInsMoveKey = 0xf6; // Tags for parsing responses and preparing reqeusts static const NSUInteger YKFPIVTagMetadataIsDefault = 0x05; static const NSUInteger YKFPIVTagMetadataAlgorithm = 0x01; -static const NSUInteger YKFPIVTagMetadataTouchPolicy = 0x02; +static const NSUInteger YKFPIVTagMetadataPolicy = 0x02; +static const NSUInteger YKFPIVTagMetadataOrigin = 0x03; +static const NSUInteger YKFPIVTagMetadataPublicKey = 0x04; static const NSUInteger YKFPIVTagMetadataRetries = 0x06; static const NSUInteger YKFPIVTagDynAuth = 0x7c; static const NSUInteger YKFPIVTagAuthWitness = 0x80; @@ -133,7 +137,7 @@ + (void)sessionWithConnectionController:(nonnull id *records = [YKFTLVRecord sequenceOfRecordsFromData:data]; + SecKeyRef publicKey = nil; + CFErrorRef cfError = nil; + if (type == YKFPIVKeyTypeECCP256 || type == YKFPIVKeyTypeECCP384) { + NSData *eccKeyData = [records ykfTLVRecordWithTag:(UInt64)0x86].value; + CFDataRef cfDataRef = (__bridge CFDataRef)eccKeyData; + NSDictionary *attributes = @{(id)kSecAttrKeyType: (id)kSecAttrKeyTypeEC, + (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPublic}; + CFDictionaryRef attributesRef = (__bridge CFDictionaryRef)attributes; + publicKey = SecKeyCreateWithData(cfDataRef, attributesRef, &cfError); + } else if (type == YKFPIVKeyTypeRSA1024 || type == YKFPIVKeyTypeRSA2048 || type == YKFPIVKeyTypeRSA3072 || type == YKFPIVKeyTypeRSA4096) { + NSMutableData *modulusData = [NSMutableData dataWithBytes:&(UInt8 *){0x00} length:1]; + [modulusData appendData:[records ykfTLVRecordWithTag:(UInt64)0x81].value]; + NSData *exponentData = [records ykfTLVRecordWithTag:(UInt64)0x82].value; + NSMutableData *mutableData = [NSMutableData data]; + [mutableData appendData:[[YKFTLVRecord alloc] initWithTag:0x02 value:modulusData].data]; + [mutableData appendData:[[YKFTLVRecord alloc] initWithTag:0x02 value:exponentData].data]; + YKFTLVRecord *record = [[YKFTLVRecord alloc] initWithTag:0x30 value:mutableData]; + NSData *keyData = record.data; + NSDictionary *attributes = @{(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA, + (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPublic}; + CFDictionaryRef attributesRef = (__bridge CFDictionaryRef)attributes; + CFDataRef cfKeyDataRef = (__bridge CFDataRef)keyData; + publicKey = SecKeyCreateWithData(cfKeyDataRef, attributesRef, &cfError); + } + *error = (__bridge NSError *) cfError; + return publicKey; +} + - (void)generateKeyInSlot:(YKFPIVSlot)slot type:(YKFPIVKeyType)type pinPolicy:(YKFPIVPinPolicy)pinPolicy touchPolicy:(YKFPIVTouchPolicy)touchPolicy completion:(nonnull YKFPIVSessionReadKeyCompletionBlock)completion { NSError *error = [self checkKeySupport:type pinPolicy:pinPolicy touchPolicy:touchPolicy generateKey:YES]; if (error) { @@ -273,37 +307,10 @@ - (void)generateKeyInSlot:(YKFPIVSlot)slot type:(YKFPIVKeyType)type pinPolicy:(Y NSData *tlvsData = tlvsContainer.data; YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0 ins:YKFPIVInsGenerateAsymetric p1:0 p2:slot data:tlvsData type:YKFAPDUTypeExtended]; [self.smartCardInterface executeCommand:apdu timeout:120.0 completion:^(NSData * _Nullable data, NSError * _Nullable error) { - NSArray *records = [YKFTLVRecord sequenceOfRecordsFromData:[[YKFTLVRecord sequenceOfRecordsFromData:data] ykfTLVRecordWithTag:(UInt64)0x7F49].value]; - SecKeyRef publicKey = nil; - CFErrorRef cfError = nil; - if (type == YKFPIVKeyTypeECCP256 || type == YKFPIVKeyTypeECCP384) { - NSData *eccKeyData = [records ykfTLVRecordWithTag:(UInt64)0x86].value; - CFDataRef cfDataRef = (__bridge CFDataRef)eccKeyData; - NSDictionary *attributes = @{(id)kSecAttrKeyType: (id)kSecAttrKeyTypeEC, - (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPublic}; - CFDictionaryRef attributesRef = (__bridge CFDictionaryRef)attributes; - publicKey = SecKeyCreateWithData(cfDataRef, attributesRef, &cfError); - } else if (type == YKFPIVKeyTypeRSA1024 || type == YKFPIVKeyTypeRSA2048 || type == YKFPIVKeyTypeRSA3072 || type == YKFPIVKeyTypeRSA4096) { - NSMutableData *modulusData = [NSMutableData dataWithBytes:&(UInt8 *){0x00} length:1]; - [modulusData appendData:[records ykfTLVRecordWithTag:(UInt64)0x81].value]; - NSData *exponentData = [records ykfTLVRecordWithTag:(UInt64)0x82].value; - NSMutableData *mutableData = [NSMutableData data]; - [mutableData appendData:[[YKFTLVRecord alloc] initWithTag:0x02 value:modulusData].data]; - [mutableData appendData:[[YKFTLVRecord alloc] initWithTag:0x02 value:exponentData].data]; - YKFTLVRecord *record = [[YKFTLVRecord alloc] initWithTag:0x30 value:mutableData]; - NSData *keyData = record.data; - NSDictionary *attributes = @{(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA, - (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPublic}; - CFDictionaryRef attributesRef = (__bridge CFDictionaryRef)attributes; - CFDataRef cfKeyDataRef = (__bridge CFDataRef)keyData; - publicKey = SecKeyCreateWithData(cfKeyDataRef, attributesRef, &cfError); - } else { - [NSException raise:@"UnknownKeyType" format:@"Unknown key type."]; - - completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnknownKeyType userInfo:@{NSLocalizedDescriptionKey: @"Unknown key type."}]); - } - NSError *bridgedError = (__bridge NSError *) cfError; - completion(publicKey, bridgedError); + NSData *keyData = [[YKFTLVRecord sequenceOfRecordsFromData:data] ykfTLVRecordWithTag:(UInt64)0x7F49].value; + NSError *keyError; + SecKeyRef publicKey = [self secKeyFromYubiKeyData:keyData keyType:type error:&keyError]; + completion(publicKey, keyError); }]; } @@ -345,7 +352,7 @@ - (NSError * _Nullable)checkKeySupport:(YKFPIVKeyType)keyType pinPolicy:(YKFPIVP } if (errorMessage) { - return [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%@ not supported by this YubiKey.", errorMessage]}]; + return [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%@ not supported by this YubiKey.", errorMessage]}]; } return nil; @@ -397,7 +404,7 @@ - (void)putKey:(SecKeyRef)key inSlot:(YKFPIVSlot)slot pinPolicy:(YKFPIVPinPolicy break; } default: - completion(YKFPIVKeyTypeUnknown, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnknownKeyType userInfo:@{NSLocalizedDescriptionKey: @"Unknown key type."}]); + completion(YKFPIVKeyTypeUnknown, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnknownKeyType userInfo:@{NSLocalizedDescriptionKey: @"Unknown key type."}]); return; } @@ -418,6 +425,33 @@ - (void)putKey:(SecKeyRef)key inSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIV [self putKey:key inSlot:slot pinPolicy:YKFPIVPinPolicyDefault touchPolicy:YKFPIVTouchPolicyDefault completion:completion]; } +- (void)moveKey:(YKFPIVSlot)sourceSlot destinationSlot:(YKFPIVSlot)destinationSlot completion:(nonnull YKFPIVSessionGenericCompletionBlock)completion { + if (![self.features.moveDelete isSupportedBySession:self]) { + completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Move keys not supported by this YubiKey."}]); + return; + } + if (sourceSlot == YKFPIVSlotAttestation) { + completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeIllegalArgument userInfo:@{NSLocalizedDescriptionKey: @"Moving keys to the attestation slot is not allowed."}]); + return; + } + YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0 ins:YKFPIVInsMoveKey p1:destinationSlot p2:sourceSlot data:[NSData data] type:YKFAPDUTypeExtended]; + [self.smartCardInterface executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { + completion(error); + }]; +} + +- (void)deleteKeyInSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionGenericCompletionBlock)completion { + if (![self.features.moveDelete isSupportedBySession:self]) { + completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Delete keys not supported by this YubiKey."}]); + return; + } + YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0 ins:YKFPIVInsMoveKey p1:0xff p2:slot data:[NSData data] type:YKFAPDUTypeExtended]; + [self.smartCardInterface executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { + completion(error); + }]; +} + + - (void)putCertificate:(SecCertificateRef)certificate inSlot:(YKFPIVSlot)slot completion:(YKFPIVSessionGenericCompletionBlock)completion { [self putCertificate:certificate inSlot:slot compress:NO completion:completion]; } @@ -471,7 +505,7 @@ - (void)getCertificateInSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionR if (certificate != nil) { completion(certificate, nil); } else { - completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeDataParseError userInfo:@{NSLocalizedDescriptionKey: @"Failed to parse certificate."}]); + completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeDataParseError userInfo:@{NSLocalizedDescriptionKey: @"Failed to parse certificate."}]); } } }]; @@ -485,11 +519,11 @@ - (void)deleteCertificateInSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessi - (void)setManagementKey:(nonnull NSData *)managementKey type:(nonnull YKFPIVManagementKeyType *)type requiresTouch:(BOOL)requiresTouch completion:(nonnull YKFPIVSessionGenericCompletionBlock)completion { if (requiresTouch && ![self.features.usagePolicy isSupportedBySession:self]) { - completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"PIN/Touch policy not supported by this YubiKey."}]); + completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"PIN/Touch policy not supported by this YubiKey."}]); return; } if (type.name != YKFPIVManagementKeyTypeTripleDES && ![self.features.aesKey isSupportedBySession:self]) { - completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"AES management key not supported by this YubiKey."}]); + completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"AES management key not supported by this YubiKey."}]); return; } YKFTLVRecord *tlv = [[YKFTLVRecord alloc] initWithTag:YKFPIVSlotCardManagement value:managementKey]; @@ -506,7 +540,7 @@ - (void)setManagementKey:(nonnull NSData *)managementKey type:(nonnull YKFPIVMan - (void)authenticateWithManagementKey:(nonnull NSData *)managementKey type:(nonnull YKFPIVManagementKeyType *)keyType completion:(nonnull YKFPIVSessionGenericCompletionBlock)completion { if (keyType.keyLenght != managementKey.length) { - YKFPIVError *error = [[YKFPIVError alloc] initWithCode:YKFPIVErrorCodeBadKeyLength message:[NSString stringWithFormat: @"Magagement key must be %i bytes in length. Used key is %lu long.", keyType.keyLenght, (unsigned long)managementKey.length]]; + YKFPIVError *error = [[YKFPIVError alloc] initWithCode:YKFPIVErrorCodeInvalidCipherTextLength message:[NSString stringWithFormat: @"Magagement key must be %i bytes in length. Used key is %lu long.", keyType.keyLenght, (unsigned long)managementKey.length]]; completion(error); return; } @@ -561,7 +595,7 @@ - (void)authenticateWithManagementKey:(nonnull NSData *)managementKey type:(nonn NSData *encryptedData = encryptedRecord.value; NSData *expectedData = [challenge ykf_encryptDataWithAlgorithm:[keyType.name ykfCCAlgorithm] key:managementKey]; if (![encryptedData isEqual:expectedData]) { - completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeAuthenticationFailed userInfo:@{NSLocalizedDescriptionKey: @"Authentication failed."}]); + completion([[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeAuthenticationFailed userInfo:@{NSLocalizedDescriptionKey: @"Authentication failed."}]); return; } completion(nil); @@ -590,7 +624,7 @@ - (void)resetWithCompletion:(YKFPIVSessionGenericCompletionBlock)completion { - (void)getSerialNumberWithCompletion:(YKFPIVSessionSerialNumberCompletionBlock)completion { if (![self.features.serial isSupportedBySession:self]) { - completion(-1, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Read serial number not supported by this YubiKey."}]); + completion(-1, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Read serial number not supported by this YubiKey."}]); return; } @@ -598,7 +632,7 @@ - (void)getSerialNumberWithCompletion:(YKFPIVSessionSerialNumberCompletionBlock) [self.smartCardInterface executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { if (data != nil) { if ([data length] != 4) { - completion(-1, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeInvalidResponse userInfo:@{NSLocalizedDescriptionKey: @"Invalid response when reading serial number."}]); + completion(-1, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeInvalidResponse userInfo:@{NSLocalizedDescriptionKey: @"Invalid response when reading serial number."}]); return; } UInt32 serialNumber = CFSwapInt32BigToHost(*(UInt32*)([data bytes])); @@ -623,11 +657,11 @@ - (void)verifyPin:(nonnull NSString *)pin completion:(nonnull YKFPIVSessionVerif int retries = [self getRetriesFromStatusCode:(int)sessionError.code]; if (retries > 0) { currentPinAttempts = retries; - completion(currentPinAttempts, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeInvalidPin userInfo:@{NSLocalizedDescriptionKey: @"Invalid PIN code."}]); + completion(currentPinAttempts, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeInvalidPin userInfo:@{NSLocalizedDescriptionKey: @"Invalid PIN code."}]); return; } else if (retries == 0) { - completion(retries, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodePinLocked userInfo:@{NSLocalizedDescriptionKey: @"PIN code entry locked."}]); + completion(retries, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodePinLocked userInfo:@{NSLocalizedDescriptionKey: @"PIN code entry locked."}]); return; } } @@ -658,7 +692,7 @@ - (void)unblockPinWithPuk:(nonnull NSString *)puk newPin:(nonnull NSString *)new - (void)getPinPukMetadata:(UInt8)p2 completion:(nonnull YKFPIVSessionPinPukMetadataCompletionBlock)completion { YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0 ins:YKFPIVInsGetMetadata p1:0 p2:p2 data:[NSData data] type:YKFAPDUTypeShort]; if (![self.features.metadata isSupportedBySession:self]) { - completion(0, 0, 0, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Read metadata not supported by this YubiKey."}]); + completion(0, 0, 0, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Read metadata not supported by this YubiKey."}]); return; } @@ -675,9 +709,44 @@ - (void)getPinPukMetadata:(UInt8)p2 completion:(nonnull YKFPIVSessionPinPukMetad }]; } +- (void)getMetadataForSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionSlotMetadataCompletionBlock)completion { + if (![self.features.metadata isSupportedBySession:self]) { + completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Read metadata not supported by this YubiKey."}]); + return; + } + YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0 ins:YKFPIVInsGetMetadata p1:0 p2:slot data:[NSData data] type:YKFAPDUTypeShort]; + [self.smartCardInterface executeCommand:apdu completion:^(NSData * _Nullable data, NSError * _Nullable error) { + if (error != nil) { + completion(nil, error); + return; + } + NSArray *records = [YKFTLVRecord sequenceOfRecordsFromData:data]; + NSData *keyTypeData = [records ykfTLVRecordWithTag:YKFPIVTagMetadataAlgorithm].value; + NSData *policyData = [records ykfTLVRecordWithTag:YKFPIVTagMetadataPolicy].value; + NSData *originData = [records ykfTLVRecordWithTag:YKFPIVTagMetadataOrigin].value; + NSData *publicKeyData = [records ykfTLVRecordWithTag:YKFPIVTagMetadataPublicKey].value; + + if (keyTypeData && policyData && originData && publicKeyData) { + YKFPIVKeyType keyType = [keyTypeData ykf_integerValue]; + YKFPIVPinPolicy pinPolicy = ((UInt8 *)policyData.bytes)[0]; + YKFPIVTouchPolicy touchPolicy = ((UInt8 *)policyData.bytes)[1]; + bool origin = [originData ykf_integerValue]; + NSError *keyError; + SecKeyRef publicKey = [self secKeyFromYubiKeyData:publicKeyData keyType:keyType error:&error]; + if (error) { + completion(nil, keyError); + } + YKFPIVSlotMetadata *metadata = [[YKFPIVSlotMetadata alloc] initWithKeyType:keyType publicKey:publicKey pinPolicy:pinPolicy touchPolicy:touchPolicy generated:origin]; + completion(metadata, nil); + } else { + completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeDataParseError userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing data returned from YubiKey."}]); + } + }]; +} + - (void)getManagementKeyMetadataWithCompletion:(nonnull YKFPIVSessionManagementKeyMetadataCompletionBlock)completion { if (![self.features.metadata isSupportedBySession:self]) { - completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Read metadata not supported by this YubiKey."}]); + completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: @"Read metadata not supported by this YubiKey."}]); return; } YKFAPDU *apdu = [[YKFAPDU alloc] initWithCla:0 ins:YKFPIVInsGetMetadata p1:0 p2:YKFPIVSlotCardManagement data:[NSData data] type:YKFAPDUTypeShort]; @@ -695,7 +764,7 @@ - (void)getManagementKeyMetadataWithCompletion:(nonnull YKFPIVSessionManagementK keyType = [YKFPIVManagementKeyType TripleDES]; } bool isDefault = ((UInt8 *)[records ykfTLVRecordWithTag:YKFPIVTagMetadataIsDefault].value.bytes)[0] != 0; - YKFPIVTouchPolicy touchPolicy = ((UInt8 *)[records ykfTLVRecordWithTag:YKFPIVTagMetadataTouchPolicy].value.bytes)[1]; + YKFPIVTouchPolicy touchPolicy = ((UInt8 *)[records ykfTLVRecordWithTag:YKFPIVTagMetadataPolicy].value.bytes)[1]; YKFPIVManagementKeyMetadata *metaData = [[YKFPIVManagementKeyMetadata alloc] initWithKeyType:keyType touchPolicy:touchPolicy isDefault:isDefault]; completion(metaData, nil); diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.h index 66e06878..a3340159 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.h @@ -25,6 +25,7 @@ @property (nonatomic, readonly) YKFFeature * _Nonnull attestation; @property (nonatomic, readonly) YKFFeature * _Nonnull p384; @property (nonatomic, readonly) YKFFeature * _Nonnull touchCached; + @property (nonatomic, readonly) YKFFeature * _Nonnull moveDelete; @end #endif /* YKFPIVSessionFeatures_h */ diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m index 1051a9e2..6c6821d6 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m @@ -25,6 +25,7 @@ @interface YKFPIVSessionFeatures() @property (nonatomic, readwrite) YKFFeature * _Nonnull p384; @property (nonatomic, readwrite) YKFFeature * _Nonnull touchCached; @property (nonatomic, readwrite) YKFFeature * _Nonnull rsaGeneration; + @property (nonatomic, readwrite) YKFFeature * _Nonnull moveDelete; @end @implementation YKFPIVSessionFeatures @@ -39,6 +40,7 @@ - (instancetype)init { self.attestation = [[YKFFeature alloc] initWithName:@"Attestation" versionString:@"4.3.0"]; self.p384 = [[YKFFeature alloc] initWithName:@"Curve P384" versionString:@"4.0.0"]; self.touchCached = [[YKFFeature alloc] initWithName:@"Cached touch policy" versionString:@"4.3.0"]; + self.moveDelete = [[YKFFeature alloc] initWithName:@"Move and delete keys" versionString:@"5.7.0"]; } return self; } diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h new file mode 100644 index 00000000..777cc9ca --- /dev/null +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h @@ -0,0 +1,26 @@ +// Copyright 2018-2024 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef Header_h +#define Header_h + +#import "YKFPIVSlotMetadata.h" + +@interface YKFPIVSlotMetadata() + +- (instancetype)initWithKeyType:(YKFPIVKeyType)keyType publicKey:(SecKeyRef)publicKey pinPolicy:(YKFPIVPinPolicy)pinPolicy touchPolicy:(YKFPIVTouchPolicy)touchPolicy generated:(bool)generated NS_DESIGNATED_INITIALIZER; + +@end + +#endif /* Header_h */ diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h new file mode 100644 index 00000000..44c6b659 --- /dev/null +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h @@ -0,0 +1,30 @@ +// Copyright 2018-2024 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef YKFPIVSlotMetadata_h +#define YKFPIVSlotMetadata_h + +#import "YKFPIVSession.h" + +@interface YKFPIVSlotMetadata : NSObject + +@property (nonatomic, readonly) YKFPIVKeyType keyType; +@property (nonatomic, readonly) YKFPIVPinPolicy pinPolicy; +@property (nonatomic, readonly) YKFPIVTouchPolicy touchPolicy; +@property (nonatomic, readonly) SecKeyRef publicKey; +@property (nonatomic, readonly) bool generated; + +@end + +#endif /* YKFPIVSlotMetadata_h */ diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m new file mode 100644 index 00000000..09394618 --- /dev/null +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m @@ -0,0 +1,42 @@ +// Copyright 2018-2024 Yubico AB +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import "YKFPIVSlotMetadata.h" + +@interface YKFPIVSlotMetadata() + +@property (nonatomic, readwrite) YKFPIVKeyType keyType; +@property (nonatomic, readwrite) YKFPIVPinPolicy pinPolicy; +@property (nonatomic, readwrite) YKFPIVTouchPolicy touchPolicy; +@property (nonatomic, readwrite) SecKeyRef publicKey; +@property (nonatomic, readwrite) bool generated; + +@end + +@implementation YKFPIVSlotMetadata + +- (instancetype)initWithKeyType:(YKFPIVKeyType)keyType publicKey:(SecKeyRef)publicKey pinPolicy:(YKFPIVPinPolicy)pinPolicy touchPolicy:(YKFPIVTouchPolicy)touchPolicy generated:(bool)generated { + self = [super init]; + if (self) { + self.keyType = keyType; + self.publicKey = publicKey; + self.pinPolicy = pinPolicy; + self.touchPolicy = touchPolicy; + self.generated = generated; + } + return self; +}; + +@end diff --git a/YubiKit/YubiKit/YubiKit.h b/YubiKit/YubiKit/YubiKit.h index ca639319..7c5149c6 100644 --- a/YubiKit/YubiKit/YubiKit.h +++ b/YubiKit/YubiKit/YubiKit.h @@ -56,6 +56,7 @@ #import "YKFManagementDeviceInfo.h" #import "YKFManagementInterfaceConfiguration.h" #import "YKFPIVKeyType.h" +#import "YKFPIVSlotMetadata.h" #import "YKFChallengeResponseSession.h" #import "YKFManagementSession.h" diff --git a/YubiKitTests/Tests/PIVTests.swift b/YubiKitTests/Tests/PIVTests.swift index d1668456..f54f16a3 100644 --- a/YubiKitTests/Tests/PIVTests.swift +++ b/YubiKitTests/Tests/PIVTests.swift @@ -73,7 +73,7 @@ class PIVTests: XCTestCase { return } session.verifyPin("123456") { retries, error in - session.decryptWithKey(in: .keyManagement, algorithm: SecKeyAlgorithm.rsaEncryptionPKCS1, encrypted: encryptedData as Data) { data, error in + session.decryptWithKey(in: .keyManagement, algorithm: SecKeyAlgorithm.rsaEncryptionPKCS1, encrypted: encryptedData as Data) { data, error in guard let data = data else { XCTFail("🔴 Failed to decrypt key: \(error!)"); completion(); return } let decrypted = String(data:data, encoding: .utf8) XCTAssert(decrypted == "Hello World!", "🔴 Got: '\(String(describing: decrypted))', exptected 'Hello World!'.") @@ -98,7 +98,7 @@ class PIVTests: XCTestCase { return } session.verifyPin("123456") { retries, error in - session.decryptWithKey(in: .keyManagement, algorithm: SecKeyAlgorithm.rsaEncryptionOAEPSHA224, encrypted: encryptedData as Data) { data, error in + session.decryptWithKey(in: .keyManagement, algorithm: SecKeyAlgorithm.rsaEncryptionOAEPSHA224, encrypted: encryptedData as Data) { data, error in guard let data = data else { XCTFail("🔴 Failed to decrypt key: \(error!)"); completion(); return } let decrypted = String(data:data, encoding: .utf8) XCTAssert(decrypted == "Hello World!", "🔴 Got: '\(String(describing: decrypted))', exptected 'Hello World!'.") @@ -384,7 +384,7 @@ class PIVTests: XCTestCase { } } } - + let certificate = SecCertificateCreateWithData(nil, Data(base64Encoded: "MIIBKzCB0qADAgECAhQTuU25u6oazORvKfTleabdQaDUGzAKBggqhkjOPQQDAjAWMRQwEgYDVQQDDAthbW9zLmJ1cnRvbjAeFw0yMTAzMTUxMzU5MjVaFw0yODA1MTcwMDAwMDBaMBYxFDASBgNVBAMMC2Ftb3MuYnVydG9uMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEofwN6S+atSZmzeLK7aSI+mJJwxh0oUBiCOngHLeToYeanrTGvCZQ2AK/R9esnqSxMyBUDp91UO4F6U4c6RTooTAKBggqhkjOPQQDAgNIADBFAiAnj/KUSpW7l5wnenQEbwWudK/7q3WtyrqdB0H1xc258wIhALDLImzu3S+0TT2/ggM95LLWE4Llfa2RQM71bnW6zqqn")! as CFData)! func testPutAndReadCertificate() throws { @@ -403,6 +403,60 @@ class PIVTests: XCTestCase { } } + func testMoveKey() throws { + runYubiKitTest { connection, completion in + connection.authenticatedPivTestSession { session in + session.putCertificate(self.certificate, inSlot: .authentication) { error in + XCTAssertNil(error) + session.putCertificate(self.certificate, inSlot: .signature) { error in + XCTAssertNil(error) + session.generateKey(in: .authentication, type: .RSA1024) { secKey, error in + XCTAssertNil(error) + XCTAssertNotNil(secKey) + session.getMetadataFor(.authentication) { metadata, error in + XCTAssertNil(error) + XCTAssertNotNil(metadata?.publicKey) + session.moveKey(.authentication, destinationSlot: .signature) { error in + XCTAssertNil(error) + session.getMetadataFor(.signature) { metadata, error in + XCTAssertNil(error) + XCTAssertNotNil(metadata?.publicKey) + completion() + } + } + } + } + } + } + } + } + } + + func testDeleteKey() throws { + runYubiKitTest { connection, completion in + connection.authenticatedPivTestSession { session in + session.putCertificate(self.certificate, inSlot: .authentication) { error in + XCTAssertNil(error) + session.generateKey(in: .authentication, type: .RSA1024) { secKey, error in + XCTAssertNil(error) + XCTAssertNotNil(secKey) + session.getMetadataFor(.authentication) { metadata, error in + XCTAssertNil(error) + XCTAssertNotNil(metadata?.publicKey) + session.deleteKey(in: .authentication) { error in + XCTAssertNil(error) + session.getMetadataFor(.authentication) { metadata, error in + XCTAssertNotNil(error) + completion() + } + } + } + } + } + } + } + } + func testPutCompressedAndReadCertificate() throws { runYubiKitTest { connection, completion in connection.authenticatedPivTestSession { session in @@ -445,7 +499,6 @@ class PIVTests: XCTestCase { print("✅ authenticated") completion() } - } } } @@ -489,53 +542,27 @@ class PIVTests: XCTestCase { } } - func testAuthenticateWithWrongManagementKey() throws { - runYubiKitTest { connection, completion in - connection.pivTestSession { session in - let managementKey = Data([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]) - session.authenticate(withManagementKey: managementKey, type: .tripleDES()) { error in - guard let error = error as NSError? else { XCTFail("🔴 Expected an error but got none"); completion(); return } - XCTAssert(error.code == 0x6982) - print("✅ got expected error: \(error)") - completion() - } - } - } - } - - func testVerifyPIN() throws { - runYubiKitTest { connection, completion in - connection.pivTestSession { session in - session.verifyPin("123456") { retries, error in - guard error == nil else { XCTFail("\(error!)"); completion(); return } - print("✅ PIN verified \(retries) left") - completion() - } - } - } - } - func testVerifyPINRetryCount() throws { runYubiKitTest { connection, completion in connection.pivTestSession { session in session.verifyPin("333333") { retries, error in guard let error = error else { XCTFail("Error was not nil"); return } - XCTAssert((error as NSError).code == YKFPIVFErrorCode.invalidPin.rawValue) + XCTAssert((error as NSError).code == YKFPIVErrorCode.invalidPin.rawValue) XCTAssert(retries == 2) print("✅ PIN retry count \(retries)") session.verifyPin("111111") { retries, error in guard let error = error else { XCTFail("Error was not nil"); return } - XCTAssert((error as NSError).code == YKFPIVFErrorCode.invalidPin.rawValue) + XCTAssert((error as NSError).code == YKFPIVErrorCode.invalidPin.rawValue) XCTAssert(retries == 1) print("✅ PIN retry count \(retries)") session.verifyPin("444444") { retries, error in guard let error = error else { XCTFail("Error was not nil"); return } - XCTAssert((error as NSError).code == YKFPIVFErrorCode.pinLocked.rawValue) + XCTAssert((error as NSError).code == YKFPIVErrorCode.pinLocked.rawValue) XCTAssert(retries == 0) print("✅ PIN retry count \(retries)") session.verifyPin("111111") { retries, error in guard let error = error else { XCTFail("Error was not nil"); return } - XCTAssert((error as NSError).code == YKFPIVFErrorCode.pinLocked.rawValue) + XCTAssert((error as NSError).code == YKFPIVErrorCode.pinLocked.rawValue) XCTAssert(retries == 0) print("✅ PIN retry count \(retries)") completion() @@ -546,6 +573,34 @@ class PIVTests: XCTestCase { } } } + + func testAuthenticateWithWrongManagementKey() throws { + runYubiKitTest { connection, completion in + connection.pivTestSession { session in + let managementKey = Data([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]) + session.authenticate(withManagementKey: managementKey, type: .tripleDES()) { error in + guard let error = error as NSError? else { XCTFail("🔴 Expected an error but got none"); completion(); return } + XCTAssert(error.code == 0x6982) + print("✅ got expected error: \(error)") + completion() + } + } + } + } + + func testVerifyPIN() throws { + runYubiKitTest { connection, completion in + connection.pivTestSession { session in + session.verifyPin("123456") { retries, error in + guard error == nil else { XCTFail("\(error!)"); completion(); return } + print("✅ PIN verified \(retries) left") + completion() + } + } + } + } + + func testGetPinAttempts() throws { runYubiKitTest { connection, completion in @@ -602,7 +657,7 @@ class PIVTests: XCTestCase { connection.pivTestSession { session in XCTAssertNotNil(session.version) XCTAssert(session.version.major == 5) - XCTAssert(session.version.minor == 2 || session.version.minor == 3 || session.version.minor == 4) + XCTAssert(session.version.minor == 2 || session.version.minor == 3 || session.version.minor == 4 || session.version.minor == 7) print("✅ Got version: \(session.version.major).\(session.version.minor).\(session.version.micro)") completion() }