Hello,
I'm running a regression tests agains iOS 13b4 and i have found an issue with the SecItemCopyMatching API.
In iOS 12 the following class can generate a key-pair, and later on retrieve and query for the existance of a key given an alias:
@interface PKIUtils : NSObject
+ (BOOL)generateKeyPair:(NSString* _Nonnull)alias error:(NSError* _Nullable* _Nullable)error;
+ (BOOL)hasKeyPair:(NSString* _Nonnull)alias;
+ (SecKeyRef) privateKeyForAlias:(NSString* _Nonnull) alias error:(NSError * _Nullable* _Nullable) error;
@end
@implementation PKIUtils
+ (BOOL)generateKeyPair:(NSString* _Nonnull)alias error:(NSError* _Nullable* _Nullable)error {
if (!alias || alias.length <= 0) {
NSLog(@"Alias cannot be empty");
return NO;
}
if ([self hasKeyPair:alias]) {
NSLog(@"key already exists");
return NO;
}
NSDictionary* attributes = [self buildAttributes:alias error:error];
if (!attributes) {
return NO;
}
CFErrorRef errorRef = nil;
SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &errorRef);
if (!privateKey) {
if (error) *error = (__bridge NSError*)errorRef;
return NO;
}
// release the private key reference as soon as it is not longer needed.
CFRelease(privateKey);
// everything went fine :)
return YES;
}
+ (NSDictionary*)buildAttributes:(NSString*)alias error:(NSError**)error {
CFErrorRef errorRef = nil;
UInt32 accessControlFlags = kSecAccessControlPrivateKeyUsage;
SecAccessControlRef access = SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
accessControlFlags, &errorRef);
if (!access) { // unable to create the access Control reference
if (error)
*error = (__bridge NSError*)errorRef;
return nil;
}
NSDictionary* attributes = @{
(__bridge NSString*)kSecAttrKeyType : (id)kSecAttrKeyTypeECSECPrimeRandom,
(__bridge NSString*)kSecAttrKeySizeInBits : @256,
(__bridge NSString*)kSecPrivateKeyAttrs : @{
(__bridge NSString*)kSecAttrIsPermanent : @YES,
(__bridge NSString*)kSecAttrApplicationTag : [alias dataUsingEncoding:NSUTF8StringEncoding],
(__bridge NSString*)kSecAttrAccessControl : (__bridge id)access,
}
};
// release the access control reference as soon as it is not longer needed.
CFRelease(access);
return attributes;
}
+ (BOOL)hasKeyPair:(NSString* _Nonnull)alias {
SecKeyRef privateKeyRef = [self privateKeyForAlias:alias error:nil];
return privateKeyRef != nil;
}
+ (SecKeyRef) privateKeyForAlias:(NSString* _Nonnull) alias
error:(NSError * _Nullable* _Nullable) error {
NSDictionary* keyQuery = @{
(__bridge NSString*)kSecClass : (id)kSecClassKey,
(__bridge NSString*)kSecAttrApplicationTag : [alias dataUsingEncoding:NSUTF8StringEncoding],
(__bridge NSString*)kSecReturnRef : @YES
};
SecKeyRef privateKeyRef = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)keyQuery, (CFTypeRef*)&privateKeyRef);
if (status != noErr) {
NSLog(@"Error retrieving key-pair from the storage (%@)", @(status));
return nil;
}
return privateKeyRef;
}
@end
Called from an app delegate as:
NSError* error;
NSString* alias = @"some alias"
BOOL result = [PKIUtils generateKeyPair:alias error:&error];
if(result){
if([PKIUtils hasKeyPair:alias]){
NSLog(@"Hooray");
} else {
NSLog(@"Oh no!");
}
}
However, in iOS 13b4, it can generate the key-pair, i can perform operations over the private key reference (export public key, sign, verify) but whenever i attempt to query for the same private key, it cannot be found. The OSStatus code returned is -25300 (item not found).
Am i missing something?
NOTES:
- The code has only been tested on the simulator.
- Moving to CrytoKit is not a viable option
- I have attempted multiple combinations of parameters, all the same result.
The solution i found was to add precompiler if around certain parameters when using the simulator, in particular:
#if !(TARGET_IPHONE_SIMULATOR)
(__bridge NSString*)kSecAttrTokenID : (id)kSecAttrTokenIDSecureEnclave,
#endif
(__bridge NSString*)kSecPrivateKeyAttrs : @{
#if !(TARGET_IPHONE_SIMULATOR)
(__bridge NSString*)kSecAttrAccessControl : (__bridge id)access,
#endif
This fully solved the issue for me.