docs: add ISSUANCE-CYCLE.md (#7444)
Also update the CA and RA doccomments to link to it and describe the roles of key functions a little better. Remove outdated reference to generating OCSP at issuance time.
This commit is contained in:
parent
a9c2fa3f69
commit
b7553f1290
24
ca/ca.go
24
ca/ca.go
|
@ -291,6 +291,16 @@ var ocspStatusToCode = map[string]int{
|
|||
"unknown": ocsp.Unknown,
|
||||
}
|
||||
|
||||
// IssuePrecertificate is the first step in the [issuance cycle]. It allocates and stores a serial number,
|
||||
// selects a certificate profile, generates and stores a linting certificate, sets the serial's status to
|
||||
// "wait", signs and stores a precertificate, updates the serial's status to "good", then returns the
|
||||
// precertificate.
|
||||
//
|
||||
// Subsequent final issuance based on this precertificate must happen at most once, and must use the same
|
||||
// certificate profile. The certificate profile is identified by a hash to ensure an exact match even if
|
||||
// the configuration for a specific profile _name_ changes.
|
||||
//
|
||||
// [issuance cycle]: https://github.com/letsencrypt/boulder/blob/main/docs/ISSUANCE-CYCLE.md
|
||||
func (ca *certificateAuthorityImpl) IssuePrecertificate(ctx context.Context, issueReq *capb.IssueCertificateRequest) (*capb.IssuePrecertificateResponse, error) {
|
||||
// issueReq.orderID may be zero, for ACMEv1 requests.
|
||||
// issueReq.CertProfileName may be empty and will be populated in
|
||||
|
@ -333,13 +343,13 @@ func (ca *certificateAuthorityImpl) IssuePrecertificate(ctx context.Context, iss
|
|||
}, nil
|
||||
}
|
||||
|
||||
// IssueCertificateForPrecertificate takes a precertificate and a set
|
||||
// of SCTs for that precertificate and uses the signer to create and
|
||||
// sign a certificate from them. The poison extension is removed and a
|
||||
// IssueCertificateForPrecertificate final step in the [issuance cycle].
|
||||
//
|
||||
// Given a precertificate and a set of SCTs for that precertificate, it generates
|
||||
// a linting final certificate, then signs a final certificate using a real issuer.
|
||||
// The poison extension is removed from the precertificate and a
|
||||
// SCT list extension is inserted in its place. Except for this and the
|
||||
// signature the certificate exactly matches the precertificate. After
|
||||
// the certificate is signed a OCSP response is generated and the
|
||||
// response and certificate are stored in the database.
|
||||
// signature the final certificate exactly matches the precertificate.
|
||||
//
|
||||
// It's critical not to sign two different final certificates for the same
|
||||
// precertificate. This can happen, for instance, if the caller provides a
|
||||
|
@ -355,6 +365,8 @@ func (ca *certificateAuthorityImpl) IssuePrecertificate(ctx context.Context, iss
|
|||
// final certificate, but this is just a belt-and-suspenders measure, since
|
||||
// there could be race conditions where two goroutines are issuing for the same
|
||||
// serial number at the same time.
|
||||
//
|
||||
// [issuance cycle]: https://github.com/letsencrypt/boulder/blob/main/docs/ISSUANCE-CYCLE.md
|
||||
func (ca *certificateAuthorityImpl) IssueCertificateForPrecertificate(ctx context.Context, req *capb.IssueCertificateForPrecertificateRequest) (*corepb.Certificate, error) {
|
||||
// issueReq.orderID may be zero, for ACMEv1 requests.
|
||||
if core.IsAnyNilOrZero(req, req.DER, req.SCTs, req.RegistrationID, req.CertProfileHash) {
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# The Issuance Cycle
|
||||
|
||||
What happens during an ACME finalize request?
|
||||
|
||||
At a high level:
|
||||
|
||||
1. Check that all authorizations are good.
|
||||
2. Recheck CAA for hostnames that need it.
|
||||
3. Allocate and store a serial number.
|
||||
4. Select a certificate profile.
|
||||
5. Generate and store linting certificate, set status to "wait" (precommit).
|
||||
6. Sign, log (and don't store) precertificate, set status to "good".
|
||||
7. Submit precertificate to CT.
|
||||
8. Generate linting final certificate. Not logged or stored.
|
||||
9. Sign, log, and store final certificate.
|
||||
|
||||
Revocation can happen at any time after (5), whether or not step (6) was successful. We do things this way so that even in the event of a power failure or error storing data, we have a record of what we planned to sign (the tbsCertificate bytes of the linting certificate).
|
||||
|
||||
Note that to avoid needing a migration, we chose to store the linting certificate from (5)in the "precertificates" table, which is now a bit of a misnomer.
|
||||
|
||||
# OCSP Status state machine:
|
||||
|
||||
wait -> good -> revoked
|
||||
\
|
||||
-> revoked
|
||||
|
||||
Serial numbers with a "wait" status recorded have not been submitted to CT,
|
||||
because issuing the precertificate is a prerequisite to setting the status to
|
||||
"good". And because they haven't been submitted to CT, they also haven't been
|
||||
turned into a final certificate, nor have they been returned to a user.
|
||||
|
||||
OCSP requests for serial numbers in "wait" status will return 500, but we expect
|
||||
not to serve any 500s in practice because these serial numbers never wind up in
|
||||
users' hands. Serial numbers in "wait" status are not added to CRLs.
|
||||
|
||||
Note that "serial numbers never wind up in users' hands" does not relieve us of
|
||||
any compliance duties. Our duties start from the moment of signing a
|
||||
precertificate with trusted key material.
|
||||
|
||||
Since serial numbers in "wait" status _may_ have had a precertificate signed,
|
||||
We need the ability to set revocation status for them. For instance if the public key
|
||||
we planned to sign for turns out to be weak or compromised, we would want to serve
|
||||
a revoked status for that serial. However since they also _may not_ have had a
|
||||
Precertificate signed, we also can't serve an OCSP "good" status. That's why we
|
||||
serve 500. A 500 is appropriate because the only way a serial number can have "wait"
|
||||
status for any significant amount of time is if there was an internal error of some
|
||||
sort: an error during or before signing, or an error storing a record of the
|
||||
signing success in the database.
|
||||
|
||||
For clarity, "wait" is not an RFC 6960 status, but is an internal placeholder
|
||||
value specific to Boulder.
|
11
ra/ra.go
11
ra/ra.go
|
@ -1286,8 +1286,10 @@ type certProfileID struct {
|
|||
hash []byte
|
||||
}
|
||||
|
||||
// issueCertificateInner handles the heavy lifting aspects of certificate
|
||||
// issuance.
|
||||
// issueCertificateInner is part of the [issuance cycle].
|
||||
//
|
||||
// It gets a precertificate from the CA, submits it to CT logs to get SCTs,
|
||||
// then sends the precertificate and the SCTs to the CA to get a final certificate.
|
||||
//
|
||||
// This function is responsible for ensuring that we never try to issue a final
|
||||
// certificate twice for the same precertificate, because that has the potential
|
||||
|
@ -1299,6 +1301,8 @@ type certProfileID struct {
|
|||
// never have final certificates issued for them (because there is a possibility
|
||||
// that the certificate was actually issued but there was an error returning
|
||||
// it).
|
||||
//
|
||||
// [issuance cycle]: https://github.com/letsencrypt/boulder/blob/main/docs/ISSUANCE-CYCLE.md
|
||||
func (ra *RegistrationAuthorityImpl) issueCertificateInner(
|
||||
ctx context.Context,
|
||||
csr *x509.CertificateRequest,
|
||||
|
@ -1329,6 +1333,9 @@ func (ra *RegistrationAuthorityImpl) issueCertificateInner(
|
|||
OrderID: int64(oID),
|
||||
CertProfileName: profileName,
|
||||
}
|
||||
// Once we get a precert from IssuePrecertificate, we must attempt issuing
|
||||
// a final certificate at most once. We achieve that by bailing on any error
|
||||
// between here and IssueCertificateForPrecertificate.
|
||||
precert, err := ra.CA.IssuePrecertificate(ctx, issueReq)
|
||||
if err != nil {
|
||||
return nil, nil, wrapError(err, "issuing precertificate")
|
||||
|
|
Loading…
Reference in New Issue