Is the Keychain and the Secure Enclave worthless on jailbroken devices?

I'd like to prevent an attacker who uses a jailbroken iPhone or iPad from reading the contents of the Keychain of my app.

Is there any point in using the Secure Enclave to generate a public-private key pair if an attacker with a jailbroken device can easily attach a debugger to my app, read from the Keychain of my app and also request decryption via the Secure Enclave while appearing to the Secure Enclave as my genuine app?

Doesn't the whole security of the Keychain stand on the assumption that an attacker cannot attach a debugger to my app?

Is there really no reliable way how to prevent someone from attaching a debugger to my app? I think that using PT_DENY_ATTACH is very easily bypassed by attackers.

I don't mind if someone attaches a debugger to a re-signed version of my app, because such re-signed app would have a different Team ID, so the Secure Enclave would fail when attempting to use the key generated by my genuine app.

I just want to prevent the situation when an attacker could read from the Keychain of my app and request the Secure Enclave to apply the private keys which were generated by my genuine app.

Is the Keychain and the Secure Enclave essentially worthless on jailbroken devices if a determined attacker wants to take control of my app running on their jailbroken device?

Answered by DTS Engineer in 913004022

I’m reluctant to talk about jailbreaking because it’s a complex situation with poor terminology that evolves rapidly. Fortunately, there’s a different context where we can talk about this sort of thing, namely a Mac with System Integrity Protection disabled. And while you’re not targeting the Mac, it’s reasonable to draw analogies between the two situations.

On a Mac with SIP disabled the user can replace the kernel. That’s problematic because it puts you in a situation where your code is trying to protect itself from code running at a higher privilege. That’s not possible in the general, primarily because you can’t trust your own code. Basic functionality, like checking the hash of code pages being brought in from disk, is no longer trustworthy, so you can’t be sure that any checks that you do are actually going to run.

Which isn’t to say that the SE-protected credentials are useless. If you use them to communicate with a server, then the combination of SE protection and App Attest is useful signal.


Oh, and I want to speak to this:

Written by si3 in 853951021

re-signed app would have a different Team ID, so the Secure Enclave would fail when attempting to use the key generated by my genuine app.

That’s not how this pans out. When you store an SE-protected key in the keychain, it’s the keychain that ensures that only your app can access it, based on the entitlements described in Sharing access to keychain items among a collection of apps. The consequences are the same, but the mechanics are different.

Finally, I’m gonna recommend that you read Apple Platform Security. It has a lot of detailed info on how security is implement on iOS, and if any of my answers are misaligned with it then it’s likely that it’s right and I’m wrong (-:

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

I’m reluctant to talk about jailbreaking because it’s a complex situation with poor terminology that evolves rapidly. Fortunately, there’s a different context where we can talk about this sort of thing, namely a Mac with System Integrity Protection disabled. And while you’re not targeting the Mac, it’s reasonable to draw analogies between the two situations.

On a Mac with SIP disabled the user can replace the kernel. That’s problematic because it puts you in a situation where your code is trying to protect itself from code running at a higher privilege. That’s not possible in the general, primarily because you can’t trust your own code. Basic functionality, like checking the hash of code pages being brought in from disk, is no longer trustworthy, so you can’t be sure that any checks that you do are actually going to run.

Which isn’t to say that the SE-protected credentials are useless. If you use them to communicate with a server, then the combination of SE protection and App Attest is useful signal.


Oh, and I want to speak to this:

Written by si3 in 853951021

re-signed app would have a different Team ID, so the Secure Enclave would fail when attempting to use the key generated by my genuine app.

That’s not how this pans out. When you store an SE-protected key in the keychain, it’s the keychain that ensures that only your app can access it, based on the entitlements described in Sharing access to keychain items among a collection of apps. The consequences are the same, but the mechanics are different.

Finally, I’m gonna recommend that you read Apple Platform Security. It has a lot of detailed info on how security is implement on iOS, and if any of my answers are misaligned with it then it’s likely that it’s right and I’m wrong (-:

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

Thank you for your answer. I would like to ask follow-up questions, if you don't mind.

Written by DTS Engineer in 913004022

Which isn’t to say that the SE-protected credentials are useless. If you use them to communicate with a server, then the combination of SE protection and App Attest is useful signal.

Doesn't App Attest only verify that a user is running my genuine app (i.e. an app that has the same App ID as my app has) on a real Apple hardware, but it does not check if iOS itself is "compromised"?

The documentation regarding App Attest does not suggest that App Attest can detect when an attacker is running my ("non-compromised") app on a compromised device (i.e. a device where the attacker has control over the kernel). In such scenario, couldn't an attacker simply attach a debugger to my app and generate App Attest tokens which would pass all checks?

My understanding is that App Attest only guards against someone running a modified version of my app (i.e. a re-signed app with a different App ID), but it cannot guard against the case when someone runs my genuine app, attaches a debugger to it and uses the debugger to execute arbitrary instructions. Is my understanding correct? Maybe that is why you said App Attest is a useful signal (implying that it cannot be trusted alone)?

A related question:
Assuming a device with non-modified kernel, does there exist an entitlement or a configuration in Info.plist, which makes iOS reject any attempts at debugging my app, so attaching a debugger to my app would require modifying Info.plist, and thus re-signing my app with a different App ID? I imagine such hypothetical protection would be a bit stronger than PT_DENY_ATTACH.

Written by si3 in 913098022

does there exist an entitlement … which makes iOS reject any attempts at debugging my app … ?

Yes, but it’s the opposite to what you’ve described. On iOS, the system will only let the debugger attach to apps that are signed with a true value in their get-task-allow entitlement [1] [2]. If you look at a development-signed app, you’ll see that entitlement set:

% codesign -d --entitlements - Test853951.app 
…
[Dict]
    [Key] application-identifier
    [Value]
        [String] SKMME9E2Y8.com.example.Test853951
    [Key] get-task-allow
    [Value]
        [Bool] true
    …

while for a distribution-signed app it’s clear:

% codesign -d --entitlements - Test853951.app 
…
[Dict]
    [Key] application-identifier
    [Value]
        [String] SKMME9E2Y8.com.example.Test853951
    [Key] get-task-allow
    [Value]
        [Bool] false
    …

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

[1] On macOS the entitlement is called com.apple.security.get-task-allow, and the rules on macOS are less strict for historical reasons.

[2] This isn’t the only constraints. For example, for the debugger to work the device must have Developer Mode enabled.