# EST

Use EST for network infrastructure such as VPN gateways, routers, switches, appliances, and IoT fleets.

Each endpoint has its own base URL:

```text
https://pki.example.com/.well-known/est/<endpoint-id>
```

The base provides `/cacerts`, `/simpleenroll`, `/simplereenroll`, and `/csrattrs`. Most clients require only the base URL. HTTPS is required.

On the **EST** tab of SCEP · ACME · EST, select **Add endpoint**, enter a name, and select an issuing CA.

![An EST endpoint page, with base URL and enrollment credentials](/assets/docs/est-endpoint-page.png)

## Clients authenticate with a password, not a certificate

Configure your client for HTTP Basic. A client set up to present a TLS client certificate will be refused.

SimpleSCEP terminates TLS before the application, so it uses **HTTP Basic over server-authenticated TLS** as permitted by RFC 7030 §3.2.3.

The username and password are sent with every enrollment and re-enrollment.

Plaintext requests return `403` without an authentication challenge. Revoke any credential sent over plaintext.

`/simplereenroll` is authenticated by two things together:

1. the same Basic credential, and
2. a **binding check** — the subject in the CSR must already hold a live certificate this endpoint issued, and the request must fall inside the renewal window.

An expired EST credential blocks both enrollment and renewal. Leave the expiry blank for long-lived fleets.

## Enrollment credentials

Under **Enrollment credentials**, select **Mint credential**. Use one per fleet or device. The password is shown once; mint another if it is lost.

![The Mint an enrollment credential dialog](/assets/docs/est-credential.png)

| Field                         | What it does                                                                                                                                                                                                                                                                                                                              |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Username                      | The Basic username. No spaces or colons, up to 64 characters, and unique per endpoint, case-insensitively                                                                                                                                                                                                                                 |
| Label                         | For your records only                                                                                                                                                                                                                                                                                                                     |
| Pin to these names (optional) | Comma-separated exact names. A client using this credential can enroll only these, checked against the CSR's common name, DNS names, and IP addresses. The match is exact, not a prefix or substring — a CSR common name of `gw-muc-01` will not match a pin of `gw-muc-01.example.internal`. Blank falls back to the endpoint's patterns |
| Expires after (hours)         | Bounds how long the credential works, for enrollment **and** renewal. Blank never expires                                                                                                                                                                                                                                                 |

**Revoking a credential stops the next enrollment and the next renewal.** Certificates already issued keep working until they expire.

For provisioning scripts, the route also accepts JSON:

```bash
curl -sS -X POST \
  https://pki.example.com/api/est/endpoints/<endpoint-id>/credentials \
  -H 'Content-Type: application/json' \
  -d '{"username":"branch-gateways","label":"EMEA branches","identifiers":"gw-muc-01.example.internal","ttl_hours":720}'

# → {"url":"…/.well-known/est/<endpoint-id>","username":"branch-gateways","password":"…"}
```

## Configure a client

Every client needs to trust every certificate above the one `/simpleenroll` hands back — and that's more than one file for most organizations. Two separate things feed into it:

1. **Your own CA's chain**, fetched once from `/cacerts` (no credentials needed — see below). This is **one certificate for a single-tier CA, or two for a root-plus-issuing-CA setup**, which is the common case. `/simpleenroll` and `/simplereenroll` return only the newly issued leaf, never the chain above it, so a client needs this fetched and trusted ahead of time — the same way it needs the credential ahead of time.
2. **The public or private root behind your deployment's TLS certificate**, which verifies the connection to the endpoint itself and is independent of your SimpleSCEP CA.

A browser or a machine with a full OS certificate bundle already trusts #2 and needs nothing extra for it. Many EST clients — strongSwan's `pki` tool included — trust only what you hand them explicitly and don't fall back to a system store, so both are needed regardless of how many certificates are in each.

Fetch your chain once, from a trusted channel, and keep each certificate in its own file — most clients that take an explicit trust list read only one certificate per file or per argument, so a bundle with more than one certificate concatenated together silently loses everything after the first:

```bash
curl -s https://pki.example.com/.well-known/est/<endpoint-id>/cacerts \
  | base64 -d | openssl pkcs7 -inform DER -print_certs -out chain.pem
awk '/BEGIN CERTIFICATE/{n++} {print > ("simplescep-ca" n ".pem")}' chain.pem
# simplescep-ca1.pem is the certificate nearest your device's; simplescep-ca2.pem
# (if your CA is root-plus-issuing) is the root above it.
```

Get the TLS root from your deployment operator through a trusted channel rather than fetching it from the endpoint itself. Use a self-signed trust anchor; a client that only trusts self-signed roots may reject a cross-signed copy of the same key.

**strongSwan**

```bash
pki --est --url https://pki.example.com --label <endpoint-id> \
    --userpass 'branch-gateways:<password>' \
    --in gw-muc-01.req \
    --cacert simplescep-root.pem \
    --cacert simplescep-issuer.pem \
    --cacert deployment-tls-root.pem \
    --outform pem > gw-muc-01.pem
```

`--url` is the base URL only. strongSwan builds `/.well-known/est/<label>/<operation>` itself, so the endpoint ID goes in `--label`; putting the whole path in `--url` yields `/.well-known/est/<endpoint-id>/.well-known/est/simpleenroll` and a 404 that looks like a missing endpoint rather than a malformed request.

