PIVToken/Token.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Implements PIV token core |
*/ |
#import <os/log.h> |
#import <Foundation/Foundation.h> |
#import <CryptoTokenKit/CryptoTokenKit.h> |
#import "Token.h" |
#import "NSData_Zip.h" |
@implementation NSData(hexString) |
- (NSString *)hexString { |
NSUInteger capacity = self.length * 2; |
NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:capacity]; |
const unsigned char *dataBuffer = self.bytes; |
for (NSInteger i = 0; i < self.length; i++) { |
[stringBuffer appendFormat:@"%02lX", (unsigned long)dataBuffer[i]]; |
} |
return stringBuffer; |
} |
@end |
@implementation PIVTokenKeychainKey |
- (instancetype)initWithCertificate:(SecCertificateRef)certificateRef objectID:(TKTokenObjectID)objectID certificateID:(TKTokenObjectID)certificateID alwaysAuthenticate:(BOOL)alwaysAuthenticate { |
if (self = [super initWithCertificate:certificateRef objectID:objectID]) { |
_certificateID = certificateID; |
_alwaysAuthenticate = alwaysAuthenticate; |
} |
return self; |
} |
- (UInt8)keyID { |
return [self.objectID unsignedCharValue]; |
} |
- (UInt8)algID { |
//SP 800-78-4 Table 6-2 and 6-3 |
if ([self.keyType isEqual:(id)kSecAttrKeyTypeECSECPrimeRandom]) { |
switch (self.keySizeInBits) { |
case 256: |
return 0x11; //EC 256 |
case 384: |
return 0x14; //EC 384 |
} |
} else if ([self.keyType isEqual:(id)kSecAttrKeyTypeRSA]) { |
switch (self.keySizeInBits) { |
case 1024: |
return 0x06; //RSA 1024 |
case 2048: |
return 0x07; //RSA 2048 |
} |
} |
return 0; |
} |
@end |
@implementation TKTokenKeychainItem(PIVDataFormat) |
- (void)setName:(NSString *)name { |
if (self.label != nil) { |
self.label = [NSString stringWithFormat:@"%@ (%@)", name, self.label]; |
} else { |
self.label = name; |
} |
} |
@end |
@implementation TKSmartCard(PIVDataFormat) |
- (TKTLVRecord *)sendIns:(UInt8)ins p1:(UInt8)p1 p2:(UInt8)p2 request:(TKTLVRecord *)request expectedTag:(TKTLVTag)expectedTag sw:(UInt16 *)sw error:(NSError * _Nullable __autoreleasing *)error { |
*sw = 0; |
NSData *replyData = [self sendIns:ins p1:p1 p2:p2 data:request.data le:@0 sw:sw error:error]; |
if (replyData.length == 0) { |
if (error != nil && replyData != nil && (*sw == 0x9000 || *sw == 0x6a82 || *sw == 0x6a80)) { |
*error = [NSError errorWithDomain:TKErrorDomain code:TKErrorCodeObjectNotFound userInfo:nil]; |
} |
return nil; |
} |
TKTLVRecord *response = [TKBERTLVRecord recordFromData:replyData]; |
if (response.tag != expectedTag) { |
os_log_error(OS_LOG_DEFAULT, "expecting response with tag 0x%x, got %@", (unsigned)expectedTag, response); |
if (error != nil) { |
*error = [NSError errorWithDomain:TKErrorDomain code:TKErrorCodeCorruptedData userInfo:nil]; |
} |
return nil; |
} |
return response; |
} |
- (nullable NSArray<TKTLVRecord *> *)recordsOfObject:(TKTokenObjectID)objectID error:(NSError **)error { |
if (![objectID isKindOfClass:NSData.class]) { |
if (error != nil) { |
*error = [NSError errorWithDomain:TKErrorDomain code:TKErrorCodeObjectNotFound userInfo:nil]; |
} |
return nil; |
} |
os_log_debug(OS_LOG_DEFAULT, "reading card object %@", objectID); |
TKTLVRecord *request = [[TKBERTLVRecord alloc] initWithTag:0x5c value:(NSData *)objectID]; |
UInt16 sw; |
TKTLVRecord *response = [self sendIns:0xcb p1:0x3f p2:0xff request:request expectedTag:0x53 sw:&sw error:error]; |
if (response == nil) { |
return nil; |
} |
NSArray<TKTLVRecord *> *records = [TKBERTLVRecord sequenceOfRecordsFromData:response.value]; |
if (records == nil) { |
os_log_error(OS_LOG_DEFAULT, "read data object has incorrect structure"); |
if (error != nil) { |
*error = [NSError errorWithDomain:TKErrorDomain code:TKErrorCodeCorruptedData userInfo:nil]; |
} |
} |
return records; |
} |
@end |
@implementation PIVToken |
- (nullable NSData *)dataOfCertificate:(TKTokenObjectID)certificateObjectID smartCard:(TKSmartCard *)smartCard error:(NSError * _Nullable __autoreleasing *)error { |
// Read certificate records from the card. |
NSArray<TKTLVRecord *> *certificateRecords = [smartCard recordsOfObject:certificateObjectID error:error]; |
if (certificateRecords == nil) { |
return nil; |
} |
// Process certificate records, extract data and info field. |
__block NSData *certificateData; |
__block BOOL compressed = NO; |
[certificateRecords enumerateObjectsUsingBlock:^(TKTLVRecord * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { |
if (obj.tag == 0x70) { |
certificateData = obj.value; |
} else if (obj.tag == 0x71 && obj.value.length > 0) { |
UInt8 info = *(const UInt8 *)obj.value.bytes; |
if ((info & 0x01) != 0) { |
compressed = YES; |
} |
} |
}]; |
if (certificateData == nil) { |
if (error != nil) { |
*error = [NSError errorWithDomain:TKErrorDomain code:TKErrorCodeObjectNotFound userInfo:nil]; |
} |
return nil; |
} |
return compressed ? [certificateData inflate] : certificateData; |
} |
- (BOOL)populateIdentityFromSmartCard:(TKSmartCard *)smartCard into:(NSMutableArray<TKTokenKeychainItem *> *)items certificateTag:(TKTLVTag)certificateTag name:(NSString *)certificateName keyTag:(TKTLVTag)keyTag name:(NSString *)keyName sign:(BOOL)sign keyManagement:(BOOL)keyManagement alwaysAuthenticate:(BOOL)alwaysAuthenticate error:(NSError **)error { |
// Read certificate data. |
TKTokenObjectID certificateID = [TKBERTLVRecord dataForTag:certificateTag]; |
NSData *certificateData = [self dataOfCertificate:certificateID smartCard:smartCard error:error]; |
if (certificateData == nil) { |
// If certificate cannot be found, just silently skip the operation, otherwise report an error. |
return (error != nil && [(*error).domain isEqual:TKErrorDomain] && (*error).code == TKErrorCodeObjectNotFound); |
} |
// Create certificate item. |
id certificate = CFBridgingRelease(SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certificateData)); |
if (certificate == NULL) { |
if (error != nil) { |
*error = [NSError errorWithDomain:TKErrorDomain code:TKErrorCodeCorruptedData userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"CORRUPTED_CERT", nil)}]; |
} |
return NO; |
} |
TKTokenKeychainCertificate *certificateItem = [[TKTokenKeychainCertificate alloc] initWithCertificate:(__bridge SecCertificateRef)certificate objectID:certificateID]; |
if (certificateItem == nil) { |
return NO; |
} |
[certificateItem setName:certificateName]; |
// Create key item. |
TKTokenKeychainKey *keyItem = [[PIVTokenKeychainKey alloc] initWithCertificate:(__bridge SecCertificateRef)certificate objectID:@(keyTag) certificateID:certificateItem.objectID alwaysAuthenticate:alwaysAuthenticate]; |
if (keyItem == nil) { |
return NO; |
} |
[keyItem setName:keyName]; |
NSMutableDictionary<NSNumber *, TKTokenOperationConstraint> *constraints = [NSMutableDictionary dictionary]; |
keyItem.canSign = sign; |
keyItem.suitableForLogin = sign; |
TKTokenOperationConstraint constraint = alwaysAuthenticate ? PIVConstraintPINAlways : PIVConstraintPIN; |
if (sign) { |
constraints[@(TKTokenOperationSignData)] = constraint; |
} |
if ([keyItem.keyType isEqual:(id)kSecAttrKeyTypeRSA]) { |
keyItem.canDecrypt = keyManagement; |
if (keyManagement) { |
constraints[@(TKTokenOperationDecryptData)] = constraint; |
} |
} else if ([keyItem.keyType isEqual:(id)kSecAttrKeyTypeECSECPrimeRandom]) { |
keyItem.canPerformKeyExchange = keyManagement; |
if (keyManagement) { |
constraints[@(TKTokenOperationPerformKeyExchange)] = constraint; |
} |
} |
keyItem.constraints = constraints; |
[items addObject:certificateItem]; |
[items addObject:keyItem]; |
return YES; |
} |
- (nullable instancetype)initWithSmartCard:(TKSmartCard *)smartCard AID:(nullable NSData *)AID PIVDriver:(PIVTokenDriver *)tokenDriver error:(NSError **)error { |
// Read and parse Card Holder Unique Identifier. |
NSArray<TKTLVRecord *> *chuid = [smartCard recordsOfObject:[TKBERTLVRecord dataForTag:0x5fc102] error:error]; |
if (chuid == nil) { |
os_log_error(OS_LOG_DEFAULT, "failed to read CHUID record (%@)", error ? *error : nil); |
return nil; |
} |
// Find Card GUID in CHUID. |
__block NSString *instanceID; |
[chuid enumerateObjectsUsingBlock:^(TKTLVRecord * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { |
if (obj.tag == 0x34) { |
instanceID = [obj.value hexString]; |
*stop = YES; |
} |
}]; |
if (instanceID == nil || instanceID.length == 0) { |
if (error) { |
*error = [NSError errorWithDomain:TKErrorDomain code:TKErrorCodeObjectNotFound userInfo:nil]; |
os_log_error(OS_LOG_DEFAULT, "CHUID record does not contain Card GUID"); |
} |
return nil; |
} |
if (self = [super initWithSmartCard:smartCard AID:AID instanceID:instanceID tokenDriver:tokenDriver]) { |
// Find out how many on-card key history objects are present, use Key History object. |
__block NSInteger onCardKeyHistoryCount = 0; |
NSArray<TKTLVRecord *> *keyHistory = [smartCard recordsOfObject:[TKBERTLVRecord dataForTag:0x5fc10c] error:error]; |
if (keyHistory != nil) { |
[keyHistory enumerateObjectsUsingBlock:^(TKTLVRecord * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { |
if (obj.tag == 0xc1 && obj.value.length > 0) { |
onCardKeyHistoryCount = *(const UInt8 *)obj.value.bytes; |
*stop = YES; |
} |
}]; |
} else if (error != nil && (*error).code != TKErrorCodeObjectNotFound) { |
return nil; |
} |
// Prepare array with keychain items representing on card objects. |
NSMutableArray<TKTokenKeychainItem *> *items = [NSMutableArray arrayWithCapacity:4 + onCardKeyHistoryCount]; |
if (![self populateIdentityFromSmartCard:smartCard into:items certificateTag:0x5fc105 name:NSLocalizedString(@"PIV_AUTH_CERT", nil) keyTag:0x9a name:NSLocalizedString(@"PIV_AUTH_KEY", nil) sign:YES keyManagement:NO alwaysAuthenticate:NO error:error] || |
![self populateIdentityFromSmartCard:smartCard into:items certificateTag:0x5fc101 name:NSLocalizedString(@"CARD_AUTH_CERT", nil) keyTag:0x9e name:NSLocalizedString(@"CARD_AUTH_KEY", nil) sign:YES keyManagement:NO alwaysAuthenticate:NO error:error] || |
![self populateIdentityFromSmartCard:smartCard into:items certificateTag:0x5fc10a name:NSLocalizedString(@"DIG_SIG_CERT", nil) keyTag:0x9c name:NSLocalizedString(@"DIG_SIG_KEY", nil) sign:YES keyManagement:NO alwaysAuthenticate:YES error:error] || |
![self populateIdentityFromSmartCard:smartCard into:items certificateTag:0x5fc10b name:NSLocalizedString(@"KEY_MGMT_CERT", nil) keyTag:0x9d name:NSLocalizedString(@"KEY_MGMT_KEY", nil) sign:NO keyManagement:YES alwaysAuthenticate:NO error:error]) { |
return nil; |
} |
for (NSInteger i = 0; i < onCardKeyHistoryCount; i++) { |
NSString *certificateName = [NSString stringWithFormat:NSLocalizedString(@"RET_KEY_MGMT_CERT", nil), (long)i + 1]; |
NSString *keyName = [NSString stringWithFormat:NSLocalizedString(@"RET_KEY_MGMT_KEY", nil), (long)i + 1]; |
if (![self populateIdentityFromSmartCard:smartCard into:items certificateTag:0x5fc10d + i name:certificateName keyTag:0x82 + i name:keyName sign:NO keyManagement:YES alwaysAuthenticate:NO error:error]) { |
return nil; |
} |
} |
// Populate keychain state with keys. |
[self.keychainContents fillWithItems:items]; |
} |
return self; |
} |
- (TKTokenSession *)token:(TKToken *)token createSessionWithError:(NSError * _Nullable __autoreleasing *)error { |
return [[PIVTokenSession alloc] initWithToken:self]; |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-22