Skip to content

Conversation

aarongable
Copy link
Contributor

@aarongable aarongable commented Oct 2, 2025

The issuePrecertificate, issuePrecertificateInner, and issueCertificateForPrecertificate helpers were originally necessary because the RA made two separate gRPC calls to the CA, before and after acquiring SCTs. Now, the CA handles acquiring SCTs itself, and there's only one gRPC entrypoint: ca.IssueCertficate. This means that the layers of abstraction represented by those old helpers are no longer necessary, and we can significantly simplify the CA's core logic.

Inline the contents of issuePrecertificate, issuePrecertificateInner, and issueCertificateForPrecertificate directly into IssueCertificate. Remove the sa.GetCertificate check, because it is now impossible for the final-certificate issuance code to have been reached except by progressing through the precert issuance code. Remove the need to derive an issuer and a profile from the precert, because we are still holding onto the exact same issuer and profile that were used to issue the precert.

Delete the unit tests which were only testing the underlying issuePrecertificate and issueCertificateForPrecertificate helpers. As of #8422, those tests were redundant. Move some helpers which were shared by the old and new tests to new homes closer to their now-sole callers.

Part of #8390


Warning

Do not merge before #8422

Copy link
Contributor

@jsha jsha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very nice improvement!

Comment on lines -64 to -67
Result struct {
Precertificate string `json:",omitempty"`
Certificate string `json:",omitempty"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change the structure of the log event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this Result sub-struct, the only two ways to actually log Precertificate or a Certificate are:

ca.log.AuditObject(issuanceEvent{
  Requester: 1,
  Result: struct{Precertificate string, Certificate string}{Certificate: cert}
})

or

event := issuanceEvent{
  Requester: 1,
  ...
}
event.Result.Certificate = cert
ca.log.AuditObject(event)

The first is clearly ugly and confusing, and far too much boilerplate. The second creates local event variables that shouldn't need to exist, and assigns into a field of an already-created struct, which is a pattern we try to avoid.

Historically we've been doing the latter because we've been modifying the issuanceEvent that was logged prior to issuance in order to log it again after issuance. I really didn't like that, and wanted to inline our audit event logging.

The most obvious alternative change would be

type issuanceEventResult struct {
  Precertificate string
  Certificate string
}
type issuanceEvent struct {
  Requester int64
  ...
  Result issuanceEventResult
}

but I didn't see any particular reason to keep the Result sub-struct around either, so I went with the simpler structure.

ca/ca.go Outdated
Comment on lines 615 to 625
// lintCertMatchesPrecert verifies that the contents of the linting final cert
// exactly match the contents of the precert, with the exception of the poison
// extension, SCT extension, and signature. It also ignores the AKID, since the
// linting issuer has a different key and Go auto-generates the AKID extension
// from the issuer itself. We use the linting final cert so this function can
// run before we issue the actual final cert: if we've changed issuers, swapped
// profiles, or there's a bug in RequestFromPrecertificate, this function is
// positioned to catch it.
func lintCertMatchesPrecert(precertDER []byte, lintCertDER []byte) error {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this check is already performed inside issuer.Prepare. If the issuance request has a precert, Prepare generates a linting certificate, then checks precert.Correspond between the precert and the linting certificate:

boulder/issuance/cert.go

Lines 398 to 403 in 30197ca

if len(req.precertDER) > 0 {
err = precert.Correspond(req.precertDER, lintCertBytes)
if err != nil {
return nil, nil, fmt.Errorf("precert does not correspond to linted final cert: %w", err)
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I KNEW I'd seen this code somewhere before. I thought it was in the certificate-transparency-go library and couldn't find it there. I'd completely forgotten that you'd written it for us in //precert/corr.go.

Thanks for the pointer, I'll remove this stub.

Base automatically changed from issue-certificate-tests to main October 6, 2025 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants