Skip to content

EST (RFC 7030)

EST (Enrollment over Secure Transport, RFC 7030) lets a device or a server request its certificate over HTTPS without a human in the loop. It is supported by network equipment (routers, switches, Wi-Fi controllers), industrial and IoT devices, and enrollment agents. The device generates its key pair, sends a PKCS#10 request, and receives its certificate.

PKIFactor runs an EST gateway in front of your certification authorities. Each certificate profile with EST enabled gets its own EST address; the gateway authenticates the client, the profile decides what may be issued (authority, algorithms, names, validity, organization), and every certificate lands in the inventory.

Tested

Tested with Microsoft AD CS as the issuing CA: /cacerts, /csrattrs, then /simpleenroll with HTTP Basic and an enrollment credential of the profile, through nginx with EST_PROXY_SECRET set (version 1.8.46); the certificate was issued by AD CS and recorded in the inventory. /simpleenroll and /simplereenroll with a client certificate are covered by automated tests against the real gateway.

Not tested against a real client: client certificate (mTLS) authentication from a device, and enrollment by a network device's built-in EST client.

What is supported

Operation Method and path Authentication Status
CA certificates GET /.well-known/est/<path>/cacerts None Supported
Initial enrollment POST /.well-known/est/<path>/simpleenroll As set on the profile Supported
Re-enrollment POST /.well-known/est/<path>/simplereenroll Client certificate, always Supported, see below
CSR attributes GET /.well-known/est/<path>/csrattrs None Returns a fixed list (subjectAltName, keyUsage, extKeyUsage), not derived from the profile
Server-side key generation POST /.well-known/est/<path>/serverkeygen Since 1.8.30. The response is multipart/mixed: the private key (application/pkcs8, unencrypted, protected by TLS, not kept by PKIFactor), then the certificate and its chain (application/pkcs7-mime). The key uses the algorithm of the request if the profile allows it, otherwise the first RSA or EC algorithm the profile allows. The profile must issue immediately
Full CMC (/fullcmc) Not supported

Details:

  • The request body is a PKCS#10 request, base64-encoded DER (as RFC 7030 specifies) or PEM, with Content-Type: application/pkcs10. The CSR signature is checked (proof of possession).
  • The response is a base64-encoded PKCS#7 (application/pkcs7-mime; smime-type=certs-only) carrying the certificate, and the CA chain when the authority returns it.
  • Re-enrollment requires the client to present its current certificate over TLS, and the CSR common name must be the same as that certificate's. It is processed as a new issuance: the new certificate is not linked to the one it replaces in the inventory.
  • Issuance is synchronous. There is no 202 Accepted with Retry-After: a request that cannot be issued at once fails.

Addresses

The EST gateway has its own DNS name (for example est.example.com, see Requirements) and is reached through nginx on port 443:

https://est.example.com/.well-known/est/<path>/simpleenroll

<path> is the profile's EST path. PKIFactor generates it from the profile name when you enable EST (est- followed by the name in lower case, for example est-iot-devices) and keeps it afterwards. Disabling EST on the profile clears it.

Port 80 on the EST name only redirects to HTTPS. The gateway exists only when the est service is started and EST_ENABLED=true (see Docker Compose and Network flows). It presents the TLS certificate of the EST name (see Service certificates): your clients must trust its CA.

Enable EST on a profile

  1. In the Administration workspace, open Infrastructure > Certificate Profiles, open the profile and click Edit Profile.
  2. Unfold Automatic enrollment (ACME, EST, CMP, SCEP), then in EST Protocol tick Enable EST for this profile.
  3. Choose the Authentication Method (see the next section).
  4. Save. The profile page then shows the Enrollment path and the Enrollment endpoint.

Rules applied when you save:

Rule Why
Require validation before issuance is turned off and cannot be ticked Client authentication replaces manual approval (see Approval)
The profile must belong to one organization Nobody is there to choose an organization behind a gateway. Otherwise the save is refused: Un profil activé pour ACME, EST, SCEP ou CMP doit être rattaché à une seule organisation : une passerelle ne peut pas choisir.

The rest of the profile applies to EST requests as to any other: authority, allowed algorithms, subject and name constraints, validity. See Certificate profiles.

Via the API

