Skip to main content
Version: v0.1.0-rc.1

📄 Machine config: ACME and EST

ACME (RFC 8555, http-01 only) and EST (RFC 7030) both work in this release. SCEP has its own page, Enrol devices with SCEP. WSTEP is not available.

The pki.acme and pki.est blocks of the machine config. Each enrolment protocol is off unless its block is present: a protocol is opened on purpose, never by forgetting to close it. Both issue from a leaf profile defined under pki.profiles.

A Root never serves ACME or EST

A Root keeps every enrolment protocol off. A config that switches pki.acme or pki.est on at a Root is refused by cryptosctl config apply, by the maintenance-mode install and by cryptosctl ceremony start --config, and nothing is stored. A block with enabled: false is accepted and never served. Serve ACME and EST from an intermediate or issuing node.

🔀 Switching a protocol on or off​

To switch a protocol on, add its block and run cryptosctl config apply. Changing a setting works the same way. To switch it off and keep its settings, set enabled: false in the block and apply again. To switch it back on, set enabled: true or remove the line: a block without enabled is on. Removing the whole block switches the protocol off and drops its settings.

pki:
acme:
enabled: false
base_url: https://ca.example.org/acme
profile: leaf-server

The node keeps a switched-off block's settings, and cryptosctl config get prints the block with enabled: false, so flipping enabled alone switches it back on. Nothing reads a switched-off block at boot, so changing only its settings doesn't need a reboot.

In the API's MachineConfig the blocks are Pki.acme and Pki.est, each with an enabled flag. enabled: false switches the protocol off and keeps the settings sent with it, an enabled: false block with no settings drops them, and an ApplyConfig that leaves a block out keeps what the node has, on or off.

A switch takes effect at the next reboot

config apply stores the change and prints requires_reboot=true. The listener starts, stops or picks up the new settings only when the node boots again, and the node stops issuing while it restarts. Plan the reboot for a maintenance window, then run cryptosctl reboot.

Until the reboot, cryptosctl status shows the stored state against what is running:

Protocols: ACME on (not running, reboot pending), EST off
Reboot: pending (the stored config changes take effect at the next boot)

The secrets are write-only, in a switched-off block too: hmac_key_base64 for ACME and password_sha256 for EST. cryptosctl config get prints them blank, next to their key_id or username. Leave a blank value as it is and config apply keeps the one the node stores for that identifier; set a value to replace it. A new key_id or username needs its value, or the apply is refused. Removing an entry revokes it.

🤖 ACME​

pki:
revocation_base_url: http://pki.example.org
profiles:
- name: leaf-server
key_alg: ECDSA-P384
validity_days: 90
key_usage: [digital_signature]
ext_key_usage: [server_auth]
acme:
base_url: https://ca.example.org/acme
profile: leaf-server
allowed_identifier_suffixes: [example.org]
external_account_keys:
- key_id: ops-team
hmac_key_base64: (at least 32 random bytes, base64url, no padding)
FieldTypeDefaultMeaning
base_urlstringnoneThe base URL clients dial, for example https://ca.example.org/acme. Required. Every URL handed to a client is built from it, and every signed request's url is checked against it, so it must be what clients reach, not what the node binds.
http_portinteger0 (port 8555)The TCP port the ACME listener binds. It serves plain HTTP and is meant to sit behind a TLS terminator.
profilestringnoneThe leaf profile ACME issues under. Required, and it must not be a CA profile.
terms_of_servicestringemptyWhen set, advertised in the directory, and new accounts must agree to it.
websitestringemptyAdvertised in the directory metadata.
allow_anonymous_accountsbooleanfalseDrops the External Account Binding requirement, so anyone who can answer an http-01 challenge can register and order.
external_account_keyslistemptyThe External Account Binding keys: key_id, the identifier the client sends, and hmac_key_base64, the shared secret. At least one is required unless allow_anonymous_accounts is true.
allowed_identifier_suffixeslistemptyWhen set, a name must equal one of these or be a subdomain of one. Empty places no name restriction.
order_ttl_hoursinteger0 (168 hours)How long an order and its authorizations stay valid. 0 means seven days.
RuleError
Bad base URLconfig: pki.acme.base_url: must be an http(s) URL
No profileconfig: pki.acme.profile: required when pki.acme is set
Profile not foundconfig: pki.acme.profile: no profile named "<name>" in pki.profiles
CA profileconfig: pki.acme.profile: "<name>" is a CA profile; ACME issues end-entity certificates only
No keys and anonymous accounts not allowedconfig: pki.acme.external_account_keys: at least one key is required unless pki.acme.allow_anonymous_accounts is true
Key with no ID, or a repeated IDconfig: pki.acme.external_account_keys[<i>].key_id: required or ... key_id: "<id>" is duplicated
Secret not base64url without paddingconfig: pki.acme.external_account_keys[<i>].hmac_key_base64: must be base64url without padding
Secret shorter than 32 bytesconfig: pki.acme.external_account_keys[<i>].hmac_key_base64: must decode to at least 32 bytes, got <n>
Blank secret for a key_id the node doesn't haveconfig: pki.acme.external_account_keys[<i>].hmac_key_base64: empty, and the node has no stored key for key_id "<id>" to keep; send the secret for a new key
Set on a Rootconfig: pki.acme: must not be set on a root node; a root serves no enrolment protocol, so serve ACME from an intermediate or issuing node

