6
votes

I'm trying to add a RSA public key to my iPhone's keychain using CryptoExercise's SecKeyWrapper addPeerPublicKey:keyBits: method. The logic of this method is that it first tries to add the key to the keychain and if it already there (sanityCheck==errSecDuplicateItem) it tries to retrieve this key from the keychain by calling SecKeyItemCopyMatching().

That is exactly what happens in my case: the key is already in the keychain, so the call to SecKeyItemAdd() returns errSecDuplicateItem.

Then it tries to retrieve the existing key but SecKeyItemCopyMatching() returns 0 (indicating that there was no error) but the second parameter (peerKeyRef) remains desesperately nil.

How is this possible? What is wrong with this?

Here is the code of [SecKeyWrapper addPeerPublicKey:keyBits:] from CryptoExercise sample for reference:

- (SecKeyRef)addPeerPublicKey:(NSString *)peerName keyBits:(NSData *)publicKey {
    OSStatus sanityCheck = noErr;
    SecKeyRef peerKeyRef = NULL;
    CFTypeRef persistPeer = NULL;

    LOGGING_FACILITY( peerName != nil, @"Peer name parameter is nil." );
    LOGGING_FACILITY( publicKey != nil, @"Public key parameter is nil." );

    NSData *peerTag = [[NSData alloc] initWithBytes:(const void *) [peerName UTF8String] length:[peerName length]];
    NSMutableDictionary *peerPublicKeyAttr = [[NSMutableDictionary alloc] init];

    [peerPublicKeyAttr setObject:(__bridge id) kSecClassKey forKey:(__bridge id) kSecClass];
    [peerPublicKeyAttr setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id) kSecAttrKeyType];
    [peerPublicKeyAttr setObject:peerTag forKey:(__bridge id) kSecAttrApplicationTag];
    [peerPublicKeyAttr setObject:publicKey forKey:(__bridge id) kSecValueData];
    [peerPublicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id) kSecReturnPersistentRef];

    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) peerPublicKeyAttr, (CFTypeRef *) &persistPeer);

    // The nice thing about persistent references is that you can write their value out to disk and
    // then use them later. I don't do that here but it certainly can make sense for other situations
    // where you don't want to have to keep building up dictionaries of attributes to get a reference.
    //
    // Also take a look at SecKeyWrapper's methods (CFTypeRef)getPersistentKeyRefWithKeyRef:(SecKeyRef)key
    // & (SecKeyRef)getKeyRefWithPersistentKeyRef:(CFTypeRef)persistentRef.

    LOGGING_FACILITY1( sanityCheck == noErr || sanityCheck == errSecDuplicateItem, @"Problem adding the peer public key to the keychain, OSStatus == %ld.", sanityCheck );

    if (persistPeer) {
        peerKeyRef = [self getKeyRefWithPersistentKeyRef:persistPeer];
    } else {
        [peerPublicKeyAttr removeObjectForKey:(__bridge id) kSecValueData];
        [peerPublicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id) kSecReturnRef];
        // Let's retry a different way.
        sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef) peerPublicKeyAttr, (CFTypeRef *) &peerKeyRef);
    }

    LOGGING_FACILITY1( sanityCheck == noErr && peerKeyRef != NULL, @"Problem acquiring reference to the public key, OSStatus == %ld.", sanityCheck );

    if (persistPeer) CFRelease(persistPeer);
    return peerKeyRef;
}
1
I don't think it's relevant to your issue, but it seems like the query you're passing to SecItemCopyMatching includes both kSecReturnRef and kSecReturnPersistentRef set to true. That'll make the value returned in peerKeyRef a CFDictionaryRef rather than the SecKeyRef that your code appears to expect. - bdash
I noticed that, but since that's the way it's written in the original Apple sample, I left it like that until it creates a problem. I tried to remove the old value and it yielded the same result: no result at all. - Sebastien

1 Answers

4
votes

I had the same problem and I assume you try to import a RSA key which was not exported from another iOS device.

The reason seems to be an incompatible key format - in detail iOS expects some ASN1 header NOT to be set. Why the functions returns OK is for me only explainable with a bug...

Check out the code at http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/ this is the correct solution and works for me - so thanks to Chris Luke