Home · docs · provider attestation
spec · v0.4.3
Provider attestation
Every model an Umbra provider serves is backed by a chain that proves the physical machine is the genuine, unmodified Apple device it claims to be — and that the binary running on it is the audited Umbra provider, not a fork.
The five layers
Secure Enclave P-256
The private key is generated and held inside the SE; it never leaves the chip, even to RAM. Every serving-statement is signed by this key. The coordinator and the consumer both verify the signature against the public key in the attestation blob.
MDM SecurityInfo
An MDM (e.g. MicroMDM, Mosyle, Jamf) pushes a profile that asserts the device is enrolled, not jailbroken, and has the expected posture. The profile is signed by the MDM's CA. The coordinator cross-checks the SE-asserted serial against the MDM-asserted serial.
Apple MDA X.509 chain
The attestation blob includes the full Managed Device Attestation certificate chain. The coordinator validates it against the Apple Enterprise Attestation Root CA, then extracts the SE public key + freshness nonce + Apple-arc OID extensions (serial, SepOS version, SIP, SecureBoot).
5-minute freshness challenge
Every 5 minutes the coordinator issues a fresh nonce. The provider binary's SE signs the nonce. A provider that can't keep up — or that changed state between challenges (e.g. debugger attached) — fails the freshness check and drops out of the network.
APNs code-identity
The Swift provider binary registers with APNs to prove it's the audited build (DeviceCheck + App Attest code-identity), not a same-look-alike. This is what stops a malicious operator from running a modified binary on real Apple hardware.
On a real Mac
The production path is the Swift binary at umbra/provider/Sources/UmbraProvider/. It calls, in-process:
// Generate the SE keypair (non-exportable, hardware-backed)
let priv = SecKeyCreateRandomKey([
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: 256,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
], &error) as! SecKey
// Fetch the Apple Managed Device Attestation blob
let dc = DCDevice.current
let attestation = try await dc.generateAttestation()
// Submit to the coordinator
try await coordinator.register(
attestation: attestation, // the blob + cert chain
sePublicKey: priv.publicKeyDER(),
encryptionKey: x25519.publicKey
)From a shell:
git clone https://github.com/owenisa/umbra
cd umbra/provider
swift build -c release
./.build/release/umbra-provider init \
--handle your-handle \
--token "$UMBRA_TOKEN"On Linux / dev box
The scripts/umbra-provider Python CLI is a portable reference. In dev/sim mode it builds a synthetic blob that the coordinator's Go MDAValidator accepts when configured with test roots.
pip install cryptography # optional — openssl fallback works
umbra-provider dev-sim --handle your-handle --token "$UMBRA_TOKEN"
umbra-provider status
umbra-provider attest # prints the full blobThe script writes two files in ~/.umbra/:
provider.json— SE + encryption keypairs, your handle, the trust level. Mode 0600. The SE private key on a real Mac is non-exportable; on dev it lives in this file.attestation-blob.json— the full blob sent to the coordinator. The coordinator fingerprints the public key + serial and stores that, not the full blob.
The 5-minute freshness protocol
provider coordinator
| |
| GET /v1/attest/challenge |
| nonce + deadline + challenge_id |
| <- (response from coordinator) |
| |
| SE_Sign(canonical({ |
| challenge_id, |
| nonce, |
| publicKey: se_pub, |
| deadline |
| })) |
| |
| POST /v1/attest/challenge/answer |
| body: challenge_id + signature |
| |
| verify: deadline / sig / pubkey |
| -> 200 OK + grant_quota per 5 min |If the provider can't answer in 30s, or the signature is invalid, or the public key doesn't match the registration, the coordinator evicts the provider from the network.
Coordinator verification (Go)
The full verifier is in coordinator/internal/verifier/. The chain check is MDAValidator.Validate(report):
- Parse the MDA cert chain (leaf + intermediates).
- Verify against the Apple Enterprise Attestation Root CA pool using
crypto/x509 Certificate.Verify. - Extract the Apple-arc OID extensions from the leaf.
- Cross-check:
leaf.SerialNumber == blob.serialNumberandleaf.FreshnessNonce == SHA-256(raw SE public key). - Verify the SE signature over the canonical JSON of the blob (minus
signature).
All checks fail-closed. Test vectors live in coordinator/internal/verifier/mda_test.go; sim-mode vectors in mda_sim_test.go (the synthetic blob from umbra-provider dev-sim validates against the sim root).
umbra/docs/attestation.md in the repo. The umbra/provider/ Swift code is the production implementation; the scripts/umbra-provider Python CLI is the dev reference. The Go verifier in coordinator/internal/verifier/ is the load-bearing cryptographic check.