One way to make a 32-byte key:

openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n'; echo
Expected output

One line of 43 characters, using letters, digits, - and _, with no = at the end. Hand the same value and its key_id to whoever runs the ACME client.

Anonymous accounts open the CA to the whole network

With allow_anonymous_accounts: true and no allowed_identifier_suffixes, any host that can answer an http-01 challenge for a name can get a certificate for it. Keep the account binding on, or at least restrict the names.

📮 EST​

pki:
est:
hostnames: [est.example.org, 192.0.2.20]
profile: leaf-server
allowed_identifier_suffixes: [example.org]
enroll_credentials:
- username: switch-fleet
password_sha256: (64 lowercase hex characters)
FieldTypeDefaultMeaning
hostnameslistnoneThe names and IP addresses clients reach the endpoint on. Required. The listener terminates TLS itself, with a server certificate the node mints for these names from its own CA.
http_portinteger0 (port 8443)The TCP port the EST listener binds.
profilestringnoneThe leaf profile EST issues under. Required, and it must not be a CA profile. Include client_auth in its ext_key_usage if clients will renew with simplereenroll.
labelstringemptyThe optional path segment between /.well-known/est and the operation, so one host can offer several CAs. Empty serves the unlabelled paths.
realmstringemptyThe HTTP Basic realm offered when simpleenroll asks for credentials.
allowed_identifier_suffixeslistemptyRestricts the names simpleenroll will issue for. It doesn't restrict simplereenroll, whose names are pinned to the certificate the client already holds.
allow_any_identifierbooleanfalseDrops that restriction. Must be set by name.
enroll_credentialslistemptyHTTP Basic credentials that authorize simpleenroll: username and password_sha256, the lowercase hex SHA-256 of the password. Empty keeps simpleenroll closed and offers certificate-authenticated renewal only.
RuleError
No hostnamesconfig: pki.est.hostnames: at least one hostname is required; the node mints its TLS server certificate for them
An empty hostnameconfig: pki.est.hostnames[<i>]: must not be empty
label contains /config: pki.est.label: must be a single path segment, got "<value>"
No profile, profile not found, or a CA profileconfig: pki.est.profile: required when pki.est is set, ... no profile named "<name>" in pki.profiles or ... "<name>" is a CA profile; EST issues end-entity certificates only
Credential with no username, or a repeated oneconfig: pki.est.enroll_credentials[<i>].username: required or ... username: "<name>" is duplicated
Bad password digestconfig: pki.est.enroll_credentials[<i>].password_sha256: must be 64 lowercase hex characters (a SHA-256 digest)
Credentials without a name restrictionconfig: pki.est.allowed_identifier_suffixes: required when pki.est.enroll_credentials is set, unless pki.est.allow_any_identifier is true; simpleenroll has no proof of control
Blank digest for a username the node doesn't haveconfig: pki.est.enroll_credentials[<i>].password_sha256: empty, and the node has no stored credential for username "<name>" to keep; send the digest for a new credential
Set on a Rootconfig: pki.est: must not be set on a root node; a root serves no enrolment protocol, so serve EST from an intermediate or issuing node
Only a generated password is safe here

The node stores only a SHA-256 of each password, which protects a long random value but not a chosen word: a plain digest of a dictionary word falls in seconds. Generate the password (for example with the key command above) and give its digest to the node.

To get the digest of a password held in a file with no trailing newline:

openssl dgst -sha256 -r password.txt | cut -d' ' -f1
Expected output

64 lowercase hex characters. Put them in password_sha256. A trailing newline in the file changes the digest, so check the file holds only the password.