My call to SecItemAdd is returning 0 but the public key reference I pass in is nil. This happens ONLY with iOS 11. I am importing an RSA public key and formatting it for BasicEncodingRules using a helper library called 'SCZ-BasicEncodingRules'.
The same problem started happening with iOS 9, but was easily solved by placing a null character before the modulus data. I'm not sure if this is related or not. I'm including relevant sections of code below.
Where error is happening:
-(BOOL) myMethod
{
SecKeyRef publicKeyRef = NULL;
CFTypeRef sessionKeyRef = NULL;
NSMutableDictionary *publicKeyAttrs = NULL;
NSMutableDictionary *sessionKeyAttrs = NULL;
OSStatus status;
@try {
NSData *publicKeyData = [MyUtility getBerDataFromPublicKeyBlob:_serverPublicKey]; // below
if (!publicKeyData) {
return FALSE;
}
publicKeyTag = [self makePublicKeyTag];
publicKeyAttrs = [[NSMutableDictionary alloc] init];
[publicKeyAttrs setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[publicKeyAttrs setObject:(__bridge id)kSecAttrKeyClassPublic forKey:(__bridge id)kSecAttrKeyClass];
[publicKeyAttrs setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[publicKeyAttrs setObject:publicKeyTag forKey:(__bridge id)kSecAttrApplicationTag];
[publicKeyAttrs setObject:publicKeyData forKey:(__bridge id)kSecValueData];
[publicKeyAttrs setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
status = SecItemAdd((__bridge CFDictionaryRef) publicKeyAttrs, (CFTypeRef*) &publicKeyRef);
if (status == errSecDuplicateItem) {
DebugLog(@"%%%%%% warning - public key %@ was left in keychain ", [[NSString alloc] initWithData:publicKeyTag encoding:NSASCIIStringEncoding]);
SecItemDelete((__bridge CFDictionaryRef)publicKeyAttrs);
status = SecItemAdd((__bridge CFDictionaryRef) publicKeyAttrs, (CFTypeRef*) &publicKeyRef);
}
if (status != noErr) {
return FALSE;
}
if (!publicKeyRef) {
/* fails here -- did NOT before iOS 11 */
SecItemDelete((__bridge CFDictionaryRef)publicKeyAttrs);
return FALSE;
}
// ... more code not directly related, never reaches here anyway
} @finally {
if (publicKeyRef) {
CFRelease(publicKeyRef);
publicKeyRef = NULL;
/
status = SecItemDelete((__bridge CFDictionaryRef) publicKeyAttrs);
if (status != noErr) {
}
publicKeyTag = NULL;
}
if (sessionKeyRef) {
CFRelease(sessionKeyRef);
sessionKeyRef = NULL;
}
}
return TRUE;
}
/* MyUtility getBerDataFromPublicKeyBlob */
+(NSData*) getBerDataFromPublicKeyBlob: (NSString*) blobHex
{
NSData *blobData = [MyUtility toDataFromHexStr:blobHex];
const uint8_t *blobBytes = [blobData bytes];
int pos = 0;
if (blobBytes[pos++] == PKB_PUBLIC_KEY_ID) {
/
} else {
DebugLog(@"problem - public key not found");
return nil;
}
pos++; // version
pos += 2; // reserved
uint32_t alg = *((uint32_t*) &blobBytes[pos]);
pos += sizeof(uint32_t);
if (alg == PKB_KEYX_ALG) {
/
} else if (alg == PKB_SIGN_ALG) {
/
} else {
return nil;
}
uint32_t magic = *((uint32_t*) &blobBytes[pos]);
pos += sizeof(uint32_t);
if (magic == PKB_MAGIC) {
/
} else {
return nil;
}
uint32_t bitlen = *((uint32_t*) &blobBytes[pos]);
pos += sizeof(uint32_t);
NSUInteger modLength = bitlen / 8;
uint32_t exponent = *((uint32_t*) &blobBytes[pos]);
pos += sizeof(uint32_t);
exponent = ntohl(exponent);
NSData *expData = [NSData dataWithBytes:&exponent length:sizeof(uint32_t)];
NSMutableData *modData = [[NSMutableData alloc] initWithCapacity:modLength + 1];
[modData appendBytes:&blobBytes[pos] length:modLength];
/
starting with iOS 9:
need null byte in front of modulus data;
appending it to the end here because it will be reversed
*/
static const uint8_t byte0 = 0;
[modData appendBytes:&byte0 length:1];
[MyUtility reverseMutableData:modData];
NSArray *attrs = [NSArray arrayWithObjects:modData, expData, nil];
NSData *berData = [SCZ_BasicEncodingRules_Modified berData:attrs]; // below
return berData;
}
/* SCZ_BasicEncodingRules_Modified */
// Modified from SCZ-BasicEncodingRules - Created by Peter Suk on 5/16/12 - modified to work without the use of category methods
#import "SCZ_BasicEncodingRules_Modified.h"
@implementation SCZ_BasicEncodingRules_Modified
#define kBerTypeConstructed 0x20
#define BER_SEQUENCE 0x10
#define BER_INTEGER 0x02
+ (NSData*) berData: (NSArray*) array
{
static uint8_t bitfieldTag[] = { (kBerTypeConstructed | BER_SEQUENCE) }; /
static uint8_t integerTag[] = { BER_INTEGER }; /
NSMutableData *berData = [[NSMutableData alloc] init];
[berData appendBytes:bitfieldTag length:1];
NSUInteger arrayContentsLength = 0;
for (NSData *d in array) {
arrayContentsLength += [SCZ_BasicEncodingRules_Modified berLengthBytes:d];
}
[berData appendData:[SCZ_BasicEncodingRules_Modified lengthStorageData:arrayContentsLength]];
for (NSData *d in array) {
NSMutableData *ber = [[NSMutableData alloc] init];
[ber appendBytes:integerTag length:1];
[ber appendData:[SCZ_BasicEncodingRules_Modified lengthStorageData:d.length]];
[ber appendData:d];
[berData appendData:ber];
}
return berData;
}
+ (NSData*)lengthStorageData: (NSUInteger) contentsLength
{
NSMutableData *lengthStorageData = [[NSMutableData alloc] init];
uint8_t lengthBytesTag[1];
if (contentsLength > 0x7F) {
NSUInteger lengthStorageBytes = [SCZ_BasicEncodingRules_Modified lengthBytesLog8:contentsLength];
if (lengthStorageBytes > sizeof(NSUInteger))
[NSException
raise:@"Invalid length value"
format:@"length storage greater than %lu bytes is invalid", (unsigned long) lengthStorageBytes];
lengthBytesTag[0] = 0x80 + lengthStorageBytes;
[lengthStorageData appendBytes:lengthBytesTag length:1];
uint8_t temp[sizeof(NSUInteger)];
NSInteger bitOffset;
for (NSInteger i = 0; i < lengthStorageBytes; i++) {
bitOffset = (lengthStorageBytes - i - 1)*8;
temp[i] = (contentsLength & (0xFF << bitOffset)) >> bitOffset;
}
[lengthStorageData appendBytes:temp length:lengthStorageBytes];
}
else {
lengthBytesTag[0] = contentsLength;
[lengthStorageData appendBytes:lengthBytesTag length:1];
}
return lengthStorageData;
}
+ (NSUInteger)lengthBytesLog8: (NSUInteger) length
{
NSUInteger lengthBytes = 0;
for (NSUInteger tempLength = length; tempLength > 0; lengthBytes++) {
tempLength >>= 8;
}
return lengthBytes;
}
+ (NSUInteger)berLengthBytes: (NSData*) data
{
NSUInteger berContentsLengthBytes = data.length;
if (berContentsLengthBytes <= 0x7F) {
return 2 + berContentsLengthBytes;
}
return 2 + [SCZ_BasicEncodingRules_Modified lengthBytesLog8:berContentsLengthBytes] + berContentsLengthBytes;
}
@end