In a previous post we discussed the benefits of using Apple’s DeviceCheck API to verify that an HTTP request originated from a legitimate iOS device. Now we’ll go a step further and look at the App Attest service, which can establish trust with an instance of an iOS App using keys issued by Apple.
Let’s quickly touch on a few requirements of using the App Attest API:
- iOS 14 minimum
- Physical device (no simulators)
- Signed app bundle registered to an active developer account
The first thing we’ll have to do is generate a public/private key pair using the App Attest service. The keys are never directly exposed to the client, what we get back is a keyId. This id can be used going forward to access the keys from the hardware storage area known as Secure Enclave. This block of code has been written to generate a key if it does not already exist, and save keyId in the keychain using a utility class called KeychainManager.
Once a key is generated, we’ll want to communicate with our application server to “attest” this instance of our app using the key. The app should ask the application server for a one time challenge value, any randomized string or UUID will suffice. The server will persist the challenge value before giving it back to the client, and use it again later. The SHA256 of that challenge is passed to the App Attest service, which gives us back an attestation object.
The server side implementation is where we really need to get our hands dirty. Apple’s instructions list a 9 step process to determine the integrity of the attestation object. These steps are clear enough, however picking apart the binary data structure is not entirely trivial. I’ve written some sample code in python that might be a useful reference, just keep in mind this is not fully battle tested for production.
Let’s review a few important assertions that were covered here:
- The payload contained a valid certificate (issued by a trusted Apple CA) that contained the expected keyId & public key.
- Nonce value derived from registered App Store data was also present in the certificate.
- The one-time challenge shared between client and server was verified.
An expiring one-time challenge and a short validity period on the credCert are tools to prevent replay attacks against this mechanism. Once this verification has passed, the server has established trust with the client and should save the keyId and receipt data. For future transactions that are deemed sensitive, the server can challenge the client to send an assertion payload, which applies similar concepts to any specific API request. See the section titled Verify the Assertion here for further info.
In conclusion, the App Attest service (in tandem with DeviceCheck) provides a solid app security mechanism for detecting fraudulent access to mobile web services. Feel free to contact Restless Labs if you have questions about what type of security might be appropriate for your web applications.
Additional References: