KeyChain SecItemAdd return -25308 when app is launched

The app I am working on cannot access the KeyChain on more than 50% of the user base ever

since I enable Background Modes for Location Update for MarketingCloud SDK which I integrated.

The app has Keychain Sharing enabled.


The code is:

KeyChain is read to get an encryption key, if it is not found, we create a new encryption key which is used for open a database needed to launch the app. This code is executed in AppDelegate willFinishLaunchingWithOptions.


We intentionally kill the app when KeyChain returns an error since the app logic is dependent on the database.

We cannot reproduce the same issue.


Our question is, when is the keychain available for access? Should we delay(don't use it in AppDelegate) access to the keychain during app launch?

Accepted Reply

Once you enable your app to run into the background you have to start caring about data protection, and specifically the keychain access attribute. Consider a scenario like this:

  1. Your app is run by the user and everything is good.

  2. The user presses the Home button and your app moves to the background.

  3. Shortly thereafter the system suspends your app.

  4. At some point in the future, the system needs memory and thus removes your suspended app from memory.

  5. The user locks their device.

  6. At some point in the future a location event causes the system to launch your app in the background.

  7. Your ‘willFinishLaunchingWithOptions’ app delegate method runs. If it attempts to access a keychain item with

    kSecAttrAccessible
    set to
    kSecAttrAccessibleWhenUnlocked
    , that access will fail because the device is locked.

How you resolve this depends on the nature of your app:

  • If you only need your database when the app is showing UI, you can defer this access until the app is on screen. The app can only come on screen if the device is unlocked.

  • If you need your database while in the background, you can set

    kSecAttrAccessible
    to something less secure. The exact value depends on how your app gets run in the background. Most background execution is deferred until first unlock, and thus you can use
    kSecAttrAccessibleAfterFirstUnlock
    . However, some background execution can happen before first unlock, in which case you’ll need
    kSecAttrAccessibleAlways
    . This is measurably less secure.

Obviously it’s best to keep your data as secure as possible, so you should only use the second approach if your database is absolutely critical to the background operation of your app. You may be able to come up with a hybrid approach, where you split your database in two, using better protection in general but less protection for the critical-to-background-execution stuff. Or if you only need to write to your database, you could temporarily write new data to a less secure database and then consolidate things when the device is unlocked.

Keep in mind that the keychain is not the only place where data protection applies. Ideally you should protect your files on disk as well, using the various file protection modes. The preferred API for this is

NSURLFileProtectionKey
in
NSURL
, but most of the documentation you’ll find for the older
NSFileProtectionKey
in in
NSFileManager
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Error -25308 is

errSecInteractionNotAllowed
. This usually means that you’re trying to access a keychain item that’s not accessible while the device is locked.

I’m not sure how you go from this error to a crash. The OS won’t automatically crash your process when this error occurs. Rather, the relevant keychain API call will return this error and it’s up to your app to handle that case. Is your code crashing as a result of this error?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

You are right eskimo, sorry for the confusion. I rewrote the question.

Once you enable your app to run into the background you have to start caring about data protection, and specifically the keychain access attribute. Consider a scenario like this:

  1. Your app is run by the user and everything is good.

  2. The user presses the Home button and your app moves to the background.

  3. Shortly thereafter the system suspends your app.

  4. At some point in the future, the system needs memory and thus removes your suspended app from memory.

  5. The user locks their device.

  6. At some point in the future a location event causes the system to launch your app in the background.

  7. Your ‘willFinishLaunchingWithOptions’ app delegate method runs. If it attempts to access a keychain item with

    kSecAttrAccessible
    set to
    kSecAttrAccessibleWhenUnlocked
    , that access will fail because the device is locked.

How you resolve this depends on the nature of your app:

  • If you only need your database when the app is showing UI, you can defer this access until the app is on screen. The app can only come on screen if the device is unlocked.

  • If you need your database while in the background, you can set

    kSecAttrAccessible
    to something less secure. The exact value depends on how your app gets run in the background. Most background execution is deferred until first unlock, and thus you can use
    kSecAttrAccessibleAfterFirstUnlock
    . However, some background execution can happen before first unlock, in which case you’ll need
    kSecAttrAccessibleAlways
    . This is measurably less secure.

Obviously it’s best to keep your data as secure as possible, so you should only use the second approach if your database is absolutely critical to the background operation of your app. You may be able to come up with a hybrid approach, where you split your database in two, using better protection in general but less protection for the critical-to-background-execution stuff. Or if you only need to write to your database, you could temporarily write new data to a less secure database and then consolidate things when the device is unlocked.

Keep in mind that the keychain is not the only place where data protection applies. Ideally you should protect your files on disk as well, using the various file protection modes. The preferred API for this is

NSURLFileProtectionKey
in
NSURL
, but most of the documentation you’ll find for the older
NSFileProtectionKey
in in
NSFileManager
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you for the very quick reply.
We thought about those 2 approaches that you mention and it's good that it is confirmed.
We will also look into NSURLFileProtectionKey.

Cheers!

We try moving the reading/writing of keychain to MainViewController in viewDidAppear() event but our customers still encounter the same -25308 error. Any idea why this is the case?

It’s possible that the device is still locked at this point. What does

UIApplication.isProtectedDataAvailable
return when you get this error?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I added logs to check isProtectedDataAvailable and deploy to customers.

Is it possible for a viewDidAppear event to execute even if device is locked?

Is it possible for a

viewDidAppear
event to execute even if device is locked?

I don’t know for sure, but that’s what I suspect is going on.

This is less nonsensical than it sounds: One of the key features of iOS multitasking is that the system takes snapshots of your app to show to the user in the multitasking UI. To snapshot your app it must present your views, and that’ll involve a call to

viewDidAppear(_:)
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Got new results from the logs we put in place.

appState = background

isProtectedDataAvailable = false


We monitored it with some customers and find that it activates only when there is location update(geofencing).

If the snapshot mechanism is really the culprit, then the solution is to guard against accessing the keychain when isProtectedDataAvailable is false. However when this happens, what should we do with the state of the app?

Can we kill the process? or is there a more elegant way to implement this?

what should we do with the state of the app?

There isn’t an easy answer to that. You either have to restructure your app to support running when protected data is unavailable, or disable the code that causes you to run in the background. If you don’t want to do the latter, then you need to do the engineering work to support the former.

The first step there is deciding what your app should display when protected data is unavailable, and then working through the layers of your app to meet that goal.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"