From 910be3673c556a61d99259f350c9f86462f8b86b Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Mon, 10 Jun 2024 12:35:08 +0200 Subject: [PATCH 1/5] Implemented move key and slot metadata for PIVSession. --- YubiKit/YubiKit.xcodeproj/project.pbxproj | 8 ++ .../Shared/Sessions/PIV/YKFPIVSession.h | 26 +++- .../Shared/Sessions/PIV/YKFPIVSession.m | 124 +++++++++++++----- .../Sessions/PIV/YKFPIVSessionFeatures.h | 1 + .../Sessions/PIV/YKFPIVSessionFeatures.m | 2 + .../Sessions/PIV/YKFPIVSlotMetadata+Private.h | 26 ++++ .../Shared/Sessions/PIV/YKFPIVSlotMetadata.h | 30 +++++ .../Shared/Sessions/PIV/YKFPIVSlotMetadata.m | 42 ++++++ YubiKit/YubiKit/YubiKit.h | 1 + YubiKitTests/Tests/PIVTests.swift | 61 +++++---- 10 files changed, 255 insertions(+), 66 deletions(-) create mode 100644 YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h create mode 100644 YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h create mode 100644 YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m 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/Sessions/PIV/YKFPIVSession.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h index d8450a5a..432091b6 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h @@ -56,10 +56,11 @@ typedef NS_ENUM(NSUInteger, YKFPIVFErrorCode) { YKFPIVFErrorCodeInvalidPin = 5, YKFPIVFErrorCodePinLocked = 6, YKFPIVFErrorCodeInvalidResponse = 7, - YKFPIVFErrorCodeAuthenticationFailed = 8 + YKFPIVFErrorCodeAuthenticationFailed = 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,15 @@ 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 moe 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 Writes an X.509 certificate to a slot on the YubiKey. /// @discussion This method requires authentication. /// @param certificate Certificate to write. @@ -374,6 +388,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..01aa72bc 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; @@ -261,6 +265,36 @@ - (void)attestKeyInSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionAttest }]; } +- (SecKeyRef)secKeyFromYubiKeyData:(NSData *)data keyType:(YKFPIVKeyType)type error:(NSError **)error { + NSArray *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); }]; } @@ -418,6 +425,22 @@ - (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:YKFPIVFErrorCodeUnsupportedOperation 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)putCertificate:(SecCertificateRef)certificate inSlot:(YKFPIVSlot)slot completion:(YKFPIVSessionGenericCompletionBlock)completion { [self putCertificate:certificate inSlot:slot compress:NO completion:completion]; } @@ -675,6 +698,41 @@ - (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:YKFPIVFErrorCodeUnsupportedOperation 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:YKFPIVFErrorCodeDataParseError 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."}]); @@ -695,7 +753,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..dbb18c18 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 keyse" 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..26fb7ca4 --- /dev/null +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h @@ -0,0 +1,26 @@ +// Copyright 2018-2021 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..dda0fe1e --- /dev/null +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h @@ -0,0 +1,30 @@ +// Copyright 2018-2021 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..6f6e63cf --- /dev/null +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m @@ -0,0 +1,42 @@ +// Copyright 2018-2021 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..41f42714 100644 --- a/YubiKitTests/Tests/PIVTests.swift +++ b/YubiKitTests/Tests/PIVTests.swift @@ -403,6 +403,35 @@ 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 testPutCompressedAndReadCertificate() throws { runYubiKitTest { connection, completion in connection.authenticatedPivTestSession { session in @@ -515,37 +544,7 @@ class PIVTests: XCTestCase { } } - 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(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(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(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(retries == 0) - print("✅ PIN retry count \(retries)") - completion() - } - } - } - } - } - } - } + func testGetPinAttempts() throws { runYubiKitTest { connection, completion in From be96b7e22e5cd22760829e35d9f31fc43da3f5a3 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Mon, 10 Jun 2024 12:56:40 +0200 Subject: [PATCH 2/5] Fixed type that lead to confusing error code naming for PIVSession. --- .../Connections/Shared/Errors/YKFPIVError.h | 8 ---- .../Connections/Shared/Errors/YKFPIVError.m | 5 ++- .../Shared/Sessions/PIV/YKFPIVSession.h | 18 ++++---- .../Shared/Sessions/PIV/YKFPIVSession.m | 42 +++++++++---------- YubiKitTests/Tests/PIVTests.swift | 41 +++++++++++++++--- 5 files changed, 69 insertions(+), 45 deletions(-) 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 432091b6..db87426f 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h @@ -48,15 +48,15 @@ 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 }; diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m index 01aa72bc..917746dd 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m @@ -137,7 +137,7 @@ + (void)sessionWithConnectionController:(nonnull id 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; } } @@ -681,7 +681,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; } @@ -700,7 +700,7 @@ - (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: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:slot data:[NSData data] type:YKFAPDUTypeShort]; @@ -728,14 +728,14 @@ - (void)getMetadataForSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionSlo 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:YKFPIVFErrorCodeDataParseError userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing data returned from YubiKey."}]); + 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]; diff --git a/YubiKitTests/Tests/PIVTests.swift b/YubiKitTests/Tests/PIVTests.swift index 41f42714..ffb2ac4d 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 { @@ -474,7 +474,6 @@ class PIVTests: XCTestCase { print("✅ authenticated") completion() } - } } } @@ -518,6 +517,38 @@ class PIVTests: XCTestCase { } } + 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 == 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 == 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 == 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 == YKFPIVErrorCode.pinLocked.rawValue) + XCTAssert(retries == 0) + print("✅ PIN retry count \(retries)") + completion() + } + } + } + } + } + } + } + func testAuthenticateWithWrongManagementKey() throws { runYubiKitTest { connection, completion in connection.pivTestSession { session in @@ -601,7 +632,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() } From 3dfb91a882bc602f3a55cb7b2a8a7caf0408a6c2 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Mon, 10 Jun 2024 13:14:14 +0200 Subject: [PATCH 3/5] Delete key in PIVSession. --- .../Shared/Sessions/PIV/YKFPIVSession.h | 5 ++++ .../Shared/Sessions/PIV/YKFPIVSession.m | 11 ++++++++ YubiKitTests/Tests/PIVTests.swift | 25 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h index db87426f..b7baeede 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h @@ -276,6 +276,11 @@ typedef void (^YKFPIVSessionManagementKeyMetadataCompletionBlock) /// 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. diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m index 917746dd..49e36701 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m @@ -440,6 +440,17 @@ - (void)moveKey:(YKFPIVSlot)sourceSlot destinationSlot:(YKFPIVSlot)destinationSl }]; } +- (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]; diff --git a/YubiKitTests/Tests/PIVTests.swift b/YubiKitTests/Tests/PIVTests.swift index ffb2ac4d..f54f16a3 100644 --- a/YubiKitTests/Tests/PIVTests.swift +++ b/YubiKitTests/Tests/PIVTests.swift @@ -432,6 +432,31 @@ class PIVTests: XCTestCase { } } + 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 From b6bdf528a4d5b3f4a87ca3f13b86ade1fdb979d3 Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Tue, 11 Jun 2024 16:36:07 +0200 Subject: [PATCH 4/5] Fixed spelling mistake. --- .../Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m index dbb18c18..6c6821d6 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSessionFeatures.m @@ -40,7 +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 keyse" versionString:@"5.7.0"]; + self.moveDelete = [[YKFFeature alloc] initWithName:@"Move and delete keys" versionString:@"5.7.0"]; } return self; } From b6c5b90e4de2422a9a123bd52948b26623ba5b1c Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Wed, 19 Jun 2024 09:08:04 +0200 Subject: [PATCH 5/5] Fixed typos and spelling errors. --- YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h | 2 +- .../Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h | 2 +- .../Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h | 2 +- .../Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h index b7baeede..264cb637 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h @@ -270,7 +270,7 @@ typedef void (^YKFPIVSessionManagementKeyMetadataCompletionBlock) /// @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 moe the key from. +/// @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. diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h index 26fb7ca4..777cc9ca 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata+Private.h @@ -1,4 +1,4 @@ -// Copyright 2018-2021 Yubico AB +// 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. diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h index dda0fe1e..44c6b659 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.h @@ -1,4 +1,4 @@ -// Copyright 2018-2021 Yubico AB +// 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. diff --git a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m index 6f6e63cf..09394618 100644 --- a/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m +++ b/YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSlotMetadata.m @@ -1,4 +1,4 @@ -// Copyright 2018-2021 Yubico AB +// 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.