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

📄 Machine config: pki

The pki section of the machine config: the CA's key, name and lifetime, the parent it trusts, where it publishes revocation data, and the certificate profiles it issues from. ACME and EST have their own page.

🔑 The CA key, subject and lifetime​

FieldTypeDefaultMeaning
pki.root_key_algstringnoneThe algorithm of this node's own CA key: ECDSA-P384, RSA-3072 or RSA-4096. Required on every role, despite the name.
pki.root_subject.common_namestringnoneThe CA's CN. Required.
pki.root_subject.organizationstringemptyO.
pki.root_subject.countrystringemptyC.
pki.root_subject.provincestringemptyST (state or province).
pki.root_subject.localitystringemptyL (city or locality).
pki.root_validity_yearsintegernoneHow long a Root's self-signed certificate lasts, 1 to 30 years. Required on a Root, unused on an intermediate or issuing node.
pki.path_len_constraintinteger0Accepted and checked (0 to 5), but not applied to any certificate in this release. A Root carries no path length limit; depth below it is set by the signing profile's basic_constraints.path_len.

Empty subject fields are left out of the certificate. On a Root, root_subject becomes both the subject and the issuer of the self-signed certificate; see Root certificate profile. On an intermediate or issuing node, it's the subject of the CSR the node sends its parent.

The key algorithm decides more than the key. A certificate's signature is made with the issuer's key, so this value also sets the signature algorithm on every certificate the node signs: ecdsa-with-SHA384 for ECDSA-P384, sha384WithRSAEncryption for either RSA size. RSA is there for platform CAs, such as VMware VMCA, that accept only RSA signatures anywhere in their chain. RSA-2048 is not offered: the node won't certify an RSA key smaller than 3072 bits, its own included.

RuleError
Unknown key algorithmconfig: pki.root_key_alg: must be one of ECDSA-P384, RSA-3072, RSA-4096, got "<value>"
No common nameconfig: pki.root_subject.common_name: required
Root lifetime out of rangeconfig: pki.root_validity_years: must be in [1, 30], got <n>
Path length out of rangeconfig: pki.path_len_constraint: must be in [0, 5], got <n>
An RSA key in the TPM needs a TPM that has that size

With state_key.mode: tpm, an RSA CA key is created and held in the TPM like the ECDSA one. The TPM 2.0 spec only requires RSA-2048, and many TPMs stop there. If the TPM doesn't implement the size you set, key creation stops with FailedPrecondition and an error containing tpm: key algorithm not supported by this TPM: RSA-3072 (or RSA-4096), and no key is made. The node never falls back to a smaller size or to a software key. Check the TPM's datasheet first, or use nodeid or kms mode, where the CA key is a software key on the encrypted state partition. See state_key.

🔗 parent​

A subordinate pins the CA that signs it, so it can check the chain it is handed. Required on an intermediate or issuing node, forbidden on a Root.

FieldTypeDefaultMeaning
pki.parent.ca_cert_pemstringemptyThe parent CA's certificate, as one PEM CERTIFICATE block.
pki.parent.ca_cert_sha256stringemptyThe parent certificate's SHA-256 fingerprint, as 64 hex characters.

Set exactly one of the two.

RuleError
Set on a Rootconfig: pki.parent: must not be set on a root node
Missing on a subordinateconfig: pki.parent: required on an intermediate/issuing node
Both or neither setconfig: pki.parent: exactly one of ca_cert_pem or ca_cert_sha256 is required
Fingerprint not 64 characters, or not hexconfig: pki.parent.ca_cert_sha256: must be 64 hex characters, got <n> or config: pki.parent.ca_cert_sha256: not hex: <reason>
Bad PEMconfig: pki.parent.ca_cert_pem: no PEM block found, ... PEM type "<type>", want CERTIFICATE, ... must contain exactly one PEM block or ... parse: <reason>

📡 Revocation​

FieldTypeDefaultMeaning
pki.revocation_base_urlstringemptyThe base URL where this node publishes its CRL, OCSP responder and CA certificate. Empty turns revocation publishing off.
pki.allow_unverified_revocation_urlbooleanfalseLets the node issue even when the revocation preflight fails. For an isolated lab only.
pki.crl_next_update_hoursinteger0 (168 hours)The CRL's validity window: nextUpdate is thisUpdate plus this many hours. 0 means one week.
pki.revocation_http_portinteger0 (port 80)The TCP port of the plain-HTTP revocation listener. 0 means 80.

