Problem with PFX Certificate and Background Task

Our app runs a background task while it's locked that calls a web service, and presents a URLCredential with a SecIdentity. It works fine if you simulate the background task using,

e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.ourapp.extendedtask"]

but when it actually runs in the background with the device locked, and I try to fetch the SecIdentity from the Keychain using SecItemCopyMatching, it fails with

-25308 (errSecInteractionNotAllowed)

I tried adding kSecAttrAccessibleAfterFirstUnlock when writing to the Keychain like this,

let keychainAddQuery: [String: Any] = [
  kSecValueRef as String: identity,
  kSecAttrLabel as String: "certKey",
  kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
     
let addResult = SecItemAdd(keychainAddQuery as CFDictionary, nil)

But it still fails when the background task tries to fetch it from the Keychain using SecItemCopyMatching. I could try using

kSecAttrAccessibleAlways

when I write to the Keychain, but the documentation says that is deprecated.

Is there a way to write the SecIdentity to a file and storing that within the Application Support folder rather than in the Keychain? So it's accessible when the BackgroundTask runs while the device is locked?

Our original approach was to put the PFX file in the Application Support folder, and store the password in the Keychain, then use SecPKCS12Import to generate the SecIdentity. However, it never gets that far, because fetching the password with SecItemCopyMatching didn't work due to the errSecInteractionNotAllowed issue. I then tried writing the SecIdentity itself to the Keychain using SecItemAdd as shown above, but encountered the same problem when the Background Task tried to fetch it using SecItemCopyMatching. I realize now it's not an issue with the specifics of the data being fetched from the Keychain, but rather a security issue reading from the Keychain while in the background.

Answered by southbayjt in 726893022

If the item has already been added to the Keychain, it won't update unless you explicitly delete it with SecItemDelete, or update it with SecItemUpdate. Once I deleted it and re-added it, it does work using kSecAttrAccessibleAfterFirstUnlock. Still curious if this could be done without the Keychain, but I'm guessing that's you only choice, or I would've come across more code examples showing alternate methods.

Accepted Answer

If the item has already been added to the Keychain, it won't update unless you explicitly delete it with SecItemDelete, or update it with SecItemUpdate. Once I deleted it and re-added it, it does work using kSecAttrAccessibleAfterFirstUnlock. Still curious if this could be done without the Keychain, but I'm guessing that's you only choice, or I would've come across more code examples showing alternate methods.

Still curious if this could be done without the Keychain

Sure. On iOS SecPKCS12Import creates the SecIdentity in memory [1]. If you store the PKCS#12 data and its password outside of the keychain, you can re-run the import, get the identity from that, and use it to implement your mutual TLS (defined here).

However, this is much less secure than storing this data in the keychain. The approach you just got working is correct IMO, and that’s why there aren’t more “code examples showing alternate methods”.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] This is not true on macOS, where SecPKCS12Import always adds the identity to the keychain.

Problem with PFX Certificate and Background Task
 
 
Q