How to update an already added SecIdentityRef to iOS app's keychain? - ios

I have a PKCS12 file in my app's document folder that contains one certificate and one private key.
I am able to open this .p12 file, extract the identity object and display some information thanks to Apple's documentation (https://developer.apple.com/library/ios/#documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-DontLinkElementID_10)
What I'm trying to do now is to store this Identity to the keychain so I can use it later. I've read a lot of different stuff on iOS Keychain and I'm having a hard time figuring out how it really works.
Apple's code seems to use a persistent_ref to retrieve the Identity stored in Keychain. But I don't really understand what this is... Is it a simple reference like a memory ref ? If that is the case what happens when the device reboots ?
Unable to find a lot more information about this I tried to do it differently by using the kSecAttr attributes.
The current code works fine to add Identity to keychain :
NSMutableDictionary * dictionary = [[[NSMutableDictionary alloc] init] autorelease];
[dictionary setObject:#"LABEL" forKey:kSecAttrLabel];
[dictionary setObject:(id)newIdentity forKey:(id)kSecValueRef];
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
But if I try to add it a second time I'm receiving a -25299 error which is "fine" since it already exists. I tried handling it by an update like this :
NSMutableDictionary *searchDictionary = [[[NSMutableDictionary alloc] init] autorelease];
[searchDictionary setObject:#"LABEL" forKey:kSecAttrLabel];
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnRef];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
[updateDictionary setObject:(id)newIdentity forKey:(id)kSecValueRef];
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,(CFDictionaryRef)updateDictionary);
With this code I get a -50 status error apparently because I have invalid parameters... Which one ? Why ? What can I do to update my keychain correctly ?
EDIT: As suggested I tried to delete the existing element prior to adding it but I'm stuck with the same status code (-50). Below is the code I tried:
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
OSStatus status = SecItemDelete((CFDictionaryRef)searchDictionary);
NSAssert(status == noErr, #"Problem deleting current keychain item." );
setupSearchDirectoryForIdentifier simply create a NSDictionnary with the label of my item:
- (NSMutableDictionary *)setupSearchDirectoryForIdentifier:(NSString *)identifier {
// Setup dictionary to access keychain.
NSMutableDictionary *searchDictionary = [[[NSMutableDictionary alloc] init] autorelease];
[searchDictionary setObject:identifier forKey:kSecAttrLabel];
return searchDictionary;
}
Thank you
PS : I'm developing on Xcode 4.2 / iPad 5.1.1

Even if this is an old post, as I came up with similar issues very recently when working with identities in keychains (to be used for SSL client authentication), I am providing my own experience (with XCode 5.0.1) as support for this seems to be still very confusing.
SecItemAdd must indeed be used with very few keys. kSecValueRef (with the identity) is of course mandatory and optionally kSecAttrLabel. Using several other keys (e.g. kSecClass) resulted in silent failures, i.e. no error reported but the identity wasn't added. This was very confusing.
I didn't succeed either to use SecItemUpdate with such an identity. All my attempts resulted in a -50 error.
To be able to update an existing identity I deleted the existing identity using SecItemDelete and then added it with SecItemAdd. What was again confusing is that although the call to SecItemDelete returned a -25300 error back (errSecItemNotFound), it could be ignored as the identity was indeed deleted as it was possible to add it afterwards (using SecItemAdd) without an error.

SecIdentityRefs, SecKeyRefs and similar Sec...Ref values are ephemeral representations of keychain items. They become invalid when the application exits or when their retain count reaches zero. They cannot be directly saved in persistent storage.
On the other hand, a persistent reference is a piece of CFDataRef that you can use to retrieve a particular keychain item later. You can store it in a file, in NSUserDefaults, or anywhere else you fancy. It will not become invalid when the application exits or when the device is rebooted. (However, a persistent reference may become invalid when the keychain itself is removed (i.e., when device is restored), when the item it refers to is deleted or when one of the item's identifying attributes is modified.)
Apple's sample code uses SecItemAdd's second argument to retrieve persistent references to the items it adds to the keychain. This is then presumably stored in NSUserDefaults. Given the persistent reference, the app can later use SecItemCopyMatching to convert it to an SecIdentityRef that it can use.
Note that you don't have to use persistent references if you don't want to; if you wish, you can also choose to retrieve keychain items based on their label, or any other identifying attribute.
SecItemUpdate probably fails because a SecIdentityRef isn't a real keychain item. It is but a pseudo-item that is constructed when a public key and its associated private key are both on the keychain. As such, it doesn't make much sense to update it -- it doesn't own any attributes. All its properties are inherited from its associated SecCertificateRef (the certificate) and SecKeyRef (the private key). Updating these items instead of the identity should work. (But it is easier to simply create the item correctly: you can initialize any attribute by adding it and its value to SecItemAdd's first parameter.) Alternatively, you could try simply deleting the identity (SecItemDelete) before re-adding it to the keychain.

Related

adding prefix to your current bundle id resets the keychain?

I have an app(app1) which is in appstore with bundle id com.x.y
Now i was developing another app(app2) under same developer account with bundle id com.x.z
I wanted to make the keychain value stored in app1 available to app2.
This availablity of keychain is determined by keychain-access-groups.
so if i add prefix( current Team id) to both the bundle id i am able to get the values. example teamid.com.x.y teamid.com.x.z
Issue is When i add the prefix to app1 which is in appstore, it ask for login credentials again which i dont want as app has lots of users.
I wasn't using prefix earlier i just added them.
Is there a way i could get the keychain access for both the apps without having user to login again.
First, it's important to realize that Xcode is already adding the AppIdentifierPrefix to your identifier. It unfortunately hides it in the GUI, but if you open the entitlements plist, you'll see it. This is the identifier that is used to sign the app, and it's the piece that is used to enforce access control. I don't believe the teamid prefix you're adding really does anything. I generally would recommend an access group com.x.shared or com.x.appgroup.shared and not use com.x.z (I'm assuming com.x.y already exists, so you can't change that).
I'm assuming here that you don't want to have to force users to upgrade App1, correct? I'm moving forward on that assumption.
If you can upgrade App1 (not require an upgrade, but make sure that all new customers have an upgraded version), then only store in com.x.y if it exists. Otherwise, store in com.x.shared:
When you read from the keychain, don't use an access group. This will get the first matching record.
When you write to the keychain, use the access group that was in the record you read.
If you don't want to upgrade App1 at all right now (required or not), then just always read and write to com.x.y in App2.
When you're ready to end-of-life the com.x.y group (if you're able to finally upgrade all App1 supported users), then you can switch to:
Read from com.x.y. If it's found, delete it, and recreate it as com.x.shared. You can do this one-time in the application startup (just write an NSUserDefaults that says you've done it.
From then on, always use com.x.shared explicitly.
The key tool here is that when you ask for an explicitly access group, you have to provide the whole thing, including you AppId (which isn't displayed in the Xcode GUI). You can of course hard-code it, but a better solution is to dynamically query it. I use an updated version of David H's code:
- (NSString *)bundleSeedID {
NSDictionary *query = #{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : #"bundleSeedIDQuery",
(__bridge id)kSecAttrService : #"",
(__bridge id)kSecReturnAttributes : (id)kCFBooleanTrue
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFTypeRef)query,
(CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFTypeRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess)
return nil;
NSString *accessGroup = [(__bridge NSDictionary *)result
objectForKey:(__bridge id)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = components[0];
CFRelease(result);
return bundleSeedID;
}
This will tell you your prefix at runtime. It does so b creating a bogus keychain entry, then querying it and seeing what access group was attached to it.
You may be interested in the first section of Getting Security and Privacy Right from Renaissance.io 2014. You can skip to "Protecting Secrets with Keychain."

SecItemCopyMatching with kSecMatchItemList fails

I'm having trouble getting this call to work for IOS. I've tried a number of approaches but nothing seems to work: I always get a status of errSecParam. Can anyone tell me what I'm doing wrong?
I started out using this to get a list of attributes for a certificate I hydrated from bytes. That didn't work so I reduced it to this and received the same error. I've tested on the simulator and an iPhone6 and get the same results.
First I get an array of certificates and then I pass the array back to SecItemCopyMatching. I've tried this where I queried for attributes only and get the same error.
I'm new to IOS so I don't doubt it's something I've missed.
Thanks.
// Call SecItemCopyMatching twice: the first time fetch an array of certificates
// and the second time use the array with kSecMatchItemList.
- (void) SecItemCopyMatchingTest2 {
// Now read them from the keychain.
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassCertificate,(__bridge id)kSecClass,
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnRef,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
nil];
CFTypeRef result;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
NSArray *rgRefs = CFBridgingRelease(result);
if (status == noErr){
// this works
}
// Use the array we received from our previous call
[query setObject:rgRefs forKey:(__bridge id)kSecMatchItemList];
CFTypeRef result2;
// Results in status = errSecParam
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result2);
if (status == errSecParam){
// the cal fails.
}
}
Quote from the header file documentation:
#constant kSecMatchItemList OS X only. Specifies a dictionary key whose value is a CFArray of SecKeychainItemRef items. If provided, returned items will be limited to the subset which are contained in this list.
I know that the __OSX_AVAILABLE_STARTING claims it exists for iOS but that is a lie, on the mailing list an Apple developer claimed it has never been implemented for iOS.
Try setting the same array with kSecUseItemList. This wasn't implemented for iOS either at that time according to mailing list, but if Apple is going to implement either one for iOS (or has already done so), it's rather kSecUseItemList.
There are a lot of strange keychain API differences between macOS and iOS and some of these are only found in documentation or just in the header files. Here's another example from the SecItemDelete() delete documentation:
To delete an item identified by a transient reference, on iOS, specify kSecValueRef with a item reference. On OS X, give a kSecMatchItemList containing an item reference.
Trying to use kSecMatchItemList for that on iOS will fail, just like using kSecValueRef on macOS seems to fail. These API differences don't make any sense to me, but either you can do it with kSecUseItemList or you are out of luck and it's simply not possible on iOS.

