Platform

Device CA — mTLS Device Identity

Clavex Device CA covers device/machine mTLS identity under /api/v1/organizations/{org}/devices: a Vault PKI-backed certificate authority, separate from the PAM SSH CA, that issues short-lived client certificates for IoT and machine fleets. Admin endpoints require the security resource permission; enrollment and renewal are device-facing and use a bootstrap secret / mTLS respectively, not an admin session.

The Device Lifecycle

🪪
Enrollment
One-time, per-device bootstrap secret + the device's own CSR → first certificate.
🔁
Renewal
Self-service, authenticated by the device's current certificate over a dedicated mTLS listener.
Revocation
Single-certificate or whole-device revocation, independent of CA rotation.
🔄
CA Rotation
Staged, dual-CA grace period rotation — the same pattern as the SSH CA.

0. Configure the Device CA

Point Clavex at a Vault PKI secrets engine. On first save Clavex provisions the mount, generates the root CA, and configures the device-signing role.

bash
# Point Clavex at your Vault PKI secrets engine $ curl -s -X PUT https://id.clavex.eu/api/v1/organizations/$ORG_ID/devices/ca \ -H "Authorization: Bearer $ORG_TOKEN" -H "Content-Type: application/json" \ -d '{ "vault_addr": "https://vault.acme.internal:8200", "vault_token": "hvs.CAESIJ...", "vault_role": "clavex-device-signer", "cert_ttl_seconds": 604800, "renewal_window_seconds": 172800, "bootstrap_secret_ttl_seconds": 259200 }'

1. Enrollment

Pre-register the device and issue a one-time bootstrap secret for it — scoped to that single device, never a fleet-wide shared credential. Inject the secret into the device during your physical provisioning process; the device generates its own key pair locally and sends only the CSR.

bash
# Operator: pre-register the device, issue its one-time bootstrap secret $ curl -s -X POST https://id.clavex.eu/api/v1/organizations/$ORG_ID/devices/sensor-042/enrollment-secrets \ -H "Authorization: Bearer $ORG_TOKEN" -H "Content-Type: application/json" \ -d '{"fleet_id": "warehouse-eu-west"}' | jq { "enrollment_secret_id": "a1b2...", "bootstrap_secret": "9f8e...", "expires_at": "2026-08-05T..." }
# Device: generate its own key pair + CSR (private key never leaves the device) $ openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout device.key -out device.csr -subj "/CN=sensor-042@$ORG_ID"
# Device: enroll once, with the CSR + the bootstrap secret it was provisioned with $ curl -s -X POST https://id.clavex.eu/api/v1/organizations/$ORG_ID/devices/enroll \ -H "Content-Type: application/json" \ -d "{\"device_id\":\"sensor-042\",\"bootstrap_secret\":\"9f8e...\",\"csr_pem\":\"$(cat device.csr)\"}" \ | jq -r .certificate_pem > device.crt
CN is not negotiable: the certificate's Subject Common Name is always <deviceID>@<orgID> — this is the identity format the MQTT broker's client-auth hook expects, and the CSR's own CN must already match it exactly or enrollment is rejected.

2. Self-Service Renewal

Renewal never touches the bootstrap secret or an operator. The device authenticates with its current, still-valid certificate over a dedicated mTLS listener and gets a fresh one for the same identity — safe to run unattended indefinitely.

bash
# Generate a new key + CSR for the SAME identity, then renew via mTLS $ openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout device-new.key -out device-new.csr -subj "/CN=sensor-042@$ORG_ID"
$ curl -s -X POST https://id.clavex.eu:8443/renew \ --cert device.crt --key device.key \ -H "Content-Type: application/json" \ -d "{\"csr_pem\":\"$(cat device-new.csr)\"}" \ | jq -r .certificate_pem > device-new.crt
Renewal window: devices should renew once renewal_window_seconds remains before not_after — the server accepts any renewal against a still-valid, still-active certificate, so the exact cadence is up to your device-side tooling.

3. Revocation

The real defence for a compromised device — not "stop renewing and wait", since a certificate stays cryptographically valid until its expiry regardless of whether Clavex keeps renewing it.

EndpointPurpose
POST /devices/:device_id/certificates/:serial/revokeRevoke one issued certificate by serial number
POST /devices/:device_id/revokeRevoke every active certificate for the device and flag the device itself revoked (whole-device-stolen path)
POST /devices/enrollment-secrets/:secret_id/revokeRevoke an unused (still-pending) bootstrap secret — e.g. an operator fat-fingered provisioning

Revoking a certificate dispatches a device.cert.revoked connector event carrying device_id/tenant_id/serial, so the MQTT broker (or any other external consumer) can maintain its own local deny-list without polling Clavex.

4. CA Rotation

Rare, operator/agent-driven, and staged the same way as the SSH CA's rotation:

Zero-downtime Device CA rotation: rotating the Device CA key is a staged process (POST /devices/ca/rotation/start → status polling → mark-ready → complete) so both the old and new CA certificates are trusted during cutover and no device drops its connection. This is an advanced operation — see the Admin API reference for the full state machine.