KeychainItemWrapper not getting loaded on iOS 9 OSStatus -34018 (errSecMissingEntitlement) - ios

I've been using KeychainItemWrapper just fine. But since I've updated my phone to iOS 9, it doesn't store the sessionID for some reason.
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
{
NSMutableDictionary *dictionary = [self setupSearchDirectoryForIdentifier:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:valueData forKey:(__bridge id)kSecValueData];
// Protect the keychain entry so it's only valid when the device is unlocked at least once.
[dictionary setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
// **THIS LINE OF CODE RETURNS -34108**
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
// If the addition was successful, return. Otherwise, attempt to update existing key or quit (return NO).
if (status == errSecSuccess) {
return YES;
} else if (status == errSecDuplicateItem){
return [self updateKeychainValue:value forIdentifier:identifier];
} else {
return NO; **The call returns here...**
}
}
Anybody know whats going on?
EDIT
Weirdest thing: it only happens from time to time and always in debug mode.
EDIT2
As this only occurs in debug mode, there are two work arounds that I usually do depending on the type of variable:
- Always keep the last valid variable loaded from the keychain locally (for instance a sessionID) and use it as a backup when in debug mode
- Ignore invalid value(s) if possible when in debug (in this case I would add an additional control variable to allow/disallow these invalid value(s))
(use #ifdef DEBUG to check if you're in debug mode)

According to Apple there is no solution right now.
https://forums.developer.apple.com/thread/4743

Related

SecItemCopyMatching is returning nil

I have this method
- (NSString *)bundleSeedID:(NSError **)error __attribute__((annotate("oclint:suppress"))) {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass,
BUNDLE_SEED_ID, kSecAttrAccount,
EMPTY_STRING, kSecAttrService,
(__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, (__bridge NSString *)kSecAttrAccessible,
(id)kCFBooleanTrue, kSecReturnAttributes,
nil];
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound) {
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
}
if (status != errSecSuccess) {
if (error) *error = [ErrorUtils bundleSeedErrorWithCode:(int)status];
return nil;
}
NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
CFRelease(result);
return bundleSeedID;
}
and I don't understand why sometimes result is null.
Note: This code is always being executed in main thread without any asynchronous call
Can you please help me understand why is this happening? This code is only executed when I open the app.
I know I could check if result is null and then I won't CFRelease(result); but it's not expected to happen this. Once the app crashes, if I open it again, everything is ok.
Thanks in advance 🙏
You are very likely launching in the background when the device is locked. While the device is locked, you don't have access to certain protected data in the keychain; you should see this in the error message.
You do appear to be passing kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly to the creation, it's possible that you added kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly to your app after creating the keychain items (or that you don't pass this during creation elsewhere).
Your code checks for errSecItemNotFound, but you should be checking for errSecDataNotAvailable if I recall correctly to detect this problem. (You can also check UIApplication's `isProtectedDataAvailable.) You shouldn't try to create an item in that case of course; you just need an elegant way to fail.
It's fairly rare to be launched before first unlock, but that's also possible, and the keychain won't be available. Pending Bluetooth connections, for example, can cause this as I recall. I don't believe that push notifications can launch the app in that state, but it's possible.
In any case, you need to be checking for unexpected errors and handling a nil value.

iOS Keychain randomly returning -25300

I am facing a weird issue. Quite similar to one asked here, but not answered: Read from keychain results in errSecItemNotFound 25300
My code saves a string password in the iOS keychain to be accessed later on. It works just fine most of the times and I am able to fetch the password back after reinstallation or device restart or both.
Problem: Sometimes which is actually rare and hard to reproduce, it does not return the password and instead it returns null and error status:-25300(errSecItemNotFound). Another thing is that this problem got prominent after iOS 9 update. Happening on iOS 9.1 too.
Now, I have been searching the web for a solution. Found the following, which somehow relate to the issue, but do not address to my scenario:
iOS Keychain Data Lost Upon iPhone Memory Pressure?
https://forums.developer.apple.com/thread/4743
iOS KeyChain not retrieving values from background
Has anyone got any ideas why this is happening? Many thanks.
Updated
Code for setting:
NSMutableDictionary *query = [self _queryForService:service account:account];
[query setObject:password forKey:(__bridge id)kSecValueData];
status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
}
if (status != errSecSuccess && error != NULL) {
*error = [NSError errorWithDomain:kAppKeychainErrorDomain code:status userInfo:nil];
}
return (status == noErr);
Final query:
{
acct = user;
class = genp;
svce = "myBundleIdentifier";
"v_Data" = <36314541 38463339 2d363737 462d3445 34372d42 4339452d 31324633 46463937 35374546>;}
Code for fetching:
CFTypeRef result = NULL;
NSMutableDictionary *query = [self _queryForService:service account:account];
[query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
if (status != errSecSuccess && error != NULL) {
*error = [NSError errorWithDomain:kAppKeychainErrorDomain code:status userInfo:nil];
return nil;
}
return (__bridge_transfer NSData *)result;
Final query:
{
acct = user;
class = genp;
"m_Limit" = "m_LimitOne";
"r_Data" = 1;
svce = "myBundleIdentifier";}
I can see that the question is old, but I have recently almost gone mad trying to solve a similar issue with Keychain, so I will share it in case anyone faces it.
The problem was that the app would randomly crash when writing to the keychain in the background. And the reason is that when the user has a passcode on their phone and selected access level is the safest, iOS will not allow your application to make changes in the keychain while it's protected with a passcode.

Hiding the TouchId popup on SecItemUpdate

I have seen in some touchId apps where on SecItemUpdate the touchId UI screen never pops up and the update still happens. I need similar functionality for my app and based on what I have read from Apple Developer's guide (my understanding maybe wrong) have come up with some options but they don't seem to work. Here's what I have done so far.
Setting kSecUseNoAuthenticationUI to YES, an error code -25308 is returned. Setting kSecUseNoAuthenticationUI to NO, an error code -50 is returned. If I don't include kSecUseNoAuthenticationUI, then the default authentication UI pops up.
NSDictionary *query = #{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: #"SampleService",
(__bridge id)kSecUseNoAuthenticationUI: #YES
};
NSDictionary *changes = #{
(__bridge id)kSecValueData: [#"UPDATED_SECRET_PASSWORD_TEXT" dataUsingEncoding:NSUTF8StringEncoding]
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes);
NSString *msg = [NSString stringWithFormat:NSLocalizedString(#"SEC_ITEM_UPDATE_STATUS", nil), [self keychainErrorToString:status]];
[super printResult:self.textView message:msg];
});]
So I am lost at this point. Appreciate if you can give me some pointers on how to disable this touchId UI popup on SecItemUpdate. Thanks
If you take a look at video WWDC 2014 Session 711, the kSecUseNoAuthenticationUI is mentioned around 31:35.
You can look also in "SecItem.h" :
#constant kSecUseNoAuthenticationUI Specifies a dictionary key whose value
is a CFBooleanRef. If provided with a value of kCFBooleanTrue, the error
errSecInteractionNotAllowed will be returned if the item is attempting
to authenticate with UI.
I'm not sure you can both disable the pop-up and perform an update.
What I suppose to understand : setting the kSecUseNoAuthenticationUI option will not display the pop-up. But if you 're trying to access to an item that requires an authentication, it will fail by indicating you that the item by returning a errSecInteractionNotAllowed as the operation result
According to the iOS8 release notes, this supposed is the case and you should delete and re-add your item
if (status == errSecDuplicateItem) { // exists
status = SecItemDelete((__bridge CFDictionaryRef)attributes);
if (status == errSecSuccess) {
status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
}
}

