Docs/Concepts/Device Attestation

Device Attestation

In short

Every signed proof carries hardware-backed device attestation: Apple App Attest on iOS, Google Play Integrity on Android. A relying party can confirm the proof came from a genuine app on a genuine device, not an emulator or a script. No integration code is required. It is part of proof generation.

A proof of location answers where the device is. Device attestation answers a different question: is this a real app instance on real hardware? The two travel together. Each proof the SDK signs also carries an attestation produced by the operating system's own attestation service, so the verifier can tie the proof to genuine hardware rather than take the device's word for it.

This is separate from the device-key security tier (HARDWARE_STRONGBOX / HARDWARE_TEE / SOFTWARE), which records where the signing key lives. Attestation establishes that the app and OS are genuine. The security tier records the strength of the key that signed. A verifier reads both.

What gets attested

Platform Mechanism What it shows
iOS Apple App Attest The proof came from your App ID, running on a genuine Apple device with a Secure Enclave key.
Android Google Play Integrity The app binary, the device, and the Play install are recognized by Google.
Android Key attestation The signing key lives in TEE or StrongBox, attested by a certificate chain rooted in Google's hardware-attestation root.

The evidence is bound into the signed proof chain. Editing it after signing breaks verification. On iOS the App Attest assertion carries an anti-replay counter, so a captured assertion cannot be reused on a later proof.

Configuring how often it runs

Attestation is on by default. How often a fresh attestation is produced is the one knob you set, on OctetConfig.advanced.attestationCadence:

Cadence Behaviour
per-session One attestation per SDK session. Lowest overhead.
periodic (interval) Re-attest on the interval you give. Default (5 minutes).
per-proof A fresh attestation on every proof. Highest assurance, highest cost.
let config = OctetConfig(
    licenseKey: "<your-license-key>",
    advanced: AdvancedConfig(
        attestationCadence: .periodic(interval: 300)  // the default
    )
)
let octet = try await Octet.start(config: config)
val config = OctetConfig(
    licenseKey = "<your-license-key>",
    advanced = AdvancedConfig(
        attestationCadence = AttestationCadence.Periodic(intervalSeconds = 300),
        // Optional: bind Play Integrity to a specific Google Cloud project NUMBER
        // (not the project ID). Omit to use the project linked in the Play Console.
        playIntegrityCloudProjectNumber = null
    )
)
val octet = Octet.start(context, config)

Android only: verifying a Play Integrity token needs a Google Cloud project. By default the SDK uses the project linked to your app in the Play Console. To bind a specific one, set playIntegrityCloudProjectNumber to your Google Cloud project number. The number is a non-secret identifier. iOS App Attest has no equivalent setting.

When attestation fails

If the platform attestation service returns a verdict that is not compliant, the SDK does not sign a proof it cannot stand behind. The location query returns indeterminate with reason attestationFailed. Treat it as untrusted and do not retry blindly. See Verdicts for the full reason-code table.

Verifying attestation off the device

The on-device half is automatic. The off-device half lives in the verifier. octet-verify validates attestation when built with the appattest feature:

cargo build --release --features appattest

With that build:

  • iOS App Attest is validated offline against Apple's embedded App Attest root. No network call, no secret held. The same proof verifies identically anywhere.
  • Android key attestation validates the Keystore certificate chain to Google's embedded, fingerprint-pinned hardware-attestation root, confirming the key is TEE or StrongBox backed.

Both paths are provided by octet-attest-verify, a standalone library you can also use directly if you write your own verification logic.

Play Integrity verification goes through Google

Verifying an Android Play Integrity token is not an offline operation, by Google's design: decoding and checking the token requires a round-trip to a Google Cloud project. octet-attest-verify does the proof-side half (it decodes the returned payload and confirms the token binds to the proof, by nonce and package). You supply the Google call with your own Cloud project. All three attestation paths are supported. The difference is only where the check runs: App Attest and Android key attestation verify fully offline against Apple's and Google's embedded roots. Play Integrity verifies through Google.

Where to go next

  • Verifying Proofs: what the verifier checks, including the attestation-root step.
  • Verdicts: the attestationFailed reason and the rest of the trichotomy.
  • Proof of Location: the location half of the proof, and the device-key security tier.