Knowledge Center / Checkpoint architectures / checkpoint architectures · 2025·11
Apple App Attest and DeviceCheck — the attestation/assertion split
Apple ships two related but distinct services for app and device integrity on iOS. App Attest is a two-step cryptographic model: attest an app-instance key once, then sign each subsequent call with assertions against that key. DeviceCheck is a separate per-device flag service — two bits and a last-update date, durable across reinstalls. They solve different problems, and operators frequently use both.
1. App Attest — the attestation step
App Attest, introduced in iOS 14, is built around an app-instance
key generated by the App Attest service according to Apple’s App
Attest model [1] — Secure Enclave-backed on supported devices. The
key is bound to the app and the device; the private key never
leaves the platform key store. The first time the app intends to use the key with a
given relying party, it calls
DCAppAttestService.attestKey(keyId, clientDataHash:) and Apple
returns a CBOR-encoded attestation object [1] — the format
identifier is apple-appattest, modelled on the WebAuthn
attestation object structure with an Apple-specific
authenticatorData.
The attestation object contains a certificate chain rooted in
Apple’s App Attest Root CA, a credential identifier derived from
the key, and a counter initialised at zero. The relying party
verifies the chain server-side, confirms the signed clientData
hash matches the request hash they originated, checks the
attested public key matches the key the app intends to use, and
records the attested public key + counter for that user.
Attestation is a one-time event per keyId. Once the relying
party has verified an attestation, the attested key is treated as
trusted for that user until invalidated. Re-attestation happens
when the user reinstalls the app, restores from a different
device, or — under specific platform-defined circumstances — when
the Secure Enclave invalidates the key.
2. App Attest — the assertion step
Once the attestation is on file, every protected request from the
app uses an assertion: the app calls
DCAppAttestService.generateAssertion(keyId, clientDataHash:) and
App Attest generates a signature over the data the app provides,
using the attested key [1]. The
relying party verifies the signature using the attested public
key, increments the recorded counter, and rejects the request if
the new counter is not strictly greater than the recorded value.
The cryptographic effect is straightforward: each protected call carries a signature that the relying party can verify, bound to a key whose origin (a genuine app, on a genuine device, within App Attest’s integrity guarantees) was established at attestation time and whose use is monotonic via the counter.
3. DeviceCheck — the separate per-device flag service
DeviceCheck predates App Attest and addresses a different problem [2]: associating a small amount of durable per-device state with the operator’s view of that device, persisting across app reinstall on the same physical device. (Apple’s documentation describes durability across reinstall; behaviour across iCloud account changes is not explicitly specified, though the bits are scoped per-developer per-device-identifier server-side.)
The service offers exactly two bits per app-per-device, plus a last-update date. The relying party reads the bits via Apple’s servers using a token the app produces, and writes new values the same way. The classic use case is an operator that has flagged a device for prior compromise: even if the user reinstalls the app to “clear” their state, the two bits persist with Apple and the operator can re-apply the flag.
DeviceCheck is not a runtime checkpoint. It does not say anything about the device at the moment of the call. It is a small, durable, per-device datastore that only the relying party can write — and only Apple can host.
4. What the model proves and where it stops
Together, App Attest and DeviceCheck form a stack with a clear boundary:
- App Attest assertions prove a relying-party-issued challenge was signed by an attested key on a known device, in a current call.
- The attestation chain roots that proof in Apple’s CA, bounding the population of devices and apps that can produce valid assertions.
- DeviceCheck maintains durable per-device flags that survive reinstall.
What the stack does not provide — by design, out of scope of the API — is a signed account of what the app did between assertions. Each assertion is a checkpoint: a signature at the moment the API was called. The runtime trajectory between assertions is not part of what App Attest signs, and DeviceCheck holds two bits, not a record of the call sequence. This boundary is the same one Play Integrity draws on the Android side, and it is the boundary Execution Evidence Infrastructure (EEI) — the device-identity infrastructure layer for banking and payments — is designed to compose with, not replace.
5. Cross-references
- Sibling articles in this theme:
play-integrity,fido2-and-passkeys,hardware-attestation - Theme 1:
the-play-integrity-gap,the-fido2-submission-gap - Architecture:
/architecture/runtime-coherence - Comparison:
/eei-vs-attestation
6. External references
[1] Apple. Establishing your app’s integrity (App Attest). developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity. Cited 2025-11-05.
[2] Apple. DeviceCheck framework. developer.apple.com/documentation/devicecheck. Cited 2025-11-05.
[3] Apple. Apple Platform Security — Secure Enclave. support.apple.com/guide/security/secure-enclave-sec59b0b31ff/web. Cited 2025-11-05.