The profile is updated with a partial JSON body (administrator session). Send requires_approval: false if the profile currently requires approval.

curl -s -c jar -X POST https://clm.example.com/api/auth/login \
  -d "email=admin@example.com" --data-urlencode "password=$ADMIN_PASSWORD"
curl -s -b jar -X PUT https://clm.example.com/api/v1/clm/templates/12 \
  -H "Content-Type: application/json" \
  -d '{"est_enabled": true, "est_auth_method": "basic", "requires_approval": false}'

The response contains the generated est_path.

Client authentication

Authentication Method What the client presents Typical use
TLS Client Certificate (Recommended) (default) A client certificate, validated by nginx Devices that already hold a certificate (manufacturer certificate, previous enrollment)
HTTP Basic Authentication An enrollment credential of the profile (username and secret) Bootstrapping devices that have no certificate yet
Both (Certificate + Basic) Either one; one is enough Mixed fleets

Client certificate (mTLS)

nginx asks for a client certificate on the EST name and validates it against the CA bundle ca-chain.pem in the nginx certificate folder. Put in that file the CAs that issue your devices' certificates, including the CA that issues EST certificates if devices will re-enroll with them. If the file is absent, nginx does not request client certificates at all.

The gateway trusts the identity only when nginx reports a successful validation and proves that the headers come from it: nginx adds a shared secret (EST_PROXY_SECRET) that the gateway checks. Without that secret, client certificate authentication is refused. The gateway reads the common name and the alternative names from the certificate itself. The CSR common name must equal the client certificate's common name, and every alternative name of the CSR must appear in the client certificate; otherwise the request is refused (403): a device can only enroll its own identity. A client certificate that is revoked or suspended is refused.

Enrollment credentials (HTTP Basic)

Credentials are dedicated to one profile: an application account of PKIFactor is not accepted. The secret is stored as a bcrypt hash and shown once.

  1. On the profile page, in the EST card, click Enroll (EST).
  2. Enter a Username and optionally a Description, then Generate credential.
  3. Copy the secret now (Secret shown ONCE). The window also shows a ready-made curl command; replace its host name with the EST name (est.example.com), since the window builds the address from the page you are on.

Existing credentials lists the profile's credentials with a Revoke action (deletion).

A credential can be used for several enrollments until it is revoked or expires. After 5 wrong secrets in a row it is locked temporarily: 10 seconds at the fifth failure, then twice as long at each further failure, up to 15 minutes. A correct secret after the lock ends resets the counter. The gateway also limits enrollment requests per client address (30 per minute by default, ENROLL_RATE_LIMIT) and the size of a request body (256 KiB by default, ENROLL_MAX_BODY_BYTES).

Via the API
# Create a credential (the secret is generated when "password" is omitted, returned once)
curl -s -b jar -X POST https://clm.example.com/api/est/templates/12/credentials \
  -H "Content-Type: application/json" \
  -d '{"username": "fleet-building-a", "description": "Building A sensors",
       "expires_at": "2026-12-31T23:59:59+00:00"}'

# List (without secrets), then revoke
curl -s -b jar https://clm.example.com/api/est/templates/12/credentials
curl -s -b jar -X DELETE https://clm.example.com/api/est/credentials/7

Approval and issuance policies

EST requests are never sent for manual approval: client authentication stands for it. This exemption applies to requests that arrive through the EST gateway only; a request made in the interface on the same profile follows the profile's normal rules.

Issuance policies still apply: a rule that blocks a request blocks it over EST too. The client receives 400 with the reason, which for a blocking rule is the message written on the rule, and the audit trail records POLICY_BLOCK. If the policies cannot be evaluated, the request is refused rather than allowed (POLICY_EVAL_ERROR in the audit trail). A rule that would require approval cannot be honored on an automated channel; the request goes through and the event POLICY_AUTO_ALLOWED is recorded so that it stands out in the audit trail and the SIEM.