How can I detect if an iOS app is a new install or had been installed and deleted previously

How can I detect if an iOS app is installed for the first time or was deleted and re-installed?
I do use NSUserDefaults when the app is running. However, when the app is deleted, I believe all associated data is deleted.
My goal is to do SMS verification of the users phone number only when the app was installed for the first time on the device.
If for some reason, the app was deleted and re-installed, I want to avoid redoing the SMS verification.
What else can I do? Can I store some metadata related to my app which is not deleted when the app itself is deleted on the device?
Any standard pattern to follow?
You can do this by storing a value in the user's keychain. It will persist even if the app is deleted and thus you can tell if the app is a new install or a reinstall. Add another value to the user defaults for comparison, if both values exist the app has been installed and executed at least once. If neither values exist, it's a new, first time install. If just the keychain value exists, it's a fresh reinstall.
You can use keychain and keychain does store values after we uninstall the app.
Apple has provided KeyChainItemWrapper class in their GenericKeyChain (sample code)
Try this code of storing your device code in keychain.....and keychain value remains the same even if app is deleted:
static NSString *serviceName = #"com.mycompany.myAppServiceName";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSData *passwordData = [self searchKeychainCopyMatching:#"device--code"];
if (passwordData) {
NSString *password = [[NSString alloc] initWithData:passwordData
encoding:NSUTF8StringEncoding];
NSLog(#"pasword:=%#",password);
}
else{
[self createKeychainValue:devicecode forIdentifier:#"device--code"];
}
//[self deleteKeychainValue:#"device--code"];
return YES;
}
- (void)deleteKeychainValue:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
SecItemDelete((CFDictionaryRef)searchDictionary);
}
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
[searchDictionary setObject:serviceName forKey:(id)kSecAttrService];
return searchDictionary;
}
- (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
// Add search attributes
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
// Add search return types
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
NSData *result = nil;
// OSStatus status =
SecItemCopyMatching((CFDictionaryRef)searchDictionary,(__bridge CFTypeRef *)&result);
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary,
(void *)&result);
return result;
}
- (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:passwordData forKey:(id)kSecValueData];
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
Source
There is no way to accomplish this with iOS alone, but you could achieve this by having a server with a database, and the first time the app is run you send the device's UDID to your server; if the server already has the UDID, then the app was already installed.
Of course this solution does require the user have an internet connection.
I think, you send UDID to icloud Apple to store it. If app install first time. Icloud iphone not found, else It will have UDID. You need't server.

