Device Attestation
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.
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
attestationFailedreason and the rest of the trichotomy. - Proof of Location: the location half of the proof, and the device-key security tier.