I'm trying to use Keychain Services to save a value, which would persist even if a user reinstalls the app. So I check if an item exists using SecItemCopyMatching, which returns errSecItemNotFound the first time and add a new item using SecItemAdd, which returns errSecSuccess, but the value of _attrs is nil. Also, when the code is called second time, the SecItemCopyMatching still returns errSecItemNotFound, as if SecItemAdd wasn't called. So what that could be related to?
CFMutableDictionaryRef _attrs = nil;
NSString* key = #"<unique key>";
NSMutableDictionary* query = [NSMutableDictionary dictionary];
query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
query[(__bridge id)kSecAttrLabel] = key;
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
query[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef*)&_attrs);
if (err == errSecSuccess) {
return YES;
}
NSString* str = #"<some data>";
if (err == errSecItemNotFound) {
query[(__bridge id)kSecValueData] = NSData_from_string(string_from_NSString(str));
query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAlways;
err = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef*)&_attrs);
assert(err == errSecSuccess);
}
You are re-using query for your call to SecItemAdd, and the kSecMatchLimit value being present in the dictionary for this function is breaking it. You should remove this key before calling SecItemAdd.
It's also worth noting that [str dataUsingEncoding:NSUTF8StringEncoding] might be a better choice than whatever NSData_from_string(string_from_NSString(str)) is, depending on what you're doing.
Related
I want to get a dictionary of attributes of Keychain, but I got an NSArray with 1 element of NSDictionary. Here's my code of getting attributes:
NSMutableDictionary *queryDictionary = [KeychainQueryDictionaryWithServiceAndIdentifier(serviceName, identifier) mutableCopy];
queryDictionary[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
queryDictionary[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryDictionary, (CFTypeRef *)&result);
if (status != errSecSuccess) {
NSLog(#"Unable to fetch account info with identifier \"%#\" (Error %li)", identifier, (long int)status);
return nil;
}
id ret = (NSDictionary *)CFBridgingRelease(result);
And here's some infomation from console
Why ret is an NSArray type?
queryDictionary[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
Fixed it!
I'm finding that sometimes I get that error when trying to get a secure item I previously succesfully stored by using Keychain API. I have found it when the app running in my device was in background state and I locked the screen. The device hasn't lock code set, and this is the function I'm calling:
+ (NSString *)findValueForKey:(NSString *)keyStr
{
NSString *valueStr = #"";
if ((keyStr != nil) && (![keyStr isEqualToString:#""])) {
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *query = #{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service,
(__bridge id)kSecAttrAccount : keyStr,
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue};
CFDataRef cfValue = NULL;
OSStatus results = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&cfValue);
if ([self checkIfNoError:results]) {
valueStr = [[NSString alloc] initWithData:(__bridge_transfer NSData *)cfValue encoding:NSUTF8StringEncoding];
}
else {
NSLog(#"%#", [self getErrorMessageForStatus:results]);
}
}
return valueStr;
}
and the item was stored by calling this method:
+ (BOOL)storeInKeychainWithKey:(NSString *)keyStr withValueStr:(NSString *)valueStr
{
if ((keyStr != nil) && (![keyStr isEqualToString:#""]) &&
(valueStr != nil) && (![valueStr isEqualToString:#""])) {
NSData *valueData = [valueStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *secItem = #{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service,
(__bridge id)kSecAttrAccount : keyStr,
(__bridge id)kSecValueData : valueData};
CFTypeRef result = NULL;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)secItem, &result);
NSLog(#"%#", [self getErrorMessageForStatus:status]);
return [self checkIfItem:status];
}
else {
return NO;
}
}
I thought that keychain items were always accessible in iOS... this post seems to be about something similar but I'm not sure if it is is deprecated and how should I solve this...
Thanks in advance
We had the same issue, #AppsDev, and the post you mention is accurate. We solved it by keeping the keychain as a last resort for things we need available even if we uninstall / reinstall the app.
We now loop back on the app defaults (in Swift 3 that would be UserDefaults.standard) to keep that info handy while the install lifecycle doesn't reach the "uninstalled" stage.
If uninstalled, next time is installed we go to the keychain (by definition, a just installed app isn't in background so it won't fail). With the data retrieved we refresh the app defaults and, from there, we just use the app defaults.
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)
I have a strange problem with the KeyChainItemWrapper.
I used to create 2 of them like so:
KeychainItemWrapper *kcWrapper = [[KeychainItemWrapper alloc] initWithIdentifier:#"first" accessGroup:nil];
KeychainItemWrapper *kcWrapper1 = [[KeychainItemWrapper alloc] initWithIdentifier:#"second" accessGroup:nil];
I would then set an object like this:
[kcWrapper setObject:#"something" forKey:(__bridge id)kSecValueData];
[kcWrapper1 setObject:#"something1" forKey:(__bridge id)kSecValueData];
This worked perfectly, until... nothing changed?
Now I get this error:
*** Assertion failure in -[KeychainItemWrapper writeToKeychain],
/Users/wingair/Documents/iOSProjects/tulipstempelkort-
ios/stempelkort/../KeychainItemWrapper.m:305
Line 305 is:
// No previous item found; add the new one.
result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
NSAssert( result == noErr, #"Couldn't add the Keychain Item." );
Been stuck here for quite some long time, any help is appreciated.
The writeToKeyChain function:
- (void)writeToKeychain
{
CFDictionaryRef attributes = NULL;
NSMutableDictionary *updateItem = nil;
OSStatus result;
if (SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
{
// First we need the attributes from the Keychain.
updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)attributes];
// Second we need to add the appropriate search key/values.
[updateItem setObject:[genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
// Lastly, we need to set up the updated attribute list being careful to remove the class.
NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
[tempCheck removeObjectForKey:(__bridge id)kSecClass];
[tempCheck removeObjectForKey:(__bridge id)kSecAttrAccessGroup];
// An implicit assumption is that you can only update a single item at a time.
result = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck);
NSAssert( result == noErr, #"Couldn't update the Keychain Item." );
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;
}
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