Fatal Exception: NSInvalidArgumentException SecKeyGetAlgorithmId called with NULL SecKeyRef on ios 15 only

As usual crash is happening on newer version of iOS 15

Crash log Fatal Exception: NSInvalidArgumentException 0 CoreFoundation 0x1814dc05c __exceptionPreprocess 1 libobjc.A.dylib 0x1999f6f54 objc_exception_throw 2 CoreFoundation 0x181533190 __CFDictionaryCreateGeneric 3 Security 0x18a239674 SecKeyGetAlgorithmId 4 Security 0x18a2d53d0 SecKeyGetSignatureAlgorithmForPadding 5 Security 0x18a2d5328 SecKeyRawSign 6 App Name 0x100bc90f8 -[login privatekeytouch] + 1484 (CprLoginContrl.m:1484) 7 libdispatch.dylib 0x18114cc04 _dispatch_call_block_and_release 8 libdispatch.dylib 0x18114e950 _dispatch_client_callout 9 libdispatch.dylib 0x18115cd30 _dispatch_main_queue_callback_4CF 10 CoreFoundation 0x181494ce4 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE 11 CoreFoundation 0x18144eebc __CFRunLoopRun 12 CoreFoundation 0x1814623c8 CFRunLoopRunSpecific 13 GraphicsServices 0x19cc7338c GSEventRunModal 14 UIKitCore 0x183e080bc -[UIApplication _run] 15 UIKitCore 0x183b85be8 UIApplicationMain 16 App Name 0x10079a7b8 main + 22 (main.m:22) 17 ??? 0x101639a24 (Missing)

crashed during SecKeyRawSign, anything has changed on ios 15?


crashed during SecKeyRawSign, anything has changed on ios 15?

Not sure what is happening here, but I would do a few things to get this investigated:

  1. Open a bug report with a focused sample that reproduces the issue on iOS 15. Please respond with the Feedback ID.
  2. If you are unable to resolve this, open a TSI with mention to your bug report so that either Quinn or myself can take a look at your bug with the focused sample to see if there is anything that can be done.
Matt Eaton
DTS Engineering, CoreOS

   SecKeyRef tmpprivatekeyref = [KeyInterface lookupPrivateKeyRef];     

OSStatus signStatus = SecKeyRawSign(tmpprivatekeyref,                       kSecPaddingNone,                       [digest bytes],                       [digest length],                       [signature mutableBytes],                       &signatureLength);

if tmpprivatekeyref is nil then on older versions of the OS signStatus used to return success or failure, on OS 15 it is crashing with the error mentioned in the post's heading. This is the bug. Why tmpprivatekeyref is nil you ask? because we store device id in a keychain to uniquely identify a device, so when an app gets transferred from one iphone to another keychain gets replicated even if we set thisiphoneonly flag (not through icloud), but the privatekey reference for SecKeyRef doesn'te get transferred to the new iphone. I have mentioned this before as well, but apple keeps insisting on providing a sample code, how can i provide a sample code for an issue that occurs when an app is transferred from one device to another?

So the exception here is pretty clear: It means that tmpprivatekeyref is NULL. You’ll need to trace back through your code to see how it got that way.

Or do as Matt suggested and open a TSI so that one of us can offer one-on-one help.

Share and Enjoy

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

There is this app, which is a financial app, we use UUID to uniquely identify the app, since there no way of extracting IMEI etc on apple to identify a device. We store this UUID in a keychain, because in one instance of apple OS upgrade the UUID changed after the OS upgrade, this I think is well documented. Hence we cannot rely on this if it happens again, because honestly I can’t explain these things to our clients (who don’t understand the technical aspect of things) .  So our servers uniquely identify each device/user based on this UUID, and redirects him/her to either the login page or to the registration page.