`--cacert` can be repeated, and here it has to be: one for each certificate above, three in a root-plus-issuing setup. strongSwan reads only the first certificate out of each PEM file, so concatenating any of these into one file silently drops the rest — pass each as its own `--cacert`, never merged.

strongSwan also does not match wildcard certificates when verifying the server's hostname. The deployment hostname must appear explicitly in the TLS certificate's SANs.

**Cisco IOS**

```text
crypto pki trustpoint SIMPLESCEP
 enrollment url https://pki.example.com/.well-known/est/<endpoint-id>
 enrollment mode est
 enrollment credential BRANCH-GATEWAYS
 subject-name CN=gw-muc-01.example.internal
 revocation-check crl
 auto-enroll 80
```

`auto-enroll 80` re-enrolls at 80% of the certificate lifetime. For a 365-day certificate, use a renewal window of at least 73 days.

IOS ships a broad public trust bundle, so it usually needs only your own CA's chain imported (`crypto pki trustpoint`/`crypto pki authenticate`, once per certificate for a root-plus-issuing CA) and not the public root above. Confirm with `show crypto pki certificates` if enrollment fails at the TLS step rather than at the credential.

**libest**

```bash
estclient -e -s pki.example.com -p 443 \
    --path-prefix /.well-known/est/<endpoint-id> \
    -u branch-gateways -h '<password>' \
    -o ./out --pem-output
```

libest also trusts only what it's given explicitly — pass every certificate from the section above as its explicit trust anchors, not just your issuing CA's, and not just one file if your CA is root-plus-issuing.

**curl**, for checking an endpoint by hand

```bash
# The chain, with no credentials at all:
curl -s https://pki.example.com/.well-known/est/<endpoint-id>/cacerts \
  | base64 -d | openssl pkcs7 -inform DER -print_certs -noout

# An enrollment:
openssl req -new -newkey rsa:2048 -nodes \
  -keyout gw.key -out gw.csr -subj /CN=gw-muc-01.example.internal
openssl req -in gw.csr -outform DER | base64 > gw.b64
curl -s -u branch-gateways:'<password>' \
  -H 'Content-Type: application/pkcs10' \
  --data-binary @gw.b64 \
  https://pki.example.com/.well-known/est/<endpoint-id>/simpleenroll \
  | base64 -d | openssl pkcs7 -inform DER -print_certs
```

`/cacerts` requires no credentials, as required by RFC 7030 §4.1.1. Distribute the resulting chain through a trusted channel; do not fetch it and trust it in one step outside a lab. curl and openssl must also trust the root behind your deployment's TLS certificate.

## Issuance policy

| Setting                       | Default               | What it does                                                                                                |
| ----------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------- |
| Validity                      | 365 days              | 1–3650. How long each issued certificate lasts                                                              |
| Renewal window                | 73 days               | 1–365. How early `/simplereenroll` starts working. A device asking sooner is told the date it may come back |
| Subject pattern               | blank                 | Regular expression the CSR's subject must match                                                             |
| SAN pattern                   | blank                 | Regular expression the CSR's subject alternative names must match                                           |
| Permitted extended key usages | Client authentication | The ceiling on what a CSR may ask for                                                                       |

Set the renewal window to match the client's renewal timer. For example, `auto-enroll 80` requires 73 days for a 365-day certificate.

### What the CSR must contain

SimpleSCEP reads the subject and SANs from the CSR.

- **Subject** and **SANs** are copied through, and are what the patterns match against.
- The **public key** must be RSA of at least 2048 bits, or EC on P-256 or P-384.
- The **CSR signature** must verify. This is the proof of possession.
- **Extended key usages** come from the CSR extension request. Permitted subsets are preserved; unpermitted usages are rejected. A request with no usages receives client authentication only.

`/csrattrs` advertises the CA's public-key and signature algorithms. An empty response returns `204` as required by §4.5.2.

## Renewal and revocation

Re-enrollment replaces the certificate for the existing identity.

EST has no revocation operation; RFC 7030 leaves revocation to the CA. Revoke from the Certificates page, which publishes to the CRL and answers OCSP immediately.

`/serverkeygen` and `/fullcmc` are **not implemented**. A client that probes them gets a `404` and falls back to `/simpleenroll`.

## Delete an endpoint

- The base URL stops responding immediately. Every device configured with it fails its next enrollment.
- Re-enrollment fails. Migrate devices before deletion.
- Every credential is deleted, so devices lose the ability to enroll _and_ to renew.
- No certificate is revoked. If they should stop working, revoke them _before_ deleting.

To stop new enrollments without deleting credentials, turn the endpoint off.

## Notes

- Administration requires an administrator role.
- Failures are a plain HTTP status and a sentence, as §4.2.3 asks for; EST has no problem-document format. A `401` carries `WWW-Authenticate: Basic`.
- A wrong username, a wrong password, and a revoked credential all produce the same `401`, and verification runs even for an unknown username, so neither the response nor its timing reveals which usernames exist.
- Enrollments appear in the audit log as **Certificate issued**, alongside SCEP and ACME issuance.
- The endpoint page's activity log records refusals as well as issuance, with the reason the device was given. Only refusals from a client that authenticated are recorded.
