Appattest
Verification of Apple App Attestations and Assertions for iOS apps that use DeviceCheck App Attest. All verification is performed locally - no calls to Apple's servers are made during verification.
This is a fork of appattest-rs by Ayodeji Akinola, with the full original commit history preserved.
Why this fork?
The original crate is correct and useful, but its hot path allocates heavily, pulls in
openssl and x509-parser as dependencies, and fetches Apple's root CA certificate from
the network on every single attestation verification call - one blocking HTTPS request per
device registration. This fork rewrites the verification path with different goals and a
different API: the caller is expected to fetch and cache the root cert once (using
fetch_apple_root_cert_pem() or their own HTTP client), then pass it by reference to every
verify call.
-
Minimal allocations on the hot path. CBOR parsing uses minicbor, which borrows directly from the input slice rather than deserializing into owned structures. Intermediate certificate arrays use arrayvec to stay on the stack. The root CA PEM is decoded to DER on a stack buffer. The only unavoidable allocation on the attestation path is the Vec returned by the base64 decode of the attestation object itself.
-
Custom DER walkers instead of x509-parser. Rather than parsing a full X.509 certificate into an owned AST, extension extraction and public key extraction are done by walking the raw DER TLV structure directly. This eliminates the
x509-parserdependency entirely and avoids the allocations that come with it. -
aws-lc-rs as the crypto backend. The crate uses aws-lc-rs for all cryptographic operations - SHA-256 hashing and ECDSA signature verification.
opensslno longer appears on the hot path (it remains available as a dev-dependency behind thetestingfeature for generating synthetic test data). -
rustls-webpki for certificate chain verification. The certificate chain is verified with rustls-webpki, which pairs naturally with aws-lc-rs and avoids a separate OpenSSL dependency for that step.
The net effect is a verification path that is lighter on both dependencies and runtime allocations, making it more suitable for embedding in high-throughput servers.
Overview
Apple's App Attest service lets an iOS app prove to your server that it is genuine and unmodified. The protocol has two parts:
-
Attestation - a one-time device registration step. The device generates a key pair and produces an attestation object containing a certificate chain, receipt, and authenticator data. Your server verifies the certificate chain back to a supplied root cert (Apple's root CA, or a fabricated one for testing), checks the integrity of the authenticator data, and stores the device's public key.
-
Assertion - a per-request signing step. For each request the app produces an assertion containing a signature over a nonce derived from the request data. Your server verifies the signature against the stored public key and checks that the counter has advanced.
Features
| Feature | Default | Description |
|---|---|---|
reqwest |
yes | Enables fetch_apple_root_cert_pem() for fetching Apple's root CA over HTTPS |
testing |
no | Enables helpers for generating synthetic attestations and assertions in tests |
Installation
[]
= "0.1"
To disable the network-fetch helper and avoid the reqwest dependency:
[]
= { = "0.1", = false }
Usage
Verifying an Attestation
Fetch the Apple root cert once at startup and cache it. Pass the same bytes to every
verify call - there is no implicit network access.
use ;
If you support multiple bundle IDs or environments, use app_id_verifies instead. It
accepts a slice of app IDs and returns the one that matched along with the public key and
receipt:
let app_ids: & = &;
let =
attestation.app_id_verifies?;
Verifying an Assertion
use Assertion;
use ;
If you support multiple app IDs, use app_id_verifies instead. It does not accept a
challenge and stored challenge - the check (challenge == stored_challenge) is trivial
enough that it is left to the caller. Check it yourself before calling app_id_verifies:
if challenge != stored_challenge
let matched_app_id = assertion.app_id_verifies?;
Root cert constant
If you fetch the cert yourself (for example with your own HTTP client), the URL is exposed as a constant:
use APPLE_ROOT_CERT_URL;