Device registry
Every attesting device is enrolled with an X.509 certificate. The registry is what lets verification answer "is this a device we know, and is it still trusted?"
Enrollment
A device is enrolled via POST /v1/admin/devices (used by the capture-simulator onboarding). Enrollment stores the device's certificate and binds it to a carrier and environment.
- A bundle from an enrolled device with a valid cert chain scores no device penalty.
- A bundle from an unknown device scores
device_not_enrolled(+20) — not a rejection, a signal. This is how legacy/web uploads surface. - A cert not signed by a known root CA scores
cert_unknown_ca(+40).
Hardware tier
Every device carries a server-verified hardware_tier: sep (iOS Secure Enclave), strongbox, tee, software, or simulated. The tier is assigned by the server from attestation evidence — never a value the client reports. A boolean "trust me" flag from a device is worthless in a fraud product.
| Tier | Meaning | Evidence |
| --- | --- | --- |
| sep | iOS Secure Enclave | verified App Attest object (INTEGRITY_CHECKS=on) |
| strongbox | Android StrongBox (dedicated secure chip) | Key Attestation, keymasterSecurityLevel = StrongBox |
| tee | Android TrustZone | Key Attestation, keymasterSecurityLevel = TEE |
| software | no proven hardware backing | evidence absent, unverifiable, or verification failed |
| simulated | browser-simulator software mock | no platform/evidence (simulator + CLI) |
- Android devices present a Key Attestation X.509 chain (
key_attestation_chain, generated with a server-issued challenge). The API parses its attestation extension to read the security level, and underINTEGRITY_CHECKSverifies the chain terminates at a pinned Google attestation root and the challenge matches. An optional Play Integrity verdict (play_integrity_token) is verified (JWS + package + nonce) and recorded as a corroborating signal indevice_integrity_evidence— it does not decide the tier. - iOS devices present an App Attest object (
app_attest_object). UnderINTEGRITY_CHECKSthe server verifies it (CBOR parse → the x5c chain to a pinned Apple App Attest root, the nonce = SHA-256(authData ‖ SHA-256(challenge)), andrpIdHash = SHA-256(appId)); success →sep. Off, or any failure, →software— evidence stored raw, never faked. (Pinning Apple's real root is a device-runbook step; until then, iOS fails closed tosoftware.) - Evidence absent or unverifiable →
software. Devices enrolled without any platform/evidence (the browser capture simulator and CLI) staysimulated.
Get a challenge first with POST /v1/admin/devices/enrollment-challenge, bind it into the key attestation, then pass the evidence to POST /v1/admin/devices. An enrolled device whose tier is software scores hardware_backing_absent (+40); sandbox simulated devices are exempt, so existing fixtures are unaffected.
The native capture SDK that produces this evidence is @provenance/react-native — Secure Enclave / StrongBox signing, biometric-gated captures, zero-bridge media.
Revocation
POST /v1/admin/devices/:device_id/revoke with {reason} marks a device revoked. All future attestations from it score device_revoked (+80). Existing attestations keep their original scores — revocation is forward-looking.
Certificate lifecycle
Device certs carry a validity window (the sandbox test device is valid two years). device_info.cert_valid and cert_expires on the verification result tell you where a cert stands. Production PKI (OCSP/CRL/HSM) is not yet supported — the registry here is a simple enrolled/revoked table plus root-CA chain validation.
The sandbox device
The sandbox ships one enrolled test device, 00000000-0000-0000-0000-000000000001, whose cert signs all the seeded fixtures. List it:
curl "$PROVENANCE_API/v1/admin/devices?limit=1" \
-H "Authorization: Bearer $PRV_KEY"