iOS Key Chain alternative without possibility to restore on another device

Is it possible to store some information on iOS that will not be deleted when the app is deleted (like Keychain) and also cannot be restored to another device?
As i understand - Key Chain will be restored to another device if you select an encrypted backup option.
Does iOS keychain storage persist when restoring an app to a new device?
So is it somehow possible to preserve some data - after the app is deleted (to read it after the reinstall) and for it only be avalible on the device it was added / created.
There are these options:
kSecAttrAccessibleAlwaysThisDeviceOnly
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
From the docs:
[...] Items with this attribute do not migrate to a new device. [...]
I think they are exactly what you need. They are explained well in this WWDC talk:
https://developer.apple.com/videos/wwdc/2014/#711
Example usage from the above talk:
SecAccessControlRef sacObject =
SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAccessControlUserPresence, &error);
NSData* secret = [#"top secret" dataWithEncoding:NSUTF8StringEncoding];
NSDictionary *query = #{
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecAttrService: #"myservice",
(id)kSecAttrAccount: #"account name here",
(id)kSecValueData: secret};
OSStatus status = SecItemAdd((CFDictionaryRef)query, nil);
See also: https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html#//apple_ref/doc/constant_group/Keychain_Item_Accessibility_Constants

how to remove nsdictionary from keychain for replacing