When revocation_base_url is set, every certificate the node issues carries three pointers built from it:

ExtensionURL
CRL Distribution Points<base>/crl
Authority Information Access, OCSP<base>/ocsp
Authority Information Access, CA Issuers<base>/ca.cer

The node serves all three paths over anonymous HTTP on revocation_http_port. Before it issues, it runs a revocation preflight: the base URL's host must resolve and /crl, /ocsp and /ca.cer must answer. While the preflight fails, issuance is refused with node: revocation preflight failing for configured revocation_base_url; issuance blocked (set allow_unverified_revocation_url to override). cryptosctl status reports the preflight result.

RuleError
Not an http or https URL with a hostconfig: pki.revocation_base_url: must be an http(s) URL
allow_unverified_revocation_url can't be undone for issued certificates

With this set, the node stamps CRL and OCSP pointers that may never resolve, and every certificate issued meanwhile keeps them for life. On a Root, every subordinate it signs inherits the pointer. cryptosctl config apply stops and asks you to type the Root CA's common name exactly (or yes on an intermediate or issuing node) before it sends such a config; --yes skips the prompt. Fix DNS and reachability instead wherever you can.

🕰️ Clock gate​

FieldTypeDefaultMeaning
pki.allow_unsynced_clockbooleanfalseLets the node sign certificates while its clock hasn't synced with its time source this boot. For an isolated lab only.

When the node has a time source (network.ntp_servers, or NTP servers from its DHCP lease) and its clock hasn't synced yet this boot, it refuses subordinate CA signing and every leaf path, ACME and EST included, with node: the clock has not synced with its configured time source yet; signing is refused (set pki.allow_unsynced_clock to override). The gate opens at the first good sync and stays open for the rest of the boot. A node with no time source is never gated, and CRL and OCSP are never gated. See Keep the clock in sync.

The node reads this field on every signing request, so it takes effect without a reboot.

allow_unsynced_clock stamps dates you can't trust

A node whose time source never answered may be far off, and every certificate it signs with this set carries dates from that clock for its whole life: not yet valid, expiring early, or valid long after it should have expired. On a Root that includes every subordinate it signs. Each one is logged as signed on an unsynced clock, but the certificate can't be fixed afterwards. Leave it off and fix the time source instead.

🌳 Leaf issuance from a Root​

FieldTypeDefaultMeaning
pki.root_leaf_issuancestringemptySet to exactly acknowledged-irreversible to let a Root issue end-entity certificates directly. Any other value leaves it refusing.

Without it, a Root refuses leaf issuance with node: a ROOT node refuses to issue leaf certificates without the irreversible acknowledgement. The field takes effect without a reboot.

A Root that signs a leaf can't take it back

Good practice issues leaves from an issuing CA and keeps the Root for signing CAs. Once a Root has signed a leaf, it can no longer claim to have signed only CAs, and removing the setting later doesn't change that. Issue leaves from a subordinate unless you have a reason not to.

🧩 Certificate profiles​

pki.profiles is a list of named templates. cryptosctl ca issue-leaf, ca sign-subordinate, ACME and EST all issue from a profile, which sets everything about the certificate except its subject and public key; those come from the CSR. A change to profiles takes effect without a reboot.

