PKI secrets engine - rotation primitives
Since Vault 1.11.0, Vault's PKI Secrets Engine supports multiple issuers in a single mount point. By using the certificate types below, rotation can be accomplished in various situations involving both root and intermediate CAs managed by Vault.
X.509 certificate fields
X.509 is a complex specification; modern implementations tend to refer to RFC 5280 for specific details. For validation of certificates, both RFC 5280 and the TLS validation RFC 6125 are important for understanding how to achieve rotation.
The following is a simplification of these standards for the purpose of this document.
Every X.509 certificate begins with an asymmetric key pair, using an algorithm like RSA or ECDSA. This key pair is used to create a Certificate Signing Request (CSR), which contains a set of fields the requester would like in the final certificate (but, it is up to the Certificate Authority (CA) to decide what fields to take from the CSR and which to override). The CSR also contains the public key of the pair, which is signed by the private key of the key pair to prove possession. Usually, the requester would ask for attributes in the Subject field of the CSR or in the Subject Alternative Name extension CSR to be respected in the final certificate. It is up to the CA if these values are trusted or not. When approved by the issuing authority (which may be backed by this asymmetric key itself in the case of a root self-signed certificate), the authority attaches the Subject of its certificate to the issued certificate in the Issuer field, assigns a unique serial number to the issued certificate, and signs the set of fields with its private key, thus creating the certificate.
There are some important restrictions here:
- One certificate can only have one Issuer, but this issuer is identified by the Subject on the issuing certificate and its public key.
- One key pair can be used for multiple certificates, but one certificate can only have one backing key material.
The following fields on the final certificate are relevant to rotation:
- The backing public
and private key material (Subject Public Key Info).
- Note that the private key is not included in the certificate but is uniquely determined by the public key material.
- The Subject of the certificate.
- This identifies the entity to which the certificate was issued. While the SAN values (in the Subject Alternative Name extension) is useful when validating TLS Server certificates against the negotiated hostname and URI, it isn't generally relevant for the purposes of validating intermediate certificate chains or in rotation.
- The Validity
period of this certificate.
- Notably, RFC 5280 does not place any requirements around the issued
certificate's validity period relative to the validity period of the
issuing certificate. However, it does state
that certificates ought to be revoked if their status cannot be maintained
up to their notAfter date. This is why Vault 1.11's
/pki/issuer/:issuer_ref
configuration endpoint maintains theleaf_not_after_behavior
per-issuer rather than per-role. - Additionally, some browsers will place ultimate trust in the certificates
in their trust stores, even when these certificates are expired.
- Note that this only applies to certificates in the trust store; validity periods will still be enforced for certificates not in the store (such as intermediates).
- Notably, RFC 5280 does not place any requirements around the issued
certificate's validity period relative to the validity period of the
issuing certificate. However, it does state
that certificates ought to be revoked if their status cannot be maintained
up to their notAfter date. This is why Vault 1.11's
- The Issuer and
signatureValue
of this certificate.
- In the issued certificate's Issuer field, the issuing certificate places its own Subject value. This allows the issuer to be identified later (without having to try signature validation against every known local certificate), when validating the presented certificate and chain.
- The signature over the entire certificate (by the issuer's private key) is then placed in the signatureValue field.
- The optional Authority Key Identifier
field.
- This field can contain either (or both) of two values:
- The hash of the issuer's public key. This extension is set and this value is filled in by Vault.
- The Issuer's Subject and Serial Number. This value is not set by Vault.
- The latter is a dangerous restriction for the purposes of rotation: it prevents cross-signing and reissuance as the new issuing certificates (while having the same backing key material) will have different serial numbers. See the Limitations of Primitives section below for more information on this restriction.
- This field can contain either (or both) of two values:
- The Serial Number
of this certificate.
- This field is unique to a specific issuer; when a certificate is reissued by its parent authority, it will always have a different serial number field.
- The CRL distribution
point field.
- This is a field detailing where a CRL is expected to exist for this certificate and under which CRL issuers (defaulting to the issuing certificate itself) the CRL is expected to be signed by. This is mostly informational and for server software like nginx, Vault's Cert Auth method, and Apache, CRLs are provided to the server, rather than having the server fetch CRLs for certificates automatically.
- Note that root certificates (in browsers trust stores) are generally not considered revocable. However, if an intermediate is revoked by serial, it will appear on its parent's CRL, and may prevent rotation from happening.
X.509 rotation primitives
Rotation (from an organizational standpoint) can only safely happen with certain intermediate X.509 certificates being issued. To distinguish the two types of certificates used to achieve rotation, this document notates them as primitives.
Rotation of an end-entity certificate is trivial from an X.509 trust chain
perspective; this process happens every day and should only depend on what is
in the trust store and not the end-entity certificate itself. In Vault, the
requester would hit the various issuance endpoints (/pki/issue/:name
or
/pki/sign/:name
-- or use the unsafe /pki/sign-verbatim
) and swap out the
old certificate with the new certificate and reload the configuration or
restart the service. Other parts of the organizations might use
ACME for certificate issuance
and rotation, especially if the service is public-facing (and thus needs to
be issued by a Public CA). Given it was signed by a trusted root, any devices
connecting to the service would not know the difference.
Rotation of intermediate certificates is almost as easy. Assuming a decent operational setup (wherein during end-entity issuance, the full certificate chain is updated in the service's configuration), this should be as easy as creating a new intermediate CA, signing it against the root CA, and then beginning issuance against the new intermediate certificate. In Vault, if the intermediate is generated in an existing mount path (or is moved into such), the requesting entity shouldn't care much. Under ACME, Let's Encrypt has successfully rotated intermediates to present a cross-signed chain (for older Android devices). Assuming the old intermediate's parent(s) are still valid and trusted, certificates issued under old intermediates should continue to validate.
The hard part of rotation--calling for the use of these primitives--is rotating root certificates. These live in every device's trust store and are hard to update from an organization-wide operational perspective. Unless the organization can swap out roots almost instantaneously and simultaneously (e.g., via an agent) with no missed devices, this process will likely span months.
To make this process lower risk, there are various primitive certificate types that use the above certificate fields. Key to their success is the following note:
Note: While certificates are added to the trust store, it is ultimately the associated key material that determines trust: two issuer certificates with the same subject but different public keys cannot validate the same leaf certificate; only if the keys are the same can this occur.
Cross-Signed primitive
This is the most common type of rotation primitive. A common CSR is signed by two CAs, resulting in two certificates. These certificates must have the same Subject (but may have different Issuers and will have different Serial Numbers) and the same backing key material, to allow certificates they sign to be trusted by either variant.
Note that, due to restrictions in how end-entity certificates are used and validated (services and validation libraries expect only one), cross-signing most typically only applies to intermediate.
Cross-Signed roots
Technically, cross-signing can occur between two roots, allowing trust bundles with either root to validate certs issued through the other. However, this process creates a certificate that is effectively an intermediate (as it is no longer self-signed) and usually must be served alongside the trust chain. Given this restriction, it's preferable to instead cross-sign the top-level intermediates under the root unless strictly necessary when the old root certificate has been used to directly issue leaf certificates.
Process flow
Here, a key pair was generated at some point in time. Two CSRs are created and sent to two different root authorities (Root A and Root B). These result in two separate certificates (potentially with different validity periods) with the same Subject and same backing key material.
Note that this cross-signing need not happen simultaneously; there could be a gap of several years between the first and second certificate. Additionally, there's no limit on the number of cross-signed "duplicate" (used loosely--with the same subject and key material) certificates: this could be cross-signed by many different root certificates if necessary and desired.
Certificate hierarchy
The above process results in two trust paths: either of root A or root B (or both) could exist in the client's trust stores and the leaf certificate would validate correctly. Because the same key material is used for both intermediate certificates (C and D), the issued leaf certificate's signature field would be the same regardless of which intermediate was contacted.
Cross-signing is thus a unifying primitive; two separate trust paths now join into a single one, by having leaf certificate's issuer field to point to two separate paths (via duplication of the certificate in the chain) and would be conditionally validated based on which root is present in the trust store.
This construct is documented and used in several places:
- https://letsencrypt.org/certificates/
- https://scotthelme.co.uk/cross-signing-alternate-trust-paths-how-they-work/
- https://security.stackexchange.com/questions/14043/what-is-the-use-of-cross-signing-certificates-in-x-509
Execution in Vault
To create a cross-signed certificate in Vault, use the /intermediate/cross-sign
endpoint. Here, when creating
a cross-signature to all cert B
to be validated by cert A
, provide the values
(key_ref
, all Subject parts, &c) for cert B
during intermediate generation.
Then sign this CSR (using the /issuer/:issuer_ref/sign-intermediate
endpoint) with cert A
's reference
and provide necessary values from cert B
(e.g., Subject parts). cert A
may
live outside Vault. Finally, import the cross-signed certificate into Vault
using the /issuers/import/cert
endpoint.
If this process succeeded, and both cert A
and cert B
and their key
material lives in Vault, the newly imported cross-signed certificate
will have a ca_chain
response field during read
containing cert A
, and cert B
's ca_chain
will contain the cross-signed
cert and its ca_chain
value.
Note: Regardless of issuer type, is important to provide all relevant parameters as they were originally; Vault does not infer e.g., the Subject name parameters from the existing issuer; it merely reuses the same key material.
Reissuance primitive
The second most common type of rotation primitive. In this scheme, the existing key material is used to generate a new certificate, usually at a much later point in time from the existing issuance.
While similar to the cross-signed primitive, this one differs in that usually the reissuance happens after the original certificate expires or is close to expiration and is reissued by the original root CA. In the event of a self-signed certificate (e.g., a root certificate), this parent certificate would be itself. In both cases, this changes the contents of the certificate (due to the new serial number) but allows all existing leaf signatures to still validate.
Unlike the cross-signed primitive, this primitive type can be used on all types of certificates (including leaves, intermediates, and roots).
Process flow
In this process flow, a single key pair is generated at some point in time and stored. The CSR (with same requested fields) is generated from this common key material and signed by the same issuer at multiple points in time, preserving all critical fields (Subject, Issuer, &c). While there is strictly no limit on the number of times a key can be reissued, at some point safety would dictate the key material should be rotated instead of being continually reissued.
Certificate hierarchy
Note that while this again results in two trust paths, depending on which intermediate certificate is presented and is still valid, only a root need be trusted. When a reissued certificate is a root certificate, the issuance link is simply self-loop. But, in this case, note that both certificates are (technically) valid issuers of each other. This means it should be possible to provide a reissued root certificate in the TLS certificate chain and have it chain back to an existing root certificate in a trust store.
This primitive type is thus an incrementing primitive; the life cycle of an existing key is extended into the future by issuing a new certificate with the same key material from the existing authority.
Execution in Vault
To create a reissued root certificate in Vault, use /issuers/generate/root/existing
endpoint. This allows the generation of a new
root certificate with the existing key material (via the key_ref
request parameter).
If this process succeeded, when reading the issuer
(via GET /issuer/:issuer_ref
), both issuers (old and reissued) will appear in
each others' ca_chain
response field (unless prevented so by a manual_chain
value).
To create a reissued intermediate certificate in Vault, this is a three step process:
- Use the
/issuers/generate/intermediate/existing
endpoint to generate a new CSR with the existing key material with thekey_ref
request parameter. - Sign this CSR via the same signing process under the same issuer. This step is specific to the parent CA, which may or may not be Vault.
- Finally, use the
/intermediate/set-signed
endpoint to import the signed certificate from step 2.
If the process to reissue an intermediate certificate succeeded, when
reading the issuer (via
GET /issuer/:issuer_ref
), both issuers (old and reissued) will have
the same ca_chain
response field, except for the first entry (unless
prevented so by a manual_chain
value).
Note: Regardless of issuer type, is important to provide all relevant parameters as they were originally; Vault does not infer e.g., the Subject name parameters from the existing issuer; it merely reuses the same key material.
Temporal primitives
We can use the above primitive types to rotate roots and intermediates to new keys and extend their lifetimes. This time-based rotation is what ultimately allows us to rotate root certificates.
There's two main variants of this: a forward primitive, wherein an old certificate is used to bless new key material, and a backwards primitive, wherein a new certificate is used to bless old key material. Both of these primitives are independently used by Let's Encrypt in the aforementioned chain of trust document:
- The link from DST Root CA X3 to ISRG Root X1 is an example of a forward primitive.
- The link from ISRG Root X1 to R3 (which was originally signed by DST Root CA X3) is an example of a backwards primitive.
For most organizations with a hierarchical structured CA setup, cross-signing all intermediates with both the new and old root CAs is sufficient for root rotation.
However, for organizations which have directly issued leaf certificates from a root, the old root will need to be reissued under the new root (with shorter duration) to allow these certificates to continue to validate. This combines both of the above primitives (cross-signing and reissuance) into a single backwards primitive step. In the future, these organizations should probably move to a more standard, hierarchical setup.
Limitations of primitives
The certificate's Authority Key Identifier extension field may contain either or both of the issuer's keyIdentifier (a hash of the public key) or both the issuer's Subject and Serial Number fields. Generating certificates with the latter enabled (luckily not possible in Vault, especially so since Vault uses strictly random serial numbers) prevents building a proper cross-signed chain without re-issuing the same serial number, which will not work with most browsers' trust stores and validation engines, due to caching of certificates used in successful validations. In the strictest sense, when using a cross-signing primitive (from a different CA), the intermediate could be reissued with the same serial number, assuming no previous certificate was issued by that CA with that serial. This does not work when using a reissuance primitive as these are technically the same authority and thus this authority must issue certificates with unique serial numbers.
Tutorial
Refer to the Build Your Own Certificate Authority (CA) guide for a step-by-step tutorial.
Have a look at the PKI Secrets Engine with Managed Keys for more about how to use externally managed keys with PKI.
API
The PKI secrets engine has a full HTTP API. Please see the PKI secrets engine API for more details.