Validating apps that connect to your server
Verify that connections to your server come from legitimate instances of your app.
Overview
Adopt App Attest to check whether clients connecting to your server are valid instances of your app. Your app uses the shared instance of the DCAppAttestService to create a cryptographic key on a device, and then attest to the key’s validity. This produces an attestation object that your app passes to your server, along with the corresponding key identifier. Your server verifies the attestation object, and then extracts the embedded public key and other information. Later, your server uses the key to verify assertion objects that your app sends at critical points in the app’s life cycle, like when users try to download premium content.
[Image]
This article describes how to integrate App Attest into your server logic. For more information about the activites you perform in your app to support App Attest, see Establishing your app’s integrity.
Provide a challenge
Every time your app needs to communicate attestation data to your server, the app first asks the server for a unique, one-time challenge. App Attest integrates this challenge into the objects that it provides, and that your app sends back to your server for validation. This makes it harder for an attacker to implement a replay attack.
When the app asks for a challenge, provide a randomized data value, and remember the value for use when verifying the corresponding attestation or assertion objects the client sends. How you use the challenge data depends on the kind of object that you need to validate.
Verify the attestation
The App Attest service creates an attestation object that consists of authenticator data and an attestation statement according to the Web Authentication specification. The following authenticator fields are of particular interest for App Attest:
RP ID(32 bytes) — A hash of your app’s App ID, which is the concatenation of your app’s App ID prefix, a period, and your app’s CFBundleIdentifier value. The App ID prefix is usually automatically set to be your 10-digit team identifier, and can be found by inspecting the Identifier entry for your app in the Certificates, Identifiers & Profiles section of your Apple Developer Account.
counter(4 bytes) — A value that reports the number of times your app has used the attested key to sign an assertion.aaguid(16 bytes) — An App Attest–specific constant that indicates whether the attested key belongs to the development or production environment. Apps generate keys using the former during development, and the latter after distribution, as App Attest Environment describes.credentialId length(2 bytes) — The length of the hash of the public key part of the attested cryptographic key pair, following this field.credentialId(32 bytes) — A hash of the public key part of the attested cryptographic key pair.Encoded key(77 bytes) — The CBOR object signing and encryption (COSE) formatted public key part of the attested cryptographic key pair.extensions(variable bytes) — CBOR dictionary object that includes optional client properties.
The attestation statement uses a custom Apple attestation statement format with the following syntax:
$$attStmtType //= (
fmt: "apple-appattest",
attStmt: StmtFormat
)
StmtFormat = {
x5c: [ credCert: bytes, * (caCert: bytes) ],
receipt: bytes,
}To verify and decode the attestation object, check that it has the Concise Binary Object Representation (CBOR) data format with the expected syntax. The decoded object looks like this:
{
fmt: 'apple-appattest',
attStmt: {
x5c: [
<Buffer 30 82 02 cc ... >,
<Buffer 30 82 02 36 ... >
],
receipt: <Buffer 30 80 06 09 ... >
},
authData: <Buffer 21 c9 9e 00 ... >
}Use the decoded object, along with the key identifier that your app sends, to perform the following steps:
Verify that the
x5carray contains the intermediate and leaf certificates for App Attest, starting from the credential certificate in the first data buffer in the array (credcert). Verify the validity of the certificates using Apple’s App Attest root certificate.Create
clientDataHashas the SHA256 hash of the one-time challenge your server sends to your app before performing the attestation, and append that hash to the end of the authenticator data (authDatafrom the decoded object).Generate a new SHA256 hash of the composite item to create
nonce.Obtain the value of the
credCertextension with OID1.2.840.113635.100.8.2, which is a DER-encoded ASN.1 sequence. Decode the sequence and extract the single octet string that it contains. Verify that the string equalsnonce.
Create the SHA256 hash of the public key in
credCertwith X9.62 uncompressed point format, and verify that it matches the key identifier from your app.Compute the SHA256 hash of your app’s App ID, and verify that it’s the same as the authenticator data’s
RP IDhash.Verify that the authenticator data’s
counterfield equals0.Verify that the authenticator data’s
aaguidfield is eitherappattestdevelopif operating in the development environment, orappattestfollowed by seven0x00bytes if operating in the production environment.Verify that the authenticator data’s
credentialIdfield is the same as the key identifier.Verify the
apple_validation_category_01value within theextensionsCBOR dictionary in the authenticator data.
Validation Category | Description |
|---|---|
| Invalid. |
| An operating system executable. |
| An executable distributed through TestFlight. |
| An executable signed by a development code signing identity. |
| An executable distributed through the App Store. |
| An executable distributed using an Enterprise Universal Provisioning Profile, or ad-hoc distribution. |
| An executable signed using Developer ID. |
| These values aren’t appropriate as they represent categories of binaries that the system generates in certain restricted situations. |
| An executable signed with a code signing identity that doesn’t match any other category. |
Verify the
apple_bundle_version_01value within theextensionsCBOR dictionary in the authenticator data.
After successfully completing these steps, you can trust the attestation object.
Store the public key and receipt
Store the verified public key from credCert on your server and associate it with the user for the specific device. You use this key to check assertions later. As an added protection against replay attacks, make sure that the public key doesn’t already have an association with another user.
The attestation statement also contains a receipt that you can use later in a server-to-server call to request a fraud assessment metric from Apple. You use the metric to examine the number of attested keys for a specific device. This helps you to assess the risk that an attacker is using a compromised device to serve assertions to many compromised versions of your app.
When attestation succeeds, independently verify and store the receipt immediately. For more information about how to interpret the receipt, and how to use a receipt to obtain or refresh the metric, see Assessing fraud risk.
Verify the assertion
After successful attestation, your server can require the associated client to accompany server requests with an assertion object. Each verified assertion reestablishes the legitimacy of the client. You typically require this for requests that access sensitive or premium content.
The client creates the assertion by packaging the request as clientData, and asking the App Attest service to sign the data with the attested private key. Along with the signature, App Attest includes a simplified authenticator data instance in the assertion object, similar to the one in the attestation object, but containing only the first few fields, including RP ID and counter.
After receiving the client data and the assertion, you need to verify and decode the assertion to ensure it uses the CBOR data format and has the expected contents. The decoded object looks like this:
{
signature: <Buffer 30 45 02 20 ... >,
authenticatorData: <Buffer 21 c9 9e 00 ... >
}To verify the assertion, use the decoded assertion, the client data, and the previously stored public key, and follow these steps:
Compute
clientDataHashas the SHA256 hash ofclientData.Concatenate
authenticatorDataandclientDataHash, and apply a SHA256 hash over the result to formnonce.Use the public key that you store from the attestation object to verify that the assertion’s
signatureis valid fornonce.Compute the SHA256 hash of the client’s App ID, and verify that it matches the
RP IDin the authenticator data.Verify that the authenticator data’s
countervalue is greater than the value from the previous assertion, or greater than0on the first assertion.Verify that the embedded challenge in the client data matches the earlier challenge to the client.
Verify the
validationCategorywithin theextensionsCBOR dictionary in the authenticator data.Verify the
bundleVersionwithin theextensionsCBOR dictionary in the authenticator data.
When the assertion meets all of these conditions, you can trust it. Store counter to use in step 5 when verifying the next assertion.