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)
Related
Can anybody tell me where is the miastake? I have no idea now.
//Get a query Dic
+ (NSMutableDictionary *)keychainQueryDictionary:(NSString *)service
{
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassGenericPassword,(__bridge id)kSecClass,
service, (__bridge id)kSecAttrService,
service, (__bridge id)kSecAttrAccount,
(__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible,
nil];
}
//Here i save some data to keychain
+ (OSStatus)saveData:(id)data service:(NSString *)serviceIdentify
{
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
return SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}
//delete opration
+ (OSStatus)deleteData:(NSString *)serviceIdentify
{
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
return SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}
//Here i try to update one item in keychian, but i get an error -50, but i have no idea where is the wrong param
+ (OSStatus)updataData:(id)data service:(NSString *)serviceIdentify
{
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
CFDataRef keyData = NULL;
OSStatus status = errSecNotAvailable;
if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
NSMutableDictionary *queryDict = nil;
NSDictionary *ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
queryDict = [NSMutableDictionary dictionaryWithDictionary:ret];
[queryDict setObject:[keychainQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
NSMutableDictionary *attributesToUpdate = [self keychainQueryDictionary:serviceIdentify];
[attributesToUpdate setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
status = SecItemUpdate((__bridge CFDictionaryRef)queryDict,(__bridge CFDictionaryRef)attributesToUpdate);
}
return status;
}
//read opration
+ (id)loadData:(NSString *)serviceIdentify
{
id ret = nil;
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
}
return ret;
}
-50 is errSecParam. I think you have the parameters flipped for SecItemUpdate. The first param is the search query to find the matching item that needs to be updated. This probably should be created by calling your keychainQueryDictionary method. The second parameter should contain the change - in your example this is the new data for kSecValueData like you are doing, but the dictionary generally doesn't include any of search attributes. In your example it looks like it also contains all the search attributes again which will cause an errSecParam.
As a separate point, also note that any values in the keychainQuery dictionary that are not applicable to the update will also cause errSecParam. If these values are present they can simply be removed, for example:
CFDictionaryRemoveValue(keychainQuery, kSecReturnData);
CFDictionaryRemoveValue(keychainQuery, kSecMatchLimit);
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.
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
}
I have two keys, public and private, that are both stored in SecKeyRef-variables. For simplicity's sake, let's start with the public one. What I wanna do is export it to an NSData object. For that, there is an almost famous code snippet provide by Apple, which is here:
- (NSData *)getPublicKeyBits {
OSStatus sanityCheck = noErr;
NSData * publicKeyBits = nil;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];
// Get the key bits.
sanityCheck = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyBits);
if (sanityCheck != noErr)
{
publicKeyBits = nil;
}
[queryPublicKey release];
return publicKeyBits;
}
I have Xcode 4.6.2, however, and the code appears erroneous ("__bridge" is added before each conversion to id). The new version looks like this:
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey {
OSStatus sanityCheck = noErr;
NSData * publicKeyBits = nil;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnData];
// Get the key bits.
sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyBits);
if (sanityCheck != noErr)
{
publicKeyBits = nil;
}
return publicKeyBits;
}
There are still two errors, though:
use of undeclared identifier 'publicTag'
Cast of an indirect pointer to an Objective-C pointer to 'CFTypeRef ' (aka 'const void *') is disallowed with ARC
Now, I hope that after your help, the first issue will no longer be a problem, for I do not want to build a query or whatnot to extract the key from the keychain. I have it in a variable and I wish to extract it from there. The variable's name is givenPublicKey, and that's the key I wish to convert to NSData.
So, how would I go about doing this and solving this ARC-issue?
Follow-up: How can I export a private key to NSData, since I've read several time that the function I'm trying to work with only works for public keys.
use of undeclared identifier 'publicTag'
The publicTag is just some unique identifier added to the Keychain items. In the CryptoExercise sample project it is defined as
#define kPublicKeyTag "com.apple.sample.publickey"
static const uint8_t publicKeyIdentifier[] = kPublicKeyTag;
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
Cast of an indirect pointer to an Objective-C pointer to 'CFTypeRef ' (aka 'const void *') is disallowed with ARC
This can be solved by using a temporary CFTypeRef variable:
CFTypeRef result;
sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, &result);
if (sanityCheck == errSecSuccess) {
publicKeyBits = CFBridgingRelease(result);
}
I do not want to build a query or whatnot to extract the key from the keychain. I have it in a variable and I wish to extract it from there ...
As far as I know, you have to store the SecKeyRef to the Keychain temporarily. SecItemAdd
has the option to return the added item as data. From the documentation:
To obtain the data of the added item as an object of type CFDataRef,
specify the return type key kSecReturnData with a value of
kCFBooleanTrue.
Putting all that together, the following code should do what you want:
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey {
static const uint8_t publicKeyIdentifier[] = "com.your.company.publickey";
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
OSStatus sanityCheck = noErr;
NSData * publicKeyBits = nil;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
// Temporarily add key to the Keychain, return as data:
NSMutableDictionary * attributes = [queryPublicKey mutableCopy];
[attributes setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];
[attributes setObject:#YES forKey:(__bridge id)kSecReturnData];
CFTypeRef result;
sanityCheck = SecItemAdd((__bridge CFDictionaryRef) attributes, &result);
if (sanityCheck == errSecSuccess) {
publicKeyBits = CFBridgingRelease(result);
// Remove from Keychain again:
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
}
return publicKeyBits;
}
I hope that this works, I cannot test it at the moment.
Follow-up: How can I export a private key to NSData, since I've read several time that the function I'm trying to work with only works for public keys.
I don't know.
I'm attempting to use the SFHF keychain classes (from here) with an IOS 5 project. I've successfully converted most of the class over to abide by the new ARC rules.
I'm having some trouble with one small section of the code as follows
OSStatus status = SecItemCopyMatching((CFDictionaryRef) objc_unretainedPointer(attributeQuery), (CFTypeRef *) objc_unretainedPointer(&attributeResult)
This gives the following syntax issue:
warning: Semantic Issue: Incompatible pointer types passing 'NSDictionary *__strong *' to parameter of type 'id'
I'm rather new to iOS development and this has me pretty much stumped right now. Any help is greatly appreciated.
This is the declaration of the API:
OSStatus SecItemCopyMatching (
CFDictionaryRef query,
CFTypeRef *result
);
The result is a pass-by-reference return value.
Declare a local variable of type CFTypeRef, call the function and pass the address of said local as per the API, then do any ARC specific shenanigans after the function call.
Yes -- the error is correct. You aren't passing a CFTypeRef, you are passing a CFTypeRef* and objc_unretainedPointer() has no clue what to do with that.
Do something like:
CFTypeRef localResult
SecItemCopyMatching(query, &localResult);
if (... no error ...) {
result = objc_retainedObject(localResult);
}
Had trouble with this call, this is the code I got to work:
NSMutableDictionary *queryDictionary = [[NSMutableDictionary alloc] init];
// Set some properties.
[queryDictionary setObject:[key dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecAttrGeneric];
[queryDictionary setObject:(id) kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
[queryDictionary setObject:(__bridge id) kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[queryDictionary setObject:(id) kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[queryDictionary setObject:(__bridge id) kSecClassGenericPassword forKey:(__bridge id)kSecClass];
CFTypeRef attributes;
OSStatus keychainError = SecItemCopyMatching((__bridge CFDictionaryRef)(queryDictionary), &attributes);
if (keychainError == errSecSuccess)
{
NSDictionary *returnedDictionary = (__bridge_transfer NSDictionary *)attributes;
NSData *rawData = [returnedDictionary objectForKey:(__bridge id)kSecValueData];
return [[NSString alloc] initWithBytes:[rawData bytes] length:[rawData length] encoding:NSUTF8StringEncoding];
}