Unable to identify iOS OSStatus Code

I have a really strange behavior in an iOS App.
I switched from iOS 6 to iOS 7. In iOS 6 everything worked perfectly.
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
[searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
[searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
return searchDictionary;
}
- (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
[searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
CFDataRef dataRef;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary,
(CFTypeRef *)&dataRef);
if (status != errSecSuccess) {
#ifdef DEBUG
NSLog(#"%s - No OSStatus errSecSuccess. Caused by SecItemCopyMatching", __PRETTY_FUNCTION__);
#endif
return nil;
}
NSData *result = (__bridge_transfer NSData *)dataRef;
return result;
}
When the App starts the - (NSData *)searchKeychainCopyMatching:(NSString *)identifier function loads the values from the keychain. Everything works fine for a while. But after about 15 successful value requests I get an error.
OSStatus Code -34018
The SecItemCopyMatching function returns that error code. The documentation says
#result A result code. See "Security Error Codes" (SecBase.h).
But looking in the SecBase.h there are only these OSStatus codes specified.
enum
{
errSecSuccess = 0, /* No error. */
errSecUnimplemented = -4, /* Function or operation not implemented. */
errSecIO = -36, /*I/O error (bummers)*/
errSecOpWr = -49, /*file already open with with write permission*/
errSecParam = -50, /* One or more parameters passed to a function where not valid. */
errSecAllocate = -108, /* Failed to allocate memory. */
errSecUserCanceled = -128, /* User canceled the operation. */
errSecBadReq = -909, /* Bad parameter or invalid state for operation. */
errSecInternalComponent = -2070,
errSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
errSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
errSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
errSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
errSecDecode = -26275, /* Unable to decode the provided data. */
errSecAuthFailed = -25293, /* The user name or passphrase you entered is not correct. */
};
The values doesn't get overridden, already checked.
And last but not least the search dictionary:
Edit - new info
I was debugging the whole day and I found some news. I'm downloading a Zip-File containing an executable Bundle. This is a In-House App so no worries about point 2.7 and 2.8 in the review guidelines. After successfully loading the bundle the entitlements error appears.
NSBundle *bundle = nil;
NSError *error = nil;
bundle = [[NSBundle alloc] initWithPath:bundlePath];
if (!bundle) {
return nil;
}
// Here i can access the keychain as usually
[bundle loadAndReturnError:&error];
// Well here it suddenly doesn't work anymore
// error is also nil
Well the bundle code inside does not use the keychain. May be this is some kind of security logic? Any clues?
This error indicates a problem with your app's entitlements. Found this: The cause is often that the App Identifier Prefix in the app's entitlements doesn't match the App Identifier Prefix in the provisioning profile.
To verify, use the codesign tool to view your app's entitlements:
codesign -d --entitlements - MyApp.app/
Then, compare the App Identifier Prefix to that in the provisioning profile:
cat MyApp.app/embedded.mobileprovision

Resources