I am storing and loading a serialized nsdictionary to the keychain as in this post (Store NSDictionary in keychain), but I need to be able to update/edit the dictionary contents so I would like to delete it and re add.
I just dont know how to do it. I have the following code taken from the above post:
KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:#"arbitraryId" accessGroup:nil]
NSString *error;
//The following NSData object may be stored in the Keychain
NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:dictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[keychain setObject:dictionaryRep forKey:kSecAttrService];
//When the NSData object object is retrieved from the Keychain, you convert it back to NSDictionary type
dictionaryRep = [keychain objectForKey:kSecAttrServce];
NSDictionary *dictionary = [NSPropertyListSerialization propertyListFromData:dictionaryRep mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error];
SecItemDelete((CFDictionaryRef)dictionaryRep); // doesnt work
the values arent deleted from the keychain.
thanks
This is a strange place to store the data. You're putting it in kSecAttrService, which isn't encrypted. I think you mean to put this in kSecValueData (this is the only piece of a keychain item that is encrypted).
That said, there's no need to delete the item. You can just use [keychain setObject:forKey:] to update the value whenever you want. KeychainItemWrapper automatically detects whether the item exists already, and updates it if it does.
If you want to delete the item using KeychainItemWrapper, use -resetKeychainItem. This calls SecItemDelete() with the correct value. You cannot in general mix-and-match the use of KeychainItemWrapper and raw calls to SecItem* without a good understanding of the Keychain API and exactly how KeychainItemWrapper works.
Although i have never worked on accessing Keychain properties. but by looking at the SecItemDelete method in Apple document the expected parameter is a dictionary, in your code you are passing dictionaryRep which is a NSData type.
https://developer.apple.com/library/mac/#documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/doc/c_ref/SecItemDelete
Found a question here, hope it might help.
I want to delete all items in my self created KeyChain on Mac OS X