We also have enabled biometric , we ask the Secure enclave to generate a public and a private key and we store the public key in our servers. So once server stores this public key it assumes the user has enabled biometric login. On launch of the app our server generate a token and sends it to the device. User then tries to login using face/Touch ID, we request the private key reference from the secure enclave (upon which biometric gets launched) get the private key reference , sign the token and send it to server , which will use the public key to check if the token is valid and that is our biometric authentication process. 

Now the same user now buys a new iPhone, and then transfer his data from the old iPhone using iphone to iPhone transfer, guess what happens, the UUID in the Keychain gets transferred to the new iPhone even though I have specifically stated not to. i.e via kSecAttrAccessibleAlwaysThisDeviceOnly. Now the Secure enclave doesn’t gets transferred to the new iPhone. Now the user tries to access the app on his new phone, it directly goes to the login page instead of registration page because our server gets the original UUID that was used on the old iPhone. Then server assumes the user has already enrolled in biometric sends the token and then the device tries to access the private key reference ,it returns nil and our app crashes only on iOS 15. Used to return failure now it doesn’t. 

Now there are other users who transfer (using the above same app which is in store by the way) their data via iCloud for them the UUID (Stored in keychain) doesn’t get transferred because of kSecAttrAccessibleAlwaysThisDeviceOnly, so all is well, it behaves as expected.

Now is my approach wrong? Or is it a bug at apple’s end?

this I think is well documented.

By “well documented” you mean “well documented not to be reliable”, sure (-: See this post for all the details.

the device tries to access the private key reference, it returns nil and our app crashes only on iOS 15.

Passing nil to SecKeyRawSign is clearly incorrect. Your app should not do that under any circumstances.

The fact that iOS 15 throws a language exception rather than returning an error is interesting but not incorrect; it’s within its rights to do that because you’ve broken the API contract by passing in nil.

Now is my approach wrong?

Storing two separate items puts you at risk of your items being out of sync. If you continue down that path, you must handle that. Fortunately you have an easy way to do that: If either item is missing, fall back to your unenrolled out state.

An alternative is to store a single item, the key, and store the UUID in kSecAttrApplicationTag.

ps We’ve just officially deprecated SecKeyRawSign. I recommend that you move forward to SecKeyCreateSignature.

Share and Enjoy

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

Not sure what you mean by API contract? By definition a contract shouldn't change willy-nilly, why was it working till now!! prior to ios 15 , i can see examples created by other programmers who were passing nil, its a bug , let's just leave it at there. And as far as either item is missing, i need to rollback, how will i come to know if a secure enclave is erased without prompting the user to do a biometric authentication, this is not an elegant solution, i don't think anyone should design apps in such fashion., this is like going into a rabbit hole, end user will get confused, prompting user to use face id, and then informing him his app will get deregistered! . why have you proposed this specific key kSecAttrApplicationTag i am not sure, i am storing the UUID as kSecClassGenericPassword. Also if possible make it clear as to why that on icloud restore the keychain gets erased but on iphone to iphone transfer it doesn't, i have witnessed this in multiple apps , designed by other companies. So clearly i am not the only one who is designing apps this way. Am i doing something wrong? is it a mistake to store it as kSecClassGenericPassword?

Not sure what you mean by API contract?

The API contract between your app and SecKeyRawSign is that you must not pass it a nil key. This is clear when you look at the Swift declaration, where the key parameter is non-optional.

If your app breaks that contract, by calling SecKeyRawSign with nil, the behaviour you get depends on the the implementation of that function, which can change over time.

why have you proposed this specific key kSecAttrApplicationTag

There’s two parts to this:

  • I recommend that you use a key attribute that ties the UUID with the key and thus there’s no way they can get out of sync.

  • I chose that specific key because it’s available for your use. The various key attributes can be confusing; see my SecItem attributes for keys post for a summary of them.

Am i doing something wrong? is it a mistake to store it as kSecClassGenericPassword?

I wouldn’t say that. However, if you build a solution that depends on two different bits of data then you need to be prepared for them to be out of sync.

Share and Enjoy

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