Audit and inventory

  • Each certificate appears in the inventory under the profile's organization, requested by the service account est@pkifactor.com. Its request data keeps the EST path, the client certificate common name or the credential username.
  • The PKIFactor audit trail (Oversight workspace, Compliance > Audit Trail) records the issuance (CERTIFICATE_ISSUED), policy refusals (POLICY_BLOCK, POLICY_AUTO_ALLOWED) and, when a gateway sends an unproven HTTP Basic request, EST_BASIC_UNVERIFIED (see Audit).
  • The gateway also keeps its own log of every EST operation (operation, profile path, client identity, common name, result, certificate serial) in the est.est_audit_logs table of the database.

Client example

# 1. CA certificates (no authentication)
curl -s --cacert est-tls-ca.pem \
  https://est.example.com/.well-known/est/est-iot-devices/cacerts \
  | base64 -d > cacerts.p7
openssl pkcs7 -inform DER -in cacerts.p7 -print_certs -out ca-bundle.pem

# 2. Key and CSR on the device
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout device.key -out device.csr -subj "/CN=sensor-001.example.com"

# 3. Enrollment with an enrollment credential (HTTP Basic)
openssl req -in device.csr -outform DER | base64 -w0 > device.b64
curl -s --cacert est-tls-ca.pem \
  -u "fleet-building-a:$EST_SECRET" \
  -H "Content-Type: application/pkcs10" --data-binary @device.b64 \
  https://est.example.com/.well-known/est/est-iot-devices/simpleenroll \
  | base64 -d > device.p7
openssl pkcs7 -inform DER -in device.p7 -print_certs -out device.pem

# 4. Later: re-enrollment, authenticated by the current certificate (mTLS)
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout device-new.key -out device-new.csr -subj "/CN=sensor-001.example.com"
openssl req -in device-new.csr -outform DER | base64 -w0 > device-new.b64
curl -s --cacert est-tls-ca.pem --cert device.pem --key device.key \
  -H "Content-Type: application/pkcs10" --data-binary @device-new.b64 \
  https://est.example.com/.well-known/est/est-iot-devices/simplereenroll \
  | base64 -d > device-new.p7

est-tls-ca.pem is the CA of the EST name's TLS certificate. The profile must allow the key algorithm the device uses.

Troubleshooting

Symptom Cause and fix
404 EST profile not found or EST not enabled Wrong EST path, EST disabled on the profile, or profile inactive. Copy the path from the profile page
401 Client certificate required The profile is in certificate mode and no validated client certificate reached the gateway. Check that the device's CA is in ca-chain.pem and that the device sends its certificate
401 Client certificate required for re-enrollment /simplereenroll always requires the current certificate over TLS
401 HTTP Basic authentication required or Authentication required Wrong, revoked, expired or locked credential (from the fifth failure in a row it is locked for a period that grows up to 15 minutes), or no credentials sent
500 Certificate issuance failed on a profile in HTTP Basic mode, while the credential is correct The client also presents a client certificate; the backend logs EST basic auth requires prior credential validation. In HTTP Basic mode, send the credential only, or switch the profile to Both
403 CSR subject CN must match the authenticated client certificate identity. The CSR common name differs from the client certificate's
415 Content-Type must be application/pkcs10 Set the header Content-Type: application/pkcs10
400 Invalid CSR signature (proof of possession failed) Corrupted CSR, or a raw binary DER body: send base64-encoded DER or PEM
400 with a reason (for example Key algorithm ECC-P256 not allowed for this profile) Refused by the profile or an issuance policy: fix the CSR (key algorithm, subject). For a blocking issuance policy rule, the reason is the rule's message; the POLICY_BLOCK event in the audit trail names the rule
429 Too many enrollment requests from the same address: wait and retry
413 Request body too large
500 Certificate issuance failed The authority refused or could not be reached. The reason is in the backend logs and the audit trail
502 on /cacerts The PKI connector could not return the CA chain
409 on /serverkeygen The profile did not issue immediately (approval, or a CA that answers later): server-side generation needs direct issuance
Client certificate ignored, gateway log Ignoring mTLS headers from untrusted source or X-Proxy-Auth absent ou invalide The gateway accepts client certificate headers only from nginx: EST_PROXY_SECRET must be set, with the same value, for nginx and for the EST service (the appliance generates it), and the request must come from a trusted network (TRUSTED_PROXY_NETWORKS)
Enroll (EST) does nothing Before version 1.8.50, the window opened only when ACME was also enabled on the profile: upgrade, or create the credential through the API (see above)