Keychain - Secure Data Storage

I am developing an application with keychain implementation . i am able to create & Save data into keychain . I am using the Keychain Wrapper classes provided By Apple.
According to requirement , I have to implement best possible Security in the KeyChain (The security team pointed out lapses , such as it's accessibility on Jail-broken devices).
Could Someone give me direction?
I had also Implemented keychain in application long Back using the same Wrapper you cited , but , of course with a lot of modifications.
Basically Keychain is quite secure .According to Apple , it's an encrypted container that holds secure information for multiple applications ,which means that when the keychain is locked, no one can access its protected contents .
In iOS , only the application creating the keychain can access it.
According to Apple's documentation , iOS can choose to Memory-Cache or Disk Cache it.
But from iOS 4.xx++ , it's only disk-cached(dunno why) , thus always creating a
sqlite DB , where all the data in the keychain are stored corresponding to a particular Identifier.
The Sqlite DB Can be Hacked on rooted or Jail-broken devices.
To Secure the Keychain
1 Add the security keyword "kSecAttrAccessibleWhenUnlockedThisDeviceOnly" while adding or
updating the data in keychain on the methods "SecItemUpdate" & "SecItemAdd".
Something like :-
- (void)writeToKeychain
{
NSDictionary *attributes = NULL;
NSMutableDictionary *updateItem = NULL;
OSStatus result;
if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
{
updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];
[updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];
NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
[tempCheck removeObjectForKey:(id)kSecClass];
#if TARGET_IPHONE_SIMULATOR
[tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];
#endif
[updateItem setObject:(id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(id)kSecAttrAccessible];
result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);
NSAssert( result == noErr, #"Couldn't update the Keychain Item." );
CFRelease(attributes);
}
else
{
[keychainItemData setObject:(id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(id)kSecAttrAccessible];
result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
NSAssert( result == noErr, #"Couldn't add the Keychain Item." );
}
}
2 Encrypt the data before Adding to the Keychain .I used AES-128 Encryption.
Also ensure that the key used for Encryption is RSA key.(sent by SSL Web Service ).
NOTE :-The Keychain Data is stored in the /private/var/Keychains/keychain-2.db file on the iPhone.
Hope it helps you.
[attributeDict setObject:(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];

Resources