pki:
profiles:
- name: leaf-server
key_alg: ECDSA-P384
validity_days: 90
key_usage: [digital_signature]
ext_key_usage: [server_auth]
sans:
dns: [web.example.org]
- name: platform-sub-ca
key_alg: RSA-3072
validity_days: 1825
validity_policy: cap
basic_constraints:
is_ca: true
path_len: 0
key_usage: [digital_signature, cert_sign, crl_sign]
FieldTypeDefaultMeaning
namestringnoneRequired and unique. Callers pick the profile by name.
key_algstringnoneRequired: ECDSA-P384, RSA-3072 or RSA-4096. Governs keys this node generates, not the subject keys it certifies.
subjectobjectemptycommon_name, organization, country, province, locality. Used when the node builds a CSR itself. An issued certificate takes its subject from the CSR.
validity_daysintegernoneRequired, greater than 0. The requested lifetime, counted from signing.
validity_policystringcapWhat happens when validity_days would run past the issuing CA's own notAfter: cap ends the certificate with its issuer, reject refuses to issue.
basic_constraints.is_cabooleanfalseMarks a CA profile, for signing subordinates.
basic_constraints.path_lenintegerunsetCA profiles only. 0 means no CA may be issued below; unset means no limit. It is clamped to what the signing CA's own certificate allows.
key_usagelistemptyAny of digital_signature, key_encipherment, key_agreement, cert_sign, crl_sign.
ext_key_usagelistemptyserver_auth, client_auth, or dotted OIDs such as 1.3.6.1.5.2.3.5 (Kerberos KDC).
sansobjectemptydns, ip, email, uri, krb5_principal, upn. Stamped on every certificate the profile issues; SANs in the CSR are ignored. ACME and EST replace them with the names the client proved.
allow_request_sansbooleanfalseLeaf profiles only. Lets cryptosctl ca issue-leaf --dns replace the profile's SANs.
extra_extensionslistemptyRaw extensions for anything not modelled above: oid, critical, and value, the DER bytes of the extension value.

A few formats worth knowing:

  • krb5_principal is name[/instance]@REALM, for example krbtgt/AD.EXAMPLE.ORG@AD.EXAMPLE.ORG, stamped as a KRB5PrincipalName otherName. Each part must be non-empty printable ASCII without /, @ or \.
  • upn is prefix@suffix, for example user@ad.example.org, stamped as a Microsoft UPN otherName (1.3.6.1.4.1.311.20.2.3).
  • Dotted OIDs in ext_key_usage need at least two arcs, digits only, no leading zeros, a first arc of 0, 1 or 2, and a second arc of at most 39 under 0 or 1. An OID the Go standard library knows by name counts as that name, so listing server_auth and 1.3.6.1.5.5.7.3.1 together is a duplicate.
  • extra_extensions[].value is a YAML list of byte values, for example value: [5, 0] for a DER NULL. A base64 string is rejected.
RuleError
No name, or a repeated nameconfig: pki.profiles[<i>].name: required or config: pki.profiles[<i>].name: duplicate name "<name>"
Unknown key algorithmconfig: pki.profiles[<i>].key_alg: must be one of ECDSA-P384, RSA-3072, RSA-4096, got "<value>"
validity_days is 0config: pki.profiles[<i>].validity_days: must be greater than 0
Unknown key usageconfig: pki.profiles[<i>].key_usage: ca: ParseKeyUsage: unknown key usage "<value>"
Unknown, malformed or repeated extended key usageconfig: pki.profiles[<i>].ext_key_usage: ca: ParseExtKeyUsage: <reason>
Bad extension OIDconfig: pki.profiles[<i>].extra_extensions[<j>].oid: invalid OID "<oid>": <reason>
Unknown validity policyconfig: pki.profiles[<i>].validity_policy: must be "cap" or "reject", got "<value>"
allow_request_sans on a CA profileconfig: pki.profiles[<i>].allow_request_sans: applies to leaf profiles only, not a CA profile
Bad Kerberos principal or UPNconfig: pki.profiles[<i>].sans.krb5_principal[<j>]: invalid Kerberos principal "<value>": <reason> or ... sans.upn[<j>]: invalid UPN "<value>": <reason>
A raw SAN extension next to otherName SANsconfig: pki.profiles[<i>].extra_extensions[<j>]: a raw subjectAltName extension cannot be combined with sans.krb5_principal or sans.upn

Validity and the issuer's lifetime​

A certificate can't outlive the CA that signed it, so the node caps every certificate at its own notAfter. With cap, cryptosctl prints a warning such as WARNING: requested validity ends 2046-09-22; capped to issuer notAfter 2041-09-21 and issues the shorter certificate. With reject, the call fails with FailedPrecondition before the CA key is loaded. ACME and EST issue the capped certificate without a warning, because those protocols have no channel for one.

Profile subjects lose ST and L over the API

cryptosctl config apply and the FleetOS Manager carry a profile's subject.common_name, organization and country only. A province or locality in a profile subject is dropped on apply. The CA's own root_subject isn't affected.