Intermittent & Temporary iOS Keychain Failure - ios

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

Related

Reliable periodic URL fetch from iOS app

We have been looking for a reliable way to periodically get data from a URL from an iOS app, regardless of the state of execution of the app on the device (foreground, background, suspended or terminated/force-quit).
We have gone through all the questions on this topic that are on this site, and their answers, as well as all the relevant iOS documentation from Apple and blog posts we can find on the subject. But, we have not yet found a solution that will work.
Here is what has been considered/implemented:
Background fetch - This is our current implementation. It works at the whim of the OS as far as the frequency and timing of the fetch goes, and only when the app is not "terminated" or "force-quit" by the user. This has been found to be totally unreliable, as the users themselves don't understand the quirks of iOS, and force-quit the app, yet expecting it to function even after that. The app deals with financial information, and thus can result in loss of potential financial gains to the users if the fetches are not performed on a schedule.
Change notifications from the server - this is not an option, as we neither store nor wish to store certain information on a server, for privacy reasons. Therefore, we cannot use this.
Silent notifications from the server to trigger fetch - again, this won't work if the app has been force-quit, as the notifications are not delivered to the app in that state.
PushKit - this is what apps like WhatsApp or Vonage would use to fire the app regardless of its state of execution. However, this is only available to VoIP apps, and we don't fall into that category. Can this still be used by non-VoIP apps? If so, we could settle for this but only as a last resort, as the preferred option is to have it all done from the app without involving a server to generate "empty" push notifications, simply for the purpose of triggering a URL download.
Given the nature of the app and the potential for the user to benefit financially, it seems rather harsh that iOS would not allow a simple operation such as a periodic download of a URL. So, hoping that someone knows how to overcome this challenge.
As an aside, the exact same functionality on Android works like a charm, regardless of the app's state of execution.

iOS didEnterRegion sometimes not get called

