I have this code getting back a password from the keychain for a given username NSString:
NSError *error = nil;
NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
NSString *pw = [SFHFKeychainUtils getPasswordForUsername:username andServiceName:appName error:&error];
if(error != nil)
// log the error
Most of the time for most users this all works fine - but for some specific users this call seems to fail (and carry on failing) where it returns the following error:
The operation couldn’t be completed. (SFHFKeychainUtilsErrorDomain error -25308.)
This is apparently errSecInteractionNotAllowed - which from what I've read I think this means some kind of user interaction is required for the keychain to be accessed.
Does anyone have any idea why this call may be failing for some specific users only? This keychain entry is specific to my app - so why would any user interaction be required to access it?
Any pointers much appreciated...
OK so I worked this out finally.
Eventually I worked out the users who were having problems had set a lock code on their phone. If the phone was locked the keychain system was returning this -25308 error.
If you only ever need to access the keychain when the app is active in the forground you would never see this issue - but if you need to carry on processing when the phone is locked or if the app is in background then you would see it.
Elsewhere I'd read that the default access attribute for the kechain system is kSecAttrAccessibleAlways - but I think that is out of date. It seems the default access attribute for the keychain system is such that when the phone is locked with a pin code then the items are unavailable.
The fix for this is to change the SFHFKeychainUtils code to set a specific kSecAttrAccessible attribute on the keychain items it manages (which the original code did not do - presumably as it pre-dated these attributes).
This wordpress updated version of the SFHFKeychainUtils code has the fixes in it - search for kSecAttrAccessible to see where they have added the accessible attribute code.
Hope this helps anyone else running into this...
I was having this issue in iOS 14 with Widgets extensions that are accessing to the keychain to get the JWT token to call some rests.
Apparently, widgets tried by default to update also when the device is locked and the keychain item I was trying to use was not accessible.
After setting this attribute to the keychain element (swift 5 code), everything seems to be working:
keychainItem[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
Related
I have a code like this in my app:
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
In most cases it works, and returns the Bundle version, but sometimes (let's say in 2% of cases) it returns nil.
The code is called within the function [AppDelegate application:didFinishLaunchingWithOptions:], in Main thread, an app is in foreground.
I could imagine this being an Apple's bug with some file reading error, but the percentage is quite high as for a rare Apple bug.
Also I know a one could mess with versions/bundles/Info.plist - but the percentage is too small for such case.
So, the first question: what can be the reason of [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey] returning nil in this case?
The second question: do you know if these hypotheses make sense / are easy to check:
The user launches an app the first time after update, and [NSBundle mainBundle] becomes fully configured after application:didFinishLaunchingWithOptions ?
The app is in the process of an auto-update (from AppStore), the user opens it, and the system is currently writing a new data to Info.plist.
Some background thread in my app is also reading the [NSBundle mainBundle], the system uses some weird lock, so the read from a Main thread fails.
UPD: I've seen this question, but it's not related.
By default all files of the application, on a device that uses content protection, are encrypted. If you try to read them before they are decrypted(by the OS) you will get nil. The files are decrypted and they are available shortly after the user unlocks her phone, this might change and be more strict if you set a different value for the data protection entitlement. So the data might not be available when the application launches because they are not yet decrypted. This might be one reason that you get nil some times, the solution would be to wait to be notified in the app delegate that they data are ready before you read them.
The answers to all parts of the second questions are NO. There is no documented configuration that the app needs to do after an update that might affect the main Bundle. The user can not open an application if it is in the process of updating. The access to the main Bundle is thread-safe.
I want to check whether or not my application was previously installed on a particular device. Is there any programmatic way to do so?
You can use keychain. but when user reset his/her phone this value will be lost .
I think there is no option is available to such case except you have to use iAd in your app. apple provide unique id for that that will remain constant after reinstall application.
OPTION #1:
You can use NSUserDefaults or save the flag to keychain, in order to not lose it after app uninstall, and check in application:didFinishLaunchingWithOptions: method:
BOOL installFlag = get it from user defaults or keychain;
if (installFlag)
{
//App was installed
}
else
{
//This is the first install ever
installFlag = YES;
//Here save the YES value to user defaults or keychain
}
If you will save the flag to keychain, then use a Keychain Wrapper, like this
one.
The weak part of this option is that even if keeping the flag in keychain, the value could be lost, after reseting device settings, that's why I would prefer the following option:
OPTION #2:
Keep the flag remotely on a server database, as a key you can use device identifier:
NSString *uniqueIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
And you will have to check, if there is a record in your database with current device identifier, then app was already installed, if no then add the flag and the identifier to the database. (But you will need internet connection every time).
You can use keychain to keep log of your app previously installed or not.
I use [NSUserDefaults standardDefaults] to store a boolean to see if it is the first time that application is being launched ... If so, the app should show a registration window.
It was working fine till last week, but now, sometimes when I switch to other apps and come back after a little while, I see that registration page loads while it shouldn't.
I used NSLog to see what is stored in [NSUserDefaults standardDefaults] and I see that the values I stored has been set to nil (null) while I haven't done that anywhere in my code.
Does anyone know why the values do reset ?
P.S: Actually the values are not permanently lost , because if I don't do anything in registration page and quit the app instead, It will launch normally the next time I enter the app !!!
A long time ago I encountered this issue, turns out a third party library that I was using uses the same key when storing values to NSUserDefaults. Try searching your project for this key, maybe something else is resetting it.
Here are the ways I know about to lose values in NSUserDefaults, in order of likelihood:
The key was never saved in the first place
Your app deletes it later on
The app is deleted and reinstalled
Another app overwrites or removes the key
The phone or simulator is reset
During the night, the phone is replaced by an identical-looking, different phone
It sounds like, from the discussion here, that you've ruled out 1,2,4, and probably 3 & 5. The only next debug step I can think of is to store the test phone in a locked drawer at all times.
But I'd leave my money on an intermittent problem causing #1. For that, we'd need posted code to investigate.
EDIT -
A high % of NSUserDefaults problems posted here are about storing BOOLs and other scalar types. It looks like the OP knows about wrapping in NSNumbers, but BOOLS in particular are fraught because it's easy to confuse false-y values like NO no and nil, and NSNull instance.
Let's throw that on the list for this question at #2.5. There again, would need code to confirm.
If this is happening while testing, it's normal. The fact that the program is even making this decision (should I show the registration page?) suggests that the app has been forcibly quit and is starting from scratch. When testing, this can result in clearing out the app sandbox as the app is reloaded from Xcode. In the real life of a real user, however, that won't happen (unless the user deletes the app from the device).
Make sure you are calling [[NSUserDefaults standardUserDefaults] synchronize] just after setting preferences and you are not overwriting you preferences.
I would like the determine if I have access to files that have been saved with the attribute NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication.
I have tried [UIApplication sharedApplication].protectedDataAvailable, however from my tests it will return NO whenever the device is locked (if a pin code is set) even if the user has unlocked the device at least once since last starting the phone.
It says quite clearly in the docs: The value of this property is NO if data protection is enabled and the device is currently locked, and in this case files that were assigned the NSFileProtectionComplete or NSFileProtectionCompleteUnlessOpen protection key cannot be read or written by your app. i.e. this is not the correct property.
You need use one of the multi-tasking keys that causes the launch the app on boot - e.g. the voip key for UIBackgroundModes.
Mind you, you're pretty much testing the OS at that point - if you set the keys appropriately when creating the files it should work as advertised. If it doesn't then log a radar.
I have an app in the app store and am using a logging service to get crash logs and associated log data. I am seeing an intermittent crash (low # of users affected and low # of crashes per user) but it is baffling me.
What happens in these crashes is the following:
App launches and initializes Core Data stack
App attempts to add a SQL store to the NSPersistentStoreCoordinator with the following code (storeURL is valid):
NSDictionary *options = #{
NSMigratePersistentStoresAutomaticallyOption : #(YES),
NSInferMappingModelAutomaticallyOption : #(YES)
};
sqlStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error];
One of the following errors occur when adding this store:
NSError:
Domain=NSCocoaErrorDomain
Code=256 "The operation couldn’t be completed. (Cocoa error 256.)"
UserInfo=0x1dd946a0 {NSUnderlyingException=authorization denied, NSSQLiteErrorDomain=23}
or
NSError:
Domain=NSCocoaErrorDomain
Code=256 "The operation couldn’t be completed. (Cocoa error 256.)"
UserInfo=0xc6525d0 {NSUnderlyingException=disk I/O error, NSSQLiteErrorDomain=10}
After this condition, the app will crash b/c the SQL store is required for the app to function. I could attempt to gracefully handle this failure by trying a new storeURL but I don't want the user to lose existing data. Also, I have never personally reproduced this issue and based on the low number of users affected and crash logs I believe it is a low impact problem and does not recur on a subsequent app launch.
I'm hoping there's a Core Data guru out there with some suggestions on how to debug and prevent/handle these conditions. My core data stack initialization code is straight from the xcode project generator and I have ruled out any concurrency issues in that the persistent store coordinator is only initialized once (on launch) and this error occurs in this initialization.
Happy to provide more code/info if relevant.
thanks!
It looks like XJones and I have been able to locate the cause of this. It appears to be an iOS edge-case bug or undocumented behavior. I've filed this under Apple bug ID 12935031.
There is an unaccounted for scenario where an application using Core Location significant location change or region monitoring can fail to launch properly (or have other unintended consequences) due to the fact that as of iOS 5, Core Data stores use data protection (encryption) by default.
Steps to Reproduce:
1) Turn on pass code protection on the device
2) Create an application that starts Significant Location Monitoring or Region Monitoring and keeps it started even when in the background. Ie. An app that uses background significant location change or region monitoring.
3) Wait for the battery on the device to run out (there may be other causes as well)
4) The device will shut down
5) Connect the device to the Mac
6) Once the charge is adequate, the device will boot. Important: Do not unlock device at this time.
7) Exit or enter the monitored range or cause a significant change in location to occur. The device will now automatically relaunch the application, because it registered for Significant Location Monitoring or Region Monitoring
8) However, since the device has not been unlocked by the user (pass code not yet entered), the application will not be able to read any of its protected data files. In a Core Data application, this will cause the persistent store coordinator to fail to add the persistent store file to the managed object context. This will cause the app to crash or depending on the code used by the developer even attempt to reset the database. In other apps it may cause crashes for other reasons as this is an unexpected, undocumented side effect of the data protection feature being turned on by default for Core Data stores in iOS 5 and later.
The solution or workaround until Apple corrects this or at least documents it is to either make sure your application stops monitoring significant location changes/regions in applicationWillTerminate or to turn off the default data protection feature by setting NSFileProtectionNone for the NSFileProtectionKey key in the options dictionary when adding the Core Data store to the persistent store coordinator. to wait for the file store to become available using a while() loop that checks for the protected data to become available. There may be other ways of doing this using KVO, but this method works reliably and is easiest to insert into existing code without reworking your entire application startup process.
Update: looks like just setting that key is not enough if data protection is already active on the store. You have to manually set it:
[[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObject:NSFileProtectionNone forKey:NSFileProtectionKey] ofItemAtPath:storePath error:nil];
Here's the fix for apps that need background location monitoring, thanks to more input from XJones and a bit more research in Apple's scattered docs:
while(![[UIApplication sharedApplication] isProtectedDataAvailable]) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]];
}
This code goes in before you attempt to add the store data file to the persistent store coordinator.
Update 2015: You can also set NSPersistentStoreFileProtectionKey to NSFileProtectionNone. This will properly disable file protection (if you don't need it) and just work without requiring any workarounds. The reason it did not work in previous attempts is because the dictionary key I was testing with was incorrect.
I just noticed the following notifications in the UIApplication docs:
UIApplicationProtectedDataDidBecomeAvailable
UIApplicationProtectedDataWillBecomeUnavailable
These are available in iOS 4.0 and later. #lupinglade, I think you need to observe these notifications and only access your protected files (i.e. the store) after you recieve UIApplicationProtectedDataDidBecomeAvailable.
The solution #lupinglade pretty much worked in my case (VoIP app had when starting up from a reboot), but I would also like to point out that there's a delegate function for AppDelegate that is called when protected data becomes available:
applicationProtectedDataDidBecomeAvailable:
This made it a bit easier to implement the solution in my case.
According to the header file it's available from iOS 4 onwards.
Same problem here, still have not been able to find a solution. I've found it seems to be related to having a lock code set on the device. WIthout the lock code I could never reproduce the error, now I have been able to a bunch of times. Console log is:
Error is: Error Domain=NSCocoaErrorDomain Code=256 "The operation couldn’t be completed. (Cocoa error 256.)" UserInfo=0x1fd80110 {NSUnderlyingException=authorization denied, NSSQLiteErrorDomain=23}
File does exist and not using any encryption.