Using SecItemUpdate in Keychain Services - ios

I have the following code to create a keychain item in the keychain:
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject: (__bridge id) kSecClassGenericPassword forKey: (__bridge id) kSecClass];
[dict setObject: MYKEY forKey: (__bridge id) kSecAttrService];
[dict setObject: #"0" forKey: (__bridge id) kSecValueData];
SecItemAdd ((__bridge CFDictionaryRef) dict, NULL);
Which works fine. Can anyone give the syntax for what exactly to put for SecItemUpdate if I want to change this item?
UPDATE: with the following:
NSMutableDictionary *query = [NSMutableDictionary dictionary];
NSMutableDictionary *attributesToUpdate = [NSMutableDictionary dictionary];
[query setObject: (__bridge id) kSecClassGenericPassword forKey: (__bridge id) kSecClass];
[query setObject: MYKEY forKey: (__bridge id) kSecAttrService];
[query setObject: (id) kCFBooleanTrue forKey: (__bridge id) kSecReturnData];
NSString *numberOfBalloonsString = [NSString stringWithFormat:#"%d", numberOfBalloonsUsed];
NSData *numberOfBalloonsData = [numberOfBalloonsString dataUsingEncoding:NSUTF8StringEncoding];
[attributesToUpdate setObject: numberOfBalloonsData forKey:(__bridge id)kSecValueData];
OSStatus error = SecItemUpdate ((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) attributesToUpdate);
NSLog(#"Error #: %ld", error);
I'm getting the error code -50 =
One or more parameters passed to the function were not valid.

SecItemUpdate is terribly documented.
The query parameter of SecItemUpdate is documented as a query (as used in other functions) as well as the vague statement: "Specify the items whose values you wish to change". This seems to imply that you must include the existing attribute value in this dictionary that you want to change but I don't think you do. I've found you can use the same query you use to get attributes for the item you want to update.
The attributes parameter should be the result of SecItemCopyMatching with the kSecValueData key and value added and any attributes changed.

A late answer, but an answer nonetheless:
I've been struggling with updating items in the keychain as well, my context was a little different though.
What happened:
I could add a keychain item with success (using SecItemAdd), but calling SecItemUpdate on the same item failed with the notorious errSecParam -50.
What was even worse; if the keychain item already existed (hence I called SecItemUpdate immediately), the update went through with no problems at all.
I've got absolutely no idea as of why that happened...
How I fixed it:
Quite simple actually, I just removed "params" until the big bad -50 was satisfied. This happened when I removed the kSecClass from the dictionary I retrieved from kSecItemCopyMatching.
Here's my code:
// If the item already exists, we update it instead
if (SecItemCopyMatching((__bridge CFDictionaryRef)self.searchQueryDict, (CFTypeRef *)&foundItem) == errSecSuccess) {
NSMutableDictionary *updateDict = (__bridge NSMutableDictionary *)foundItem;
[updateDict addEntriesFromDictionary:dictToSave];
[updateDict removeObjectForKey:(__bridge id)kSecClass];
OSStatus updateSuccess = SecItemUpdate((__bridge CFDictionaryRef)self.updateQueryDict,
(__bridge CFDictionaryRef)updateDict);
NSAssert(updateSuccess == errSecSuccess, #"Couldn't save the dirty info to the keychain, might want to log the updateSuccess (%d)", updateSuccess);
}
As a reference I used the following dictionaries
self.searchQueryDict contained:
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword
(__bridge id)kSecAttrService : service
(__bridge id)kSecAttrGeneric : [identifier dataUsingEncoding:NSUTF8StringEncoding]
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne
(__bridge id)kSecReturnAttributes : (__bridge id)kCFBooleanTrue
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue
self.updateQueryDict contained:
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service,
(__bridge id)kSecAttrGeneric : [identifier dataUsingEncoding:NSUTF8StringEncoding]
dictToSave should contain the values (in the correct format) which needs to change
Removing the kSecClass fixed the problem for me.

Related

Delete all keys from the Keychain

I need to delete all the keys stored at the keychain. Problem is that they are not static strings (I create them using "the user name + static string") and I have to implement something like a reset app option.
At the moment of the app implementation (couple of years ago) I used a library called FXKeychain to access the keychain but didn't seem to have an option like that.
Is there any other way to do it?
I'm working with Objective-C.
I found this code searching about it:
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
nil];
NSArray *secItemClasses = [NSArray arrayWithObjects:
(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity,
nil];
for (id secItemClass in secItemClasses) {
[query setObject:secItemClass forKey:(__bridge id)kSecClass];
CFTypeRef result = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
NSLog(#"%#", (__bridge id)result);
if (result != NULL) CFRelease(result);
}
But I don't totally understand it and I think it's not working (maybe it has to be with the library I used?)
To delete the keychain items added from your app, this should work fine :
KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:[[NSBundle mainBundle] bundleIdentifier] accessGroup:nil];
[keychain resetKeychainItem];
did you already try?

Will this method delete other people's keychain data?

I am using the following code to remove saved keychain data from my App.
for (id secclass in #[
(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity]) {
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
secclass, (__bridge id)kSecClass,
nil];
SecItemDelete((__bridge CFDictionaryRef)query);
}
This works well, but I'm not sure if this deletes keychain data for other apps,sites,ect..
I don't think it does, but I am not too sure. Please let me know.
If it did, that would be a huge security problem.
(It doesn't. You're fine.)

Access all (user) accounts inside Keychain

I've been looking all around but still not found the answer. Any idea how to return all kSecAttrAccounts from keychain? I would like to get back a list of every identifier I used when creating keychain items, then to choose the one I want to delete it using a "[KeychainWrapper deleteItemFromKeychainWithIdentifier:identifier] method".
It works fine when I remember the username (identifier) I used when creating the account but cannot figure out how to have them all back when you created lots of them.
I tried a kind of basic [dictionary objectForKey:(__bridge(id)kSecAttrAccount)] but it does not make it.
Thanks a lot!!
Tricho
Use kSecMatchLimitAll to get all values in your query dictionary for kSecMatchLimit
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit
It will fetch all passwords in keychain for kSecClassGenericPassword.You can use other keychain classes as well like this
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
(__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass, //change your class in query
nil];
CFTypeRef result = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
NSLog(#"%#", (__bridge id)result);
if (result != NULL) CFRelease(result);
EDIT : For delting all keys of your app you can use
+(void)deleteAllKeychainItems{
NSArray *secItemClasses = #[(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = #{(__bridge id)kSecClass:secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}
}
It will delete all keychain items including all accounts password or values associated.

updating kSecValueData in iOS using secItemUpdate Error Code -50

I am at a loss here, I create a keychain query, add the item if it does not already exist, then I try and update kSecValueData with a test string and it returns error code -50, which means there was an error with one or more parameters I entered...
NSString *initial = #"";
NSData *initData = [initial dataUsingEncoding:NSUTF8StringEncoding];
//Create Search Dictionary For Phone Number...
NSDictionary *secPhoneItem = #{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue,
(__bridge id)kSecValueData : initData
};
//Check to see if keychain already exists by using secItemCopyMatching and associated status code
OSStatus PhoneCheckStatus = SecItemCopyMatching((__bridge CFDictionaryRef)secPhoneItem, NULL);
//Check Status Code Phone
if (PhoneCheckStatus == errSecItemNotFound) //If Phone Keychain Item Does Not already Exist
{
//Add Phone Number To Keychain
SecItemAdd((__bridge CFDictionaryRef)secPhoneItem, NULL);
}
//Update Phone Number to String
NSString *string = #"Test String";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *attributesForUpdate = #{
(__bridge id)kSecValueData : data
};
OSStatus news = SecItemUpdate((__bridge CFDictionaryRef)secPhoneItem, (__bridge CFDictionaryRef)attributesForUpdate);
NSLog(#"Update Status Code: %ld", news);
If anyone knows why or can shed some info, the only lead I have right now from Apples Documentation is that you can only pass real attributes into secItemUpdate(), not "meta" attributes.
So after rereading the documentation, I found out that the key-value pair (__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue cannot be used in the secItemUpdate()' query parameter. To fix my problem and help better refine the search, I added the key-value pair(__bridge id)kSecAttrDescription : someUniqueData` to the search query along with the class item specification then making my attributes dictionary returned status 0: SUCCESS!!!
Key and certificate Attribute Update-----------
for the normal SecItemUpdate code gave two error:-25300(item not found),-50(trying to update more than one item in one update method)
here is the code for update method:
//Search Dictionary.....
NSMutableDictionary *searchDict = [[NSMutableDictionary alloc] init];
NSData *privateKeyTag = [NSData dataWithBytes:[keyIdentifier UTF8String] length:keyIdentifier.length];
[searchDict setObject:privateKeyTag forKey:(__bridge id)kSecAttrApplicationTag];
[searchDict setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[searchDict setObject:(__bridge id)(kSecAttrKeyTypeRSA) forKey:(__bridge id<NSCopying>)(kSecAttrKeyType)];
[searchDict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[searchDict setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id<NSCopying>)(kSecReturnData)];
status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDict, (CFTypeRef*)&item);
if (status != errSecSuccess)
{//your code for error handling }
//dictionary for the attribute that are going to update in key of certificate
//if youwant to update your passward the add the passward attribute
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,kSecAttrAccessible, nil];
/*removing the some of the from the searching dictionary*/
[searchDict removeObjectForKey:(__bridge id)kSecReturnData];
[searchDict removeObjectForKey:(__bridge id)kSecMatchLimit];
//Creating the Array for every item in the keychain that is cert of key as Search Dictionary
NSArray *secItemclasses= #[(__bridge id)kSecClassKey,(__bridge id)kSecClassCertificate];
for (id secitemclass in secItemclasses) {
//updating the key as well as certificate attribute....//
status = SecItemUpdate((__bridge CFDictionaryRef)searchDict,(__bridge CFDictionaryRef)dict);
}
if(status != errSecSuccess)

which key should I use to store the password in iOS keychain?

The KeychainItemWrapper class in the Apple GenericKeychain sample use kSecValueData key to store password.
But the reference http://developer.apple.com/library/ios/#documentation/Security/Reference/keychainservices/Reference/reference.html#//apple_ref/doc/uid/TP30000898
says kSecValueData is
used in the results dictionary for SecItemCopyMatching or SecItemAdd, indicating the type of values returned.
which key should I use when I call SecItemAdd to create a keychain item?
You should use kSecValue data as the key to store the password (in NSData or CFDataRef format).
The reference is a bit unclear in this subject, kSecValueData key works as output key as well as input key. That is, you use it when you query a keychain item (SecItemCopyMatching) and specify a kSecReturnAttributes key, so the result is returned as a dictionary, the password will get stored under a kSecValueData key of that dictionary. And you also use it when you add an item to the keychain (SecItemAdd), storing the NSData or CFDataRef value of your password in the kSecValueData key before calling the method.
Here's an example of both cases:
Retrieving password:
NSMutableDictionary *queryDictionary = [[NSMutableDictionary alloc] init];
[queryDictionary setObject: (__bridge id)kSecClassGenericPassword forKey: (__bridge id<NSCopying>)kSecClass];
[queryDictionary setObject:service forKey:kSecAttrService];
[queryDictionary setObject:account forKey:kSecAttrAccount];
// The result will be a dictionary containing the password attributes...
[queryDictionary setObject:YES forKey:(__bridge id<NSCopying>)(kSecReturnAttributes)];
// ...one of those attributes will be a kSecValueData with the password
[queryDictionary setObject:YES forKey:(__bridge id<NSCopying>)(kSecReturnData)];
OSStatus sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)(queryDictionary), (CFTypeRef *)&result);
if (sanityCheck != noErr)
{
NSDictionary * resultDict = (__bridge NSDictionary *)result;
// here's the queried password value
NSData *passwordValue = [resultDict objectForKey:(__bridge id)(kSecValueData)];
}
Adding password:
NSString *passwordString = #"my password value";
NSData *passwordData = [passwordString dataUsingEncoding:NSUTF8StringEncoding];
CFDictionaryRef result = nil;
NSMutableDictionary *addDictionary = [[NSMutableDictionary alloc] init];
[addDictionary setObject: (__bridge id)kSecClassGenericPassword forKey: (__bridge id<NSCopying>)kSecClass];
[addDictionary setObject:service forKey:kSecAttrService];
[addDictionary setObject:account forKey:kSecAttrAccount];
// here goes the password value
[addDictionary setObject:passwordData forKey:(__bridge id<NSCopying>)(kSecValueData)];
OSStatus sanityCheck = SecItemAdd((__bridge CFDictionaryRef)(queryDictionary), NULL)
if (sanityCheck != noErr)
{
// if no error the password got successfully stored in the keychain
}

Resources