I am working on xcode 8.3.3 on a project written in Swift2.3.
I installed SwiftLocation (2.3 Branch) library via Cocoapods in order to monitor region events (didEnter/didExit Region).
I had 3/4 region to monitor on average at the same time
I add them as it follows:
do {
let monitor = try Beacons.monitor(geographicRegion: epicenter, radius: 100, onStateDidChange: { (state) in
state == .Entered ? self.delegate?.checkedIn(id, status: .Awake, onFinish: nil) : self.delegate?.checkedOut(id, status: .Awake, onFinish: nil)
}) { (error) in
let error = NSError(domain: “Monitor”, code: error._code, userInfo: [NSLocalizedDescriptionKey : error.description])
self.delegate?.failedBecause(error)
}
monitor.onAuthorizationDidChange = { status in
self.delegate?.movedTo(status)
}
} catch let error {
let error = NSError(domain: “Monitor”, code: error._code, userInfo: [NSLocalizedDescriptionKey : error as! AnyObject])
self.delegate?.failedBecause(error)
}
I have activated Background Modes and asked for Authorization and I am sure it is working in background because the app is woke up and I am able to get events even if the app has been killed.
Moreover both the gps and the internet coonection on the device are turned on. The feature seems to work well, but often the callback “onStateDidChange” of the method “Beacons.monitor(:)” doesn’t get called. It’s really weird that I have a great number of exit event, but not too many entrance.
We already tested if this situation occurs only because the device was inside the region when monitoring was added but this seems to be not the reason. How is possible to have sometimes only the exit event but not the entrance? To intercept the exit event, the system knows that the device was inside the region, but the entrance event hasn’t been triggered.
Any help on fix this or debugging it is really appreciated.
Hi #Gero. Thanks for your answer.
Your scenario is likely to happend.
I simulated it on the XCode Simulator either killing the app directly or waiting more the 5 minutes with the app in background (since Apple documentation suggests that background tasks are killed after 3 minutes). In both situation UIApplicationLaunchOptionsLocationKey is not empty and it tells me that the app has been woken up.
In this case, I execute a piece of code that just instantiate the LocationManager and set the controller as delegate without restarting regionMonitoring.
I have evidences on the server side that didEnter/didExit is called via this flow. I know this because the app makes an HTTP call to store the event and sends “asleep” as a parametern to distinguish it from the “standard” flow in which it sends “awake”.
For this reason I am afraid that this is not the reason, because it would be still not clear why it is sometimes working and sometimes not.
We’ll make a try with requestState(for:) and let you know :wink:
Okay, this is only an educated guess, but since it's too long for a comment I'll try to warp it into an answer. Your comments and (excellent) edit indicate to me you know what you're doing and employ the correct methods (sidenote: A lot of questions on this topic result from improper understanding of the feature, so at first I thought it's maybe that).
What I could imagine happens is this:
The app is running while you're outside of the region, i.e. the next thing would be a .Entered kind of state in the callback. However, the app gets terminated by the OS before that happens for some reason (memory?).
Next you enter the region, so the app gets relaunched. You properly figure this out checking for the UIApplicationLaunchOptionsLocationKey in the launchOptions dictionary and start your region monitoring again. However, I think the system might then not explicitly deliver the state change via callback again. The documentation is not that clear on this for region monitoring. So now you're inside the region, but never received an according state change callback.
The solution would be to explicitly query the region if and only if your app is (re)launched with UIApplicationLaunchOptionsLocationKey using requestState(for:) of CLLocationManager and your region (this might be tedious if you have multiple regions, but is probably required).
Edit: For what it's worth I stumbled over a related question on the dev forums and even clarified the issue further there. Gualtier Malde from the Apple staff answered, so that should be a valid source. In case the link vanishes (as sometimes seems to happen), here a summary and quotes of what he explained.
The basic question was about rebooting (which terminates an app) and then the app not getting region updates. His first answer was
After a reboot, your app will not be alerted for iBeacon events until the user unlocks the device first. There is no way around this.
If you are having issues with iBeacon detection even after the user unlocks the device, barring any coding problems with your app, the first place to look at is the iBeacon advertising, and to make sure it is advertising 100% to the specs, and is also calibrated properly for its installed location.
I then further asked when exactly the resume is about to happen. I wanted to know whether because of this it's theoretically possible to miss region updates and when exactly the region updates should come in again. Answer:
Yes, your app should get relaunched in the background - considering the beacons are advertising correctly, Bluetooth is turned on, etc. - without user interaction.
All this will happen only after the first unlock. Nothing will happen with your app after a reboot until after the first unlock.
Any events that happen before the first unlock can be considered lost to your app.
From this I take that even a relaunched app gets region updates. I have no idea why you miss any, unless you actively reboot the device.
One thing to note regarding your testing procedure though:
Putting the app in background and waiting > 5 minutes will not automatically terminate the app. It will usually simply put it into the suspended state. The documentation you refer to does indeed explain that background tasks are terminated after a certain time (note that they do not guarantee a definite time here, Apple stays intentionally vague here to remain in control for future changes and stuff), but that does not mean your app process is necessarily killed! The tasks are just ended and your app is suspended. There is no real way for us to have the OS terminate an app. AFAIK it would do that if it needs the resources, but that's hard to enforce somehow. Besides, I am also pretty certain that a debug build on a development device (especially connected to Xcode/the debugger) doesn't necessarily behave like an iPhone in the wild. The simulator behaves even more different.
All that said, I am even more surprised that you get an application(_:didFinishLaunchingWithOptions:) call, because the way you described how you terminated the app shouldn't even result in it being terminated in a regular, okay way. Maybe you do have a bug, it crashes (or even gets "punished-quit" by the OS?) or the like? I don't know if an app that for example tries to "maliciously" buy background processing (e.g. by answering the application(_:performFetchWithCompletionHandler:)'s completion handler dishonestly or something?) time might be exempted by the system from region updates? That might be the case, but we're way beyond the scope of this question. It all boils down to an application state issue, i.e. life-cycle problem. You have to be very careful to properly identify what state you're trying to debug and expect to be in.

ios app startup and protected data events

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 application​Protected​Data​Did​Become​Available 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.is​Protected​Data​Available is false, and if so defer the part of the app initialization that depends on the keychain to happen in the application​Protected​Data​Did​Become​Available 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

BLE Restoration/Preservation unexpected effect

I have implemented restoration functionality for CoreBluetooth and it works fine, except one thing - after a week (+/- - not sure for 100%) in the background, without opening, app terminated and not restored anymore on any BLE-based events (tested few times).
I also add logging to all BLE related task, add analytic for capturing crashes, track restoring process/events in additional - and after checking this logs/info/reports - not found any exceptions or something that can terminate my app.
The question is - can someone explain me the reason why I got such behavior?
Bluetooth related issues can be really hard to identify. We came across several unexplainable bugs in the past. The only way to determine why BLE was no longer working or misbehaving was to look at the CoreBluetooth logs on the device.
In case you have not tried that already you can enable these logs via a configuration profile that you can download from the Apple Developer Pages (look for Bluetooth and follow the instructions).
As soon as you enable the logging you can download a complete log from the underlying BLE stack that will tell you what went wrong. They even tell you if you have misused the API in some way. Hope that helps.
From my experience with CoreBluetooth State Preservation and Restoration I have come to the conclusion that it does not work reliably. You can get it working sort of "ok", but you will never get it to reconnect reliably while using long running pending connections in the background.
I have reported several bugs regarding this, but the most serious one in my opinion is that If the bluetooth manager switches state while the app is terminated then all pending connections will be lost (since they are not remembered by the module or the iOS stack). Since bluetooth state switches will not cause your app to be relaunched then it simply means that at any point in time your pending connection can be lost and you will not know about it. This on its own effectively means that State Restoration is rather useless. You could say that this is not a bug, but rater a poor design choice…
Another one is that sometimes the framework gets ”stuck” in a bad state (possibly by an internal race-condition or similar). This can happen randomly, but you can pretty easily reproduce this by calling connectPeripheral immediately in the didFailToConnect or didDisconnect callback. When this happens the connectionState will be set to connecting while in fact it is not.
And so on…
/Anton

Significant Change Notifications Live On Beyond Shutdown, Even Version Upgrades?

I have an app that uses LocationManager. For a while I was using Significant Location Change notifications. As I read the documentation, I believed 2 things to be true:
They did not really consume any extra battery because you were basically telling the phone, 'just give me knowledge you already have.'
When the app was off, the notifications stopped.
Once I introduced that, I got a lot of emails from people accusing me of burning up their battery. So I took it out.
Got an email yesterday from a fellow showing me that the location arrow in Settings/Privacy was still purple, even when the app wasn't running. That's also true on my phone.
I can only think of two possibilities:
The registration for significant change notifications is still in tact, despite upgrades to newer versions, etc.
Or, one time the app crashed while the location manager was on and it was never turned off.
2 seems pretty far fetched, as I would think there would be a ton of that happening potentially. I was kind of disappointed that there's no way in Instruments to see what the system believes its obligations to the app are, and when they have been in effect since.
It would be nice to see what delegates are active for an app, even beyond the app running actively.
From what I know about the significantLocationChanges, they work along the same principles as the regionMonitoring. If you have users complaining about battery life, they are likely getting burned by something else. -significantLocationChanges uses fewer items for location than -regionMonitoring does. I believe they are strictly cell-tower related. They may use Wifi detection as well, but they don't advertise it.
Also, the location monitoring will persist even when the app has been killed and between updates and upgrades. I don't know how the app will behave if you upgrade the app and remove the delegate methods while the app is still monitoring for them. My guess is they will get orphaned out there, the callbacks would try to notify your app but the methods are gone and will be ignored. Just a guess, but might be worth testing out. It might be worth leaving those in or a method that will remove all location monitoring. Then remove all that in a later update just to make sure nobody still has any active location code out there.

Resources