Adding private key into iOS Keychain
Asked Answered
K

2

20

I am trying to add a private key into the iOS keychain. The certificate (public key) works fine but the private key refuses... I am totally confused why the following code does not work.

First I am checking if the current key (=key in case of that the Keychain is a key/value store) is 'free' in the Keychain. Then I am going to add the private key.

CFStringRef labelstring = CFStringCreateWithCString(NULL, [key cStringUsingEncoding:NSUTF8StringEncoding], kCFStringEncodingUTF8);

NSArray* keys = [NSArray arrayWithObjects:(__bridge id)kSecClass,kSecAttrLabel,kSecReturnData,kSecAttrAccessible,nil];
NSArray* values = [NSArray arrayWithObjects:(__bridge id)kSecClassKey,labelstring,kCFBooleanTrue,kSecAttrAccessibleWhenUnlocked,nil];
NSMutableDictionary* searchdict = [NSMutableDictionary dictionaryWithObjects:values forKeys:keys];

CFRelease(labelstring);

NSMutableDictionary *query = searchdict;


CFTypeRef item = NULL;
OSStatus error = SecItemCopyMatching((__bridge_retained CFDictionaryRef) query, &item);

if (error)
{
    NSLog(@"Error: %ld (statuscode)", error);
}

if(error != errSecItemNotFound)
{
    SecItemDelete((__bridge_retained CFDictionaryRef) query);
}

[query setObject:(id)data forKey:(__bridge id)kSecValueData];

OSStatus status = SecItemAdd((__bridge_retained CFDictionaryRef) query, &item);

if(status)
{
    NSLog(@"Keychain error occured: %ld (statuscode)", status);
    return NO;
}

The debug output is the following:

2012-07-26 15:33:03.772 App[15529:1b03] Error: -25300 (statuscode)
2012-07-26 15:33:11.195 App[15529:1b03] Keychain error occured: -25299 (statuscode)

The first error code -25300 represents errSecItemNotFound. So there is no value stored for this key. Then, when I try to add the private key into the Keychain I get -25299 which means errSecDuplicateItem. I do not understand this. Why is this happening?

Does anyone have a clue or hint on this?

Apple's error codes:

errSecSuccess                = 0,       /* No error. */
errSecUnimplemented          = -4,      /* Function or operation not implemented. */
errSecParam                  = -50,     /* One or more parameters passed to a function where not valid. */
errSecAllocate               = -108,    /* Failed to allocate memory. */
errSecNotAvailable           = -25291,  /* No keychain is available. You may need to restart your computer. */
errSecDuplicateItem          = -25299,  /* The specified item already exists in the keychain. */
errSecItemNotFound           = -25300,  /* The specified item could not be found in the keychain. */
errSecInteractionNotAllowed  = -25308,  /* User interaction is not allowed. */
errSecDecode                 = -26275,  /* Unable to decode the provided data. */
errSecAuthFailed             = -25293,  /* The user name or passphrase you entered is not correct. */ 

Thanks in advance!

Update #1: I've figured out that it works only for the first time. Even when data and key is different, after the first time stored into the keychain I cannot store further keys.

Kamala answered 26/7, 2012 at 13:42 Comment(1)
I'm facing the exact same issue. First key added using SecItemAdd without a problem, then any consecutive call to SecItemAdd fails with errSecDuplicateItem although despite SecItemCopyMatching returning errSecItemNotFound. Have you found a solution to this yet?Ingot
K
8

The following code worked for me:

NSMutableDictionary *query = [[NSMutableDictionary alloc] init]; 
[query setObject:(id)kSecClassKey forKey:(id)kSecClass]; 
[query setObject:(id)kSecAttrAccessibleWhenUnlocked forKey:(id)kSecAttrAccessible]; 
[query setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];

//adding access key 
[query setObject:(id)key forKey:(id)kSecAttrApplicationTag];


//removing item if it exists 
SecItemDelete((CFDictionaryRef)query);

//setting data (private key) 
[query setObject:(id)data forKey:(id)kSecValueData];

CFTypeRef persistKey; OSStatus status = SecItemAdd((CFDictionaryRef)query, &persistKey);

if(status) {
    NSLog(@"Keychain error occured: %ld (statuscode)", status);
    return NO; 
}
Kamala answered 12/9, 2012 at 11:57 Comment(8)
It is bad practice to delete a Keychain item only to add an item with the same info back. I don't recall the specific reason why, but I believe it may cause conflicts when attempting to do so.Curnin
I talked to an Apple employee who works on the Keychain at last years WWDC and he told me that in fact they don't offer another way to achieve this by now but they have a private API for that which they'll release soon...Kamala
I don't know what you mean Chris. I had the same problem and was able to fix my code to correctly find the existing item. My problem was I was defining it to be syncable over iCloud when adding it, but didn't include that in the query when searching, so it couldn't find a match. I did not have to delete and add it again.Curnin
can this be used for converting String to SecKeyRef?Dossal
@Joey It is bad practice especially on OSX because the user may have moved the key to a different keychain (you can have hundreds of these if you like) and when you delete and recreate it, it always is recreated in the default keychain and then the user has to move it again. It is bad practice on iOS and OS X as any access control set by the system/user or any extra data added from other apps (if the item is shared, which is possible) is lost that way.Relay
@Kamala Sorry, I don't know who you talked to, but as long as there has been SecItemAdd there also has been SecItemUpdate and guess what it is good for. Instead of creating a new item, first see it already exists and it it does, update the existing item. Only create a new item if there is no item to update.Relay
I assume key is a SecKeyRef and is the private key to save. What is data?Bent
I have read (sorry have lost the link) that the kSecClassKey has no kSecValueData. I was also not able to use that method to import a private key that I have generated using openssl.Exportation
I
1

Sorry but I'll never be able to debug your code. Apple provides some sample code (KeychainItemWrapper) which lets you save one string (I recall). Its a big help dealing with the key chain. There is a gist on the web that is a modified version of that class, but saves and restores a dictionary (archived as a data object, which is what the Apple code does to the string). This lets you save multiple items in one interface to the keychain. The gist is here Keychain for NSDictionary/data

Imray answered 26/7, 2012 at 23:23 Comment(3)
Thanks, but it need to be stored as kSecClassKey (and the corresponding certificate as kSecClassCertificate). I know Apple provides this sample code for storing user credentials (but only strings) into the keychain. Considering, that one wants to verify a certificate or use the additional protection of the kSecClassKey it cannot get stored using the approach from Apple's sample code or your link. However, I think I have found a solution but have to verify this before I post it here.Kamala
In my experience, the keychain wrapper does not allow multiple items to be saved to the same keychain group. This caused some major frustation but a solution can be found here: #11056231Menken
That's funny - since I'm doing it with a dictionary in my app and saving email, password, and another bit of context related to the user. But I modified Apple's code a bit not a lot though - you can see it in the link in my answer. This is working code that's in thousands of phones now (not millions :-( )Imray

© 2022 - 2024 — McMap. All rights reserved.