Enroll devices with EST
EST (RFC 7030) lets devices request and renew certificates over HTTPS. Compared to SCEP it adds two things that matter for a fleet: devices can renew using the certificate they already hold, and each device can bootstrap with an identity of its own instead of a secret shared across the fleet.
For the background on device enrollment, see Device certificate enrollment.
Enable EST on a CA
Create or update a TLS CA with an est section. Unlike SCEP, EST does
not require an RSA-keyed CA, so the platform default (ECDSA) is fine:
supctl create strongbox tls ca <<EOF
name: devices
est:
enabled: true
initial-enroll-secret: <a strong shared secret>
allowed-domains:
- example.com
allow-subdomains: true
ttl: 30d
EOF
EST shares its identity policy (allowed-domains, allow-subdomains,
allow-bare-domains, allow-ip-sans, ttl) with SCEP, so a
certificate carries the same guarantees whichever protocol a device
used. See Enroll devices with SCEP for what each
setting does.
EST is served on its own port, 4665. The CA publishes the URLs:
supctl show strongbox tls ca devices --fields est
est:
enabled: true
allowed-domains:
- example.com
ttl: 30d
https-url:
- https://control-tower.example.com:4665/.well-known/est/<tenant-uuid>/devices/simpleenroll
- https://192.168.1.10:4665/.well-known/est/<tenant-uuid>/devices/simpleenroll
Three endpoints live under the same base path:
| Endpoint | Purpose |
|---|---|
/cacerts | Fetch the CA certificate chain. Unauthenticated. |
/simpleenroll | First enrollment. |
/simplereenroll | Renewal, authenticated by the certificate the device already holds. |
Choose how devices authenticate their first enrollment
A device has no certificate from this CA yet when it first enrolls, so something else has to vouch for it. EST supports two methods, and you can configure either or both.
A shared secret
initial-enroll-secret is presented with HTTP Basic authentication over
TLS. It is simple, and it is the direct equivalent of the SCEP challenge
password, with the same caveat: every device shares it, so the identity
policy is what limits what a holder can obtain.
curl --cert-type PEM \
-u device:<the shared secret> \
-H "Content-Type: application/pkcs10" \
-H "Content-Transfer-Encoding: base64" \
--data-binary @request.b64 \
https://<host>:4665/.well-known/est/<tenant-uuid>/devices/simpleenroll
A per-device birth certificate
initial-enroll-trust-ca names another CA whose certificates are
accepted as TLS client authentication for first enrollment. A device
presenting a client certificate issued by that CA -- typically a factory
installed birth certificate -- may enroll with no shared secret at all.
Each device then authenticates as itself:
supctl merge strongbox tls ca devices <<EOF
est:
initial-enroll-trust-ca: device-birth
EOF
The device enrolls by presenting its birth credential:
curl --cert birth.pem --key birth.key \
-H "Content-Type: application/pkcs10" \
-H "Content-Transfer-Encoding: base64" \
--data-binary @request.b64 \
https://<host>:4665/.well-known/est/<tenant-uuid>/devices/simpleenroll
Prefer this for a fleet. A shared secret compromised on one device compromises enrollment for all of them; a birth certificate does not.
The EST port accepts client certificates from any CA, because a birth
certificate is by definition not issued by this system. Acceptance at
the TLS layer is not a trust decision: the certificate is then verified
against initial-enroll-trust-ca, and enrollment is refused with 401
if it does not chain to it. Only the EST endpoints are served on this
port.
Renewal
With allow-reenroll-with-issued-cert (on by default), a device renews
by calling simplereenroll with its current certificate as the TLS
client certificate:
curl --cert device.pem --key device.key \
-H "Content-Type: application/pkcs10" \
-H "Content-Transfer-Encoding: base64" \
--data-binary @request.b64 \
https://<host>:4665/.well-known/est/<tenant-uuid>/devices/simplereenroll
No shared secret is involved, which is what makes short certificate lifetimes practical: renewal needs no operator and no distributed secret. Have devices renew well before expiry, so a device that has been unreachable for a while still has a valid certificate to renew with.
Enroll a post-quantum credential
EST accepts ML-DSA (NIST FIPS 204) certificate requests, so a device can hold a fully post-quantum credential. Create a CA with an ML-DSA key and enable EST on it:
supctl create strongbox tls ca <<EOF
name: devices-pq
cert-key-type: ml-dsa-65
est:
enabled: true
initial-enroll-secret: <a strong shared secret>
allowed-domains:
- example.com
allow-subdomains: true
ttl: 30d
EOF
A device with OpenSSL 3.5 or later enrolls exactly as before, using an ML-DSA key:
openssl genpkey -algorithm ML-DSA-65 -out device.key
openssl req -new -key device.key -subj "/CN=sensor-7.example.com" \
-outform DER | openssl base64 -A > request.b64
Both the device's key and the CA's signature on the issued certificate are then ML-DSA:
openssl x509 -in device.crt -noout -text | grep -i ML-DSA
SCEP cannot do this: its enrollment envelope depends on RSA key transport, so an ML-DSA-keyed CA cannot serve it. EST is the path forward for post-quantum device credentials.
Troubleshooting
401 Unauthorized. The credential was not accepted:
- With a shared secret, check it matches
initial-enroll-secret. - With a birth certificate, check that it chains to the CA named in
initial-enroll-trust-ca, and that the CA still exists. - On renewal, check the client certificate was issued by this CA, is still valid, and has not been revoked.
- If neither method is configured on the CA, enrollment is refused with
403instead: there is nothing a device could present.
403 Forbidden. The request was authenticated but the requested
identity is not permitted. Check the CN and DNS SANs against
allowed-domains, allow-subdomains and allow-bare-domains, and
whether the request carries an IP SAN while allow-ip-sans is off.
400 Bad Request. The body was not a well-formed PKCS#10 request,
or its self-signature did not verify. Confirm the request is DER,
base64-encoded, and signed by the key being certified.
404 Not Found. EST is not enabled on that CA, or the tenant or CA
name in the URL is wrong. Use the URLs published under the CA's est
section.