Skip to content

Commit

Permalink
Implemented move key and slot metadata for PIVSession.
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed Jun 10, 2024
1 parent 2c29a51 commit 910be36
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 66 deletions.
8 changes: 8 additions & 0 deletions YubiKit/YubiKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -641,6 +642,9 @@
B4CFA9BF28AA95B70080813A /* YKFSmartCardConnection+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YKFSmartCardConnection+Private.h"; sourceTree = "<group>"; };
B4CFA9C228ABB9920080813A /* YKFSmartCardConnectionController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YKFSmartCardConnectionController.h; sourceTree = "<group>"; };
B4CFA9C328ABB9BB0080813A /* YKFSmartCardConnectionController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YKFSmartCardConnectionController.m; sourceTree = "<group>"; };
B4E1C3602C12EB110011F0F6 /* YKFPIVSlotMetadata.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YKFPIVSlotMetadata.h; sourceTree = "<group>"; };
B4E1C3612C12ED710011F0F6 /* YKFPIVSlotMetadata+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YKFPIVSlotMetadata+Private.h"; sourceTree = "<group>"; };
B4E1C3622C12F1140011F0F6 /* YKFPIVSlotMetadata.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YKFPIVSlotMetadata.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
26 changes: 24 additions & 2 deletions YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
124 changes: 91 additions & 33 deletions YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -261,6 +265,36 @@ - (void)attestKeyInSlot:(YKFPIVSlot)slot completion:(nonnull YKFPIVSessionAttest
}];
}

- (SecKeyRef)secKeyFromYubiKeyData:(NSData *)data keyType:(YKFPIVKeyType)type error:(NSError **)error {
NSArray<YKFTLVRecord*> *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) {
Expand All @@ -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<YKFTLVRecord*> *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);
}];
}

Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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<YKFTLVRecord*> *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."}]);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
Loading

0 comments on commit 910be36

Please sign in to comment.