Device Identity
Certificate-based device enrollment and authentication
What is Device Identity?
Device Identity is Orion's certificate-based authentication primitive for machines, agents, and IoT devices. Instead of a static API key, a device holds an Orion-issued X.509 certificate that proves its identity on every request via a signed assertion.
Each organisation has its own Certificate Authority (CA) — generated lazily by Orion and encrypted at rest. When an admin registers a device, Orion issues a one-time enrollment code. The device generates a key pair locally, submits a Certificate Signing Request (CSR) with the code, and receives a signed leaf certificate. The private key never leaves the device.
Use device identity when:
- You need stronger auth than shared secrets (no API keys to rotate or leak)
- You want hardware-bound keys via TPM attestation (non-exportable, tamper-resistant)
- Sovereign signing agents need a cert-based identity for the agent→Orion channel
- You're enrolling servers, edge devices, or CI/CD runners that need machine-to-machine auth
Manage devices at /devices/manage/{your-org}
— register devices, view enrollment status, revoke certificates. The steps below show the API/SDK equivalent.
The device CA (Orion-held) issues certs for device→Orion transport authentication only. The sovereign signing trust root (external/HSM) is validated SDK-side for token signing. A compromised Orion can forge device-auth certs but cannot forge sovereign-signed tokens. These two PKIs share no key, CA, or trust store.
1 Register a Device (Admin)
An admin generates a one-time enrollment code for the device. The code is short-lived (default 15 minutes) and shown only once.
UI: go to /devices/manage/your-org, fill in the device name, and click "Generate Enrollment Code".
The admin can optionally:
- Require TPM attestation for this specific enrollment (checkbox)
- Constrain the device's certificate subject CN
- Pre-bind the enrollment to a sovereign signing agent
2 Enroll the Device
One line — macOS / Linux (curl + openssl)
The server hosts a per-org enrollment script with the base URL and org baked in:
One line — Windows (PowerShell 7+)
Native PowerShell variant using .NET crypto (no openssl needed). Requires PowerShell 7 (pwsh) — Windows PowerShell 5.1 lacks the needed APIs:
Both generate a key pair + CSR locally (the private key never leaves the device), enroll, and write device-cert.pem, device-key.pem, ca-cert.pem to ~/.orion/devices/{server}/{org}/ (%USERPROFILE%\.orion\devices\… on Windows).
Standard enrollment (SDK)
TPM-attested enrollment (hardware-bound key)
When the admin or org policy requires TPM attestation, the enrollment uses a two-step challenge-response flow that proves the key is non-exportable and lives in a genuine TPM.
3 Authenticate as a Device
The device authenticates by sending a short-lived signed assertion JWT with its certificate chain in the x5c header.
For sovereign signing agents, the SovereignAgentClient handles this automatically when you use the
device-auth constructor — see the Sovereign Signing Guide.
The high-level SDK client
Easiest path — OrionDeviceClient auto-detects the local registration and handles token caching:
Credentials are stored per server+org under ~/.orion/devices/{server}/{org}/ (override with ORION_DEVICE_DIR), so one device can be enrolled with multiple Orion servers.
Manage Local Registrations
List, get tokens for, or remove the device registrations stored on a machine — across all servers it's enrolled with:
Windows (PowerShell 7+):
TPM Attestation Policy
TPM attestation can be required at three levels with inheritance. The most specific level wins:
| Level | Setting | Scope |
|---|---|---|
| Organisation | RequireTpmAttestation |
Default for all devices in the org |
| Sovereign Signing Pool | RequireTpmAttestation |
Override for agents in this pool |
| Enrollment Code | RequireTpmAttestation |
Override for this specific device |
Resolution: enrollment.RequireTpm ?? pool.RequireTpm ?? org.RequireTpm ?? false.
When TPM is required, the single-shot /devices/enroll endpoint returns tpm_required. The device must use the two-step /devices/enroll/begin + /devices/enroll/complete flow with attestation evidence.
How TPM Attestation Works
The two-step flow proves three things: the device key lives in a real TPM, it's non-exportable, and the TPM is genuine (vendor-rooted).
- begin: The device sends its EK certificate + attestation key (AK) public area. Orion validates the EK chains to a trusted TPM vendor root (Infineon/STM/Intel/AMD/Microsoft), then issues a
MakeCredentialchallenge — a nonce encrypted to the EK, bound to the AK. - ActivateCredential: Only the TPM holding both the EK and AK can recover the nonce. This proves the AK is co-resident with the genuine EK.
- complete: The device creates a non-exportable signing key (
fixedTPM|fixedParent) in the TPM, has the AKcertifyit (proving the key's attributes), generates a CSR from it, and sends everything back. Orion verifies the certify signature, that the key is hardware-bound, and that the CSR public key matches the attested key — then issues the certificate.
Revocation
Revoke a device from the admin UI or API. Revocation is instant — the device's certificate thumbprint is checked on every authentication request against the database. No CRL/OCSP infrastructure is needed because Orion is the sole relying party. Revoking a device also deactivates any linked sovereign signing agent.
Configuration
Troubleshooting
| Symptom | Cause & Fix |
|---|---|
| invalid_code at enrollment | Code expired (default 15 min), already used, or wrong org. Generate a new one. |
| tpm_required returned from /enroll | TPM policy is active. Use the begin/complete flow with EnrollWithTpmAsync. |
| ek_untrusted at begin | EK certificate doesn't chain to any root in ORION_TPM_VENDOR_ROOTS. Add your TPM vendor's root cert to the directory. |
| activation_failed nonce mismatch | The TPM couldn't recover the nonce. Ensure the AK and EK are co-resident in the same TPM. Session may have expired. |
| attestation_failed: not hardware-bound | The device key was created without fixedTPM|fixedParent. Recreate it with the correct attributes. |
| Assertion replay detected | Same jti used twice. The SDK generates a new jti per assertion; ensure you're calling CreateAssertion() fresh, not caching. |
| Device is not active (401) | Device was revoked by an admin. Re-enroll with a new code. |
API Endpoints
POST /oauth/{org}/devices/enroll— Standard enrollment (code + CSR → cert)POST /oauth/{org}/devices/enroll/begin— TPM enrollment step 1 (code + EK cert + AK pub → credential blob)POST /oauth/{org}/devices/enroll/complete— TPM enrollment step 2 (session + CSR + attestation → cert)GET /oauth/{org}/devices/ca.pem— Org device CA certificate (public)GET /devices/manage/{org}— Admin: list devices + register formPOST /devices/manage/{org}/register— Admin: generate enrollment codePOST /devices/manage/{org}/{id}/revoke— Admin: revoke a device