We have been struggling with a sporadic issue in our iOS app for some time now, where it seems that occasionally our application will be launched and the keychain is not yet accessible. We have not found a way to reproduce this reliably.
As do many apps, we save some sensitive data (i.e., user credentials) to the keychain using kSecAttrAccessible = kSecAttrAccessibleWhenUnlocked. The app will read those values from the keychain during startup in the didFinishLaunchingWithOptions override of our app delegate. This works fine the large majority of the time.
But sometimes our users will report problems that indicate that the keychain values could not be read. If the user simply kills the app and restarts it, the values are still there and everything is working as normal. Our application does not run in the background, so I can't think of a case where it is started by anything other than a user action, which would mean the device is unlocked.
I am familiar with the applicationProtectedDataDidBecomeAvailable method, which is called by the OS when a device is unlocked and protected data like the keychain becomes accessible. I suppose we could check in didFinishLaunchingWithOptions if UIApplication.isProtectedDataAvailable is false, and if so defer the part of the app initialization that depends on the keychain to happen in the applicationProtectedDataDidBecomeAvailable method. I'm just always leery of coding a solution without being able to reproduce and test it.
Can anyone report that they have seen instances via app logs where the iOS protected data is not yet available in the didFinishLaunchingWithOptions method, but becomes available later?
With iOS 10.3.2 release I am seeing instances where it is randomly happening to some of my users as well. Our OAuth credentials are stored in Keychain which is accessed every time an API call is made. If OAuth does not exist I logout the user as we have sensitive data on the device.
I have a check for isProtectedDataAvailable on background refresh so I know it is not queuing up any API calls then.
Like you I really don't want to add a check/wait for protected data to be available on foreground. It defeats the purpose of having kSecAttrAccessibleWhenUnlocked
Here is one recent issue posted with regards to Keychain which suggests decryption time could be another culprit. https://github.com/evgenyneu/keychain-swift/issues/15
Related
Backstory: Push notifications are currently buggy for Mac apps made with Catalyst. This means that changes made on other devices will not be received if the sync engine is CloudKit or NSUbiquitousKeyValueStore. As a temporary workaround while waiting for the bug fixes around push notifications on Mac Catalyst, our app needs to periodically and/or manually request a refresh of anything that syncs.
Question: How can we request a refresh for the default instance of NSUbiquitousKeyValueStore? (Apple's documentation shows nothing here.)
From Apple Documentation it is not possible to force NSUbiquitousKeyValueStore to pull updates from the cloud. You can only let iCloud know that new keys and values are available to be uploaded. using the Instance Method synchronize()
The system controls when those keys and values are
uploaded. The frequency of upload requests for key-value storage is
limited to several per minute.
The recommended time to call NSUbiquitousKeyValueStore.default.synchronize() is upon app launch, or upon returning to the foreground.
We have an app that heavily relies on being able to access a user's session token using iOS's Keychain. When our app opens, the first thing that's checked is whether the token is available - and if not, we show the user a login screen. We do not use any 3rd party library for this, and use Keychain's SecItemAdd() / SecItemCopyMatching() directly with the following options:
kSecClassGenericPassword
kSecAttrAccessibleAlwaysThisDeviceOnly
We see little to no issues with this during normal usage.
The Problem
We've had users reporting that upon opening their app, they're shown the login screen (suggesting Keychain could not find a value), when they were in fact logged in. We found that in this instance, we found that upon killing and relaunching the app, users were back to normal (session was found in Keychain). Once we found this, we tried added an exponential backoff to keep querying Keychain, thinking that it may had only been unavailable for the first few seconds. This did not work, and proved to us that Keychain seems to be unavailable for the entire app launch session. We are not able to reproduce this issue. It seems to happen very intermittently.
Upon further investigation, we found that commonly, users had received VoIP Pushes prior to having this issue. We're still not able to reproduce this issue reliably, but upon debugging, found that our Keychain.session was found to be nil when receiving these pushes (we rely on it there as well), which is before the user would open their app to see that they are pseudo- "logged out."
We use PushKit and PKPushRegistry in order to do VoIP Push. This requires our app to have "Background Modes: Voice over IP" enabled. We use all of this as suggested by Apple's documentation. Here's a sample:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
guard let _ = Keychain.session else {
print("Session token not available")
return
}
handle(notification: payload.dictionaryPayload)
}
This code relies on Keychain.session being immediately available upon receiving a VoIP Push. As mentioned before, we've seen instances where it is not available, and so this function logs out and simply returns.
I've also found this relevant Apple forum post, suggesting that this could be an iOS bug.
Can anyone help us with this issue? Any help would be greatly appreciated. Even if this is in fact an iOS bug, we're open to workarounds.
I think this topic is driving the iOS dev community literally crazy since long time. I've read so many articles and thread about this that I lost the count.
So, I'm dealing with this issue too, and based on the #naqi answer, I started moving in that direction and I might have found a working solution.
Assumptions:
The issue seems to happen when the app is getting suspended, due any sort of reason (eg. the app in background from long time and due memory pressure the OS send it in suspended state, or the app is in bg and user upgrades to a new version from the store or automatic app update does it). I'm no longer fully sure what suspended means, but I strongly suspect that the app may get resumed/awaken without actually getting presented on the screen (due bg task, but not necessarily).
One Apple stuff member on the Apple developer forum, had the brave to reply saying something like "try to put a delay between addDidFinishLaunchingWithOptions and appDidBecomeActive". Apart from the madness here, reading that comment made me consider that maybe when the app gets resumed it triggers the addDidFinishLaunchingWithOptions but of course not the appDidBecomeActive (as it may not be resumed by the user but from any sort of event handled by the OS in background, for which we may not have the control seems, or any other feature like bg task or silent push notifications etc).
Extra info:
According with a quite old penetration test article I've found on the web (https://resources.infosecinstitute.com/iphone-penetration-testing-3/) querying the keychain actually would address the query to another process called securityd running on the device, which magically schedule the queries and performs them to the keychain db and, finally returns back the content to the requesting app, assuming that the app is still alive when the query gets completed on the keychain db. This made me think that, if that is still true, such process may fall in sort of "DDoS" attack where your app (or maybe all the apps in execution? Not clear how it actually works) performs too many queries. This may lead to false positive errors like secItemNotFound or similar. Again, as nobody confirmed this, keep it as my personal consideration.
Approach:
From the #Naqi answer, I could exclude the points 2,3 and 4, keeping the point 1 as only still valid reason. Said that, I decided to move any CRUD operation dealing with the keychain, from the addDidFinishLaunchingWithOptions to appDidBecomeActive, by handling the app setup instructions (whatever needed by your app) in there, in order to make sure that eventual background events of suspending/resuming actions won't affect the keychain at all as the appDidBecomeActive would not get called unless user actually makes the app to get opened and shown on the screen. Of course this should happen only on app launch, so you can use a bool flag or similar semaphore approach in appDidBecomeActive to make sure your setup logic is executed only once.
Combined with the above approach, In the App Delegate I also explicitly implemented application(shouldSaveApplicationState:) by returning false and application(shouldRestoreApplicationState: still returning false (Even though I assume by default should be already like this). This may not necessarily be useful but it was worth it to try too.
Summarising, on some affected devices which were randomly failing the keychain access, seems now not to happen anymore since weeks and, consider this was happening for some, almost every 2/3 days let's say, I assume it might have attenuated the issue at least.
I can say that the monitored devices were all in different configurations, with a lot of apps installed or not, with storage memory almost full or not, with a lot of apps in bg or none at all, any form factor and different iOS versions. Keychain backup to cloud not enabled, and items attribute on the keychain at least as kSecAttrAccessibleWhenUnlockedThisDeviceOnly
Open point not fully verified: Supposedly the access to keychain should be thread safe, but I found different thread on the web contradicting each other. Maybe that could somehow impact your logic and cause unexpected state
(Congrats if you managed to read up to here :) )
Hope this helps to further understand what's going on with this keychain tragedy in iOS
I was having similar issues and after talking to engineers at WWDC we identified few issues that is leading to this behavior.
We were calling Keychain too often and this can lead to a situation where it gets expensive and some calls would not finish or timeout
If an app does not provide Access Group for keychain or does not add it in save transaction with keychain then Xcode generates one but this is dynamic and can change between dev environments
If you provided an access group later in the life of your app, then your old keychain value will remain associated with the app and a new value will be created with the access group you will now provided
This also means that when you query keychain you have to provide an access group otherwise you will get a reference to what ever was found first.
Edit: formatting
I have a group email app where customers create emails in a standard iOS compose window. If they write a long email, then switch apps to do a memory intensive task, iOS may close the compose window, losing the contents of their email.
Is there a way to prevent the window being closed? or a way to ensure iOS saves the email to drafts before closing it?
I am wondering if I am not retaining a link that I should be, perhaps?
Can anyone else confirm whether they see the same problem?
The best way to do this is to understand the App Life Cycle on iOS.
When the App is running in background, the method applicationWillTerminate will be called, this is one of your options to save the content of the email. But this method is not called every time, it is only called when your app isn't suspended or when the user reboots his device.
One solution is to save this data inside the method applicationDidEnterBackground, it is called as soon as your app starts to run in background. And reload the data inside application:didFinishLaunchingWithOptions, this allows you to perform any final initialization before your app is displayed to the user.
Is there a way to prevent the window being closed? or a way to ensure iOS saves the email to drafts before closing it?
There's nothing you can do to prevent the window from being closed, because iOS is killing the app. What you can do is to save the email at some point before that happens. Since the user obviously can't modify the email while the app is in the background, saving the message when the app goes into the background would be a good choice. That way, you'll have the data saved if iOS does end up killing the app, and you can check to see if there's a saved message that needs to be restored when the app launches.
If you take that approach, though, you'll also have to figure out a way to help the user get back to their saved message, which means that you also need to keep track of how they got there in the first place so you can recreate that state. If it's just a simple message, maybe all you need to do is to open a message editor view and set it up with the saved message. In general, though, this can be a thorny problem. Luckily, Apple has provided a nice solution...
The View Controller Programming Guide for iOS has a section called Preserving and Restoring State, which explains how you can set your app up to automatically save and restore its state. With a relatively small amount of work, you can set your app up so that the user will never even notice that the app has restarted -- if they launch it after iOS has killed it, the app will recreate the view controllers so that it looks like the app was running the whole time, even if the device was shut down in the interim.
Apples docs are a bit vague on when a device's deviceToken can change. It pays to code defensively, and to test that code.
I've written my app so that at startup it registers for push notifications and handles the didRegisterForRemoteNotificationsWithDeviceToken callback.
My didRegisterForRemoteNotificationsWithDeviceToken saves the last device token to user defaults, and if the token is still the same, it moves on. If it doesn't have a saved token at all, or if the tokens don't match, it uploads the new token our server.
I also have a mechanism that lets me name my test devices with human-readable names like "4s_1" "5s_1" "5s_2", etc. I have a server command line tool that takes the human-readable device name, looks up the device's 'identifierForVendor' from that, and then looks up the device token using the identifier. It then triggers a sandbox notification.
It seems that deleting the app and reinstalling it causes the 'identifierForVendor' to change, but not the deviceToken, which is exactly the opposite of what I'd like to have happen for testing. I want some value that never changes to uniquely identify a test device, and away to change the device token so I can test my code.
EDIT: Since writing this I've changed the app to generate it's own UUID and save that to the keychain as suggested by Wain in his answer.
Is there a way to force Apple's APNs to change a device's token so I can test out my code (both client side and server side) for handling the case where the token changes?
I don't believe so.
If you want a single unique identifier then you should create one explicitly and store it in the keychain, then you can store the device name and all other details against this so you have a single point of truth.
Any identifier you stored in the keychain would survive until it was explicitly removed or the device was restored from a backup without that keychain content.
I'm not sure if this approach also resets the token, but you may try to reset the permission:
Resetting the Push Notifications Permissions Alert on iOS The first
time a push-enabled app registers for push notifications, iOS asks the
user if they wish to receive notifications for that app. Once the user
has responded to this alert it is not presented again unless the
device is restored or the app has been uninstalled for at least a day.
If you want to simulate a first-time run of your app, you can leave
the app uninstalled for a day. You can achieve the latter without
actually waiting a day by following these steps:
Delete your app from the device. Turn the device off completely and
turn it back on. Go to Settings > General > Date & Time and set the
date ahead a day or more. Turn the device off completely again and
turn it back on.
Regarding the change of device identifiers: You could install a second application on your test devices (a blank one from the same issuer). If you reinstall your actual app, the identifierForVendor shouldn't change.
Client side you could test be changing the stored device token.
Terminate App in device
Download (xcode devices window) the Container.
Find default in Library/Perferences/ app bundle plist.
Edit entry in plist.
Replace Container (xcode devices window)
Important: I found a restart of the device was required for the changes to take effect.
Then on restart your old device token will be incorrect and what ever action is required can be done.
I used this when debugging an adhoc app I did not have src code for. But, if you have src code might be easier to just make the compare of deviceTokens fail.
I am using the keychain with iOS7 as target, I use the attribute : kSecAttrAccessibleWhenUnlockedThisDeviceOnly
It is working great, after 10sec when the device is lock the keychain variables are not accessible.
What I want now is to delete the keychain before the app is killed. applicationWillTerminate is only called if the app is killed before going into background or if the system release the application. If the app is in background for like 1min before being killed by the user, I can't find how to clear the keychain.
Is there any way to delete the keychain or have a function called when the app is killed by the user after several minute in background ?
Is there any way to delete the keychain...
NO. On iOS, there's only one keychain and its a shared resource.
If interested, here's some reading from the data security point of view. Not much has changed since the iOS 4/5 days. I think the most interesting new things are the NSSecureCoding Protocol for iOS 7 (or was it 6?) and fingerprint authentication.
Dino Zavi's Apple iOS 4 Security Evaluation
Sogeti ESEC's iOS 5 data protection updates
Belenko's Blackhat slides Evolution of iOS Data Protection and
iPhone Forensics
or have a function called when a app is killed after several minute in background
Yes and no. -applicationWillTerminate is not sent, despite what the literature says. Instead, you know you are terminating when the SIGKILL arrives. And you can't trap it. But you may be able to perform a quick wipe and return from the sighandler, though (I've never tried it, so I don't know).
The strategy to use when the data sensitivity warrants is to begin wiping data when your delegate receives -applicationWillResignActive. Or start a timer when -applicationWillResignActive arrives but cancel it if -applicationWillEnterForeground arrives. If the time elapses, then begin wiping. But both can create a poor user experience.