I am using SecItemCopyMatching to access the iOS keychain. About 1 in a hundred times I get a -34018 result code right after relaunching the app from the background. The documentation states:
The assigned error space for Keychain Services is discontinuous:
–25240 through –25279 and –25290 through –25329. Keychain Item
Services may also return noErr (0) or paramErr (–50), or CSSM result
codes
So it seems that -34018 is a 'CSSM result code'. I have followed the suggested link but could not find result codes.
What it the -34018 result code? How can I get more reliable keychain access?
- (NSData *)getKeychainData:(NSString *)key
{
NSDictionary *query = #{
(__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService:SEC_ATTR_SERVICE,
(__bridge id)kSecAttrAccount:key,
(__bridge id)kSecReturnData:#YES
};
CFDataRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if(status == errSecItemNotFound) {
return nil;
}
if(status == noErr) {
return CFBridgingRelease(result);
} else {
[self logError:[NSString stringWithFormat:#"SecItemCopyMatching status %d", (int)status] :nil];
return nil;
}
}
After some research, I found this: http://opensource.apple.com/source/Security/Security-55471/sec/Security/SecBasePriv.h
So -34018 is errSecMissingEntitlement and the comment says
Internal error when a required entitlement isn't present.
Do you experience this error while running your unit tests? If so, this might help: https://stackoverflow.com/a/22305193/171933
This issue on github says that it only seems to happen while debugging from Xcode: https://github.com/soffes/sskeychain/issues/97 (also see https://stackoverflow.com/a/28256591/171933)
Hopefully some of this will help!
I've been just researching the same error.
The gist of it is that the security service apple uses in order to communicate with the key chain, in rare cases, when the user's device is low on memory, crashes and taking away the app ability to talk to the keychain which results the dreadful -34018.
This is not happening only while running through Xcode like some may claim.
This is the most recent data regarding the issue taken from the Apple developer forums by one of the Apple staff:
UPDATE: We have finally been able to reproduce the -34018 error on iOS
8.3. This is the first step in identifying the root cause and then coming up with a fix.
As usual, we can't commit to a release timeframe, but this has
affected many developers and we really want to get this resolved.
Earlier I suggested adding a small delay in
application:didFinishLaunchingWithOptions and
applicationDidBecomeActive: before accessing the keychain as a
workaround. However, that doesn't actually appear to help. That means
that there's no known workaround at this time other than relaunching
the app.
The issue appears to be related to memory pressure, so perhaps being
more aggressive in handling memory warnings may alleviate the problem.
From Another Apple staff member:
Keychain engineering is well aware of how important this issue is.
The primary problem has been reproducing the failure here at Apple.
We're now able to do that (largely thanks to the work you guys have put in filing and following up on your bug reports).
From Another Apple staff member on Mar 22, 2016:
OK, here’s the latest. This is a complex problem with multiple
possible causes: Some instances of the problem are caused by incorrect
app signing. You can easily distinguish this case because the problem
is 100% reproducible. Some instances of the problem are caused by a
bug in how iOS supports app development (r. 23,991,853). Debugging
this was complicated by the fact that another bug in the OS (r.
23,770,418) masked its effect, meaning the problem only cropped up
when the device was under memory pressure. We believe these problems
were resolved in iOS 9.3. We suspect that there may be yet more causes
of this problem. So, if you see this problem on a user device (one
that hasn’t been talked to by Xcode) that’s running iOS 9.3 or later,
please do file a bug report about it. Try to include the device
system log in your bug report (I realise that can be tricky when
dealing with customer devices; one option is to ask the customer to
install Apple Configurator, which lets them view the system log). And
if you do file a bug, please post your bug number, just for the
record. On behalf of Apple I’d like to thank everyone for their
efforts in helping to track down this rather horrid issue. Share and
Enjoy
Unfortunately there are no known workarounds and the issue is still not fixed in 9.3.2 Beta 1 (13F51a)
This code works for me:
static const UInt8 kKeychainItemIdentifier[] = "com.apple.dts.KeychainUI\0";
- (NSData *)getKeychainData:(NSString *)key
{
NSData *keychainItemID = [NSData dataWithBytes:kKeychainItemIdentifier length:strlen((const char *)kKeychainItemIdentifier)];
NSDictionary *query = #{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: SEC_ATTR_SERVICE,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge id)kSecAttrGeneric: keychainItemID
};
CFDataRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if(status == errSecItemNotFound) {
return nil;
}
if(status == noErr) {
return CFBridgingRelease(result);
} else {
[self logError:[NSString stringWithFormat:#"SecItemCopyMatching status %d", (int)status] :nil];
return nil;
}
}
The main difference with OP's code is the addition of a Generic Attribute to the query. The Keychain Item Identifier is the default from apple. The reason behind this comes to differentiate possible different keychain items from each other. This is one way to make a more the keychain items access more reliable. Basically, in other words, this makes sure you access apple's default keychain.
After trying many of the fixes in stack overflow, things still didn't work for me.
What worked was switching the Keychain Sharing Capability in Xcode. Built and run and it worked right away.
Related
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."
I have the following codesnippet in my app.
CFTypeRef result = nil;
OSStatus userPresenceStatus = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
SecItemCopyMatching returns -25300. It happens on some iPhone 6 devices but not on every device. What does this code mean ? What can be the cause ?
Thanks for your advice
Frank
Error 25300 is errSecItemNotFound, which means that there is no such item in your key chain.
And you can find a list of potential result codes for Keychain Services here.
I'm guessing the keychain item you are trying to match against doesn't exist on that particular device.
Faced one more condition which can return an error -25300. Make sure that the data passed to kSecMatchIssuers or kSecMatchSearchList is of the same type. Loop through all filters and for each call SecItemCopyMatching
This is how you find the problematic element!
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.
I was developing an app, at some stage this error came out:
-[__NSArrayM popObjectForKey:]: unrecognized selector sent to instance
I've undone the latest edits to see what was wrong...but the error was still there.
So I've undone even more edits, but the error was still there.
Tried to delete and add back the framework where I thought the error could come from, error still there. (it's KinveyKit framework)
Tried to substitute the line of code where the app would throw the error with another one that was surely working: even that one was throwing the error.
I downloaded an older Git of the app (that was definitely working a few days ago) on a different folder, run that and: same error.
Uninstalled and reinstalled Xcode: error still there.
But: if I run other apps with similar code, nothing goes wrong.
Does anybody have any idea?
I post here the code, that throws me the error, just to give you an idea...but I don't think there's anything wrong in the code since it's always been working before.
.h
#property (strong, nonatomic) KCSAppdataStore *store;
.m
- (void)viewDidLoad
{
...
_store = [KCSAppdataStore storeWithOptions:#{ KCSStoreKeyCollectionName : #"AnEntity",
KCSStoreKeyCollectionTemplateClass : [AnEntity class]}];
[_store queryWithQuery:[KCSQuery query]
withCompletionBlock:^(NSArray *objectsOrNil1, NSError *errorOrNil) { ... }];
...
}
Cool -- being sarcastic
I spent the afternoon rebuilding the app from a new project, copy pasting the code...
Now I opened the older one once again, tried to run it just...just to try once again, knowing it would crash cause I haven't change a word it already crashed every other time I tried earlier this morning, and.... surprise: runs again. No errors.
-.-'' Solution to my question then? No idea. Still thinking it was some cache problem that eventually got solved through out the day but not while I was trying to solve it.
If anybody has had similar experiences or has some further idea, feel free to add something, always good to be prepared for next time something like this happens.
I had a similar issue that appeared to be caused by a corrupted KCSUser object written to the keychain. The popObjectForKey method appears to get called any time you call [KCSUser activeUser]. I was able to get this fixed by adding the code below first thing in application DidFinishLaunchingWithOptions:. Be sure you aren't calling [KCSUser activeUser] at any point before the following code has a chance to run.
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);
}
This appears to clear/reset the keychain. After you run it once, remove it and you app should operate normally.
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.