CertPool and Certificate.Verify currently only support roots constrained using X509 semantics. For most cases this is fine, but with the introduction of the golang.org/x/crypto/x509roots/fallback package (#43958), which plans to use the Mozilla NSS root list, there is a need to implement additional constraints.
The Mozilla NSS list encodes additional trust constraints for specific root certificates, such as only trusting certificates issued before a specific date (e.g. what is happening with TrustCor, or what previous happened with Symantec), etc. In order to support these constraints we need a new API which allows for attaching additional checks to certificates which chain to particular roots.
The API suggested below, CertPool.AddCertWithConstraint, would allow attaching a callback to a root, which Certificate.Verify would call during chain building. There would be no other externally visible API changes, as the root->constraint mapping would be a private structure in the CertPool, accessible only by Certificate.Verify.
As always the name is up for debate. The meaning of 'Constraint' in X509 is perhaps overloaded, AddCertWithAdditionalChecks could also work, but is possibly a little verbose. Another question is if the function should accept a single callback, or a list of callbacks (i.e. should the caller compose all of their constraints into a single callback, or should we allow passing multiple callbacks that we check ourselves serially).
// AddCertWithConstraint adds a certificate to the pool with the additional constraint.
// When Certificate.Verify builds a chain which is rooted by cert, it will additionally
// pass the chain to constraint to determine its validity. If constraint returns a non-nil
// error, the chain will be discarded.
func (*CertPool) AddCertWithConstraint(cert *Certificate, constraint func(*Certificate) error)
This allows adding constraints to individual certs as they're added to the pool, but what if you're starting with the system pool and want to layer additional checks on top of existing certs/system verifier checks?
I don't think we want to support adding additional arbitrary constraints to platform verifiers. Platforms have their own ways of applying constraints to trust stores (i.e. both Windows and macOS allow you to constrain certificates in complicated ways), and I think we'd prefer that the platform verifier, that can more properly understand those constraints, apply them themselves.
If users really want to apply their own constraints purely in code, that can be done by wrapping calls to Verify with the logic that excludes specific chains.
For platforms that don't allow applying additional constraints (namely Linux), the root pool can be loaded manually, and constraints can be added at that point (we've considered exposing the logic used to load Linux trust stores, which perhaps we should do to make this simpler).