x509roots: support constrained roots

Adds support for roots with the distrust-after bit set. The constraint
function construction is a little funky, but I couldn't think of an
obvious better way to do it.

Fixes golang/go#70777
Fixes golang/go#70623

Change-Id: I780f866416b626360eaee9368185768da7bc75ef
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/652996
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Roland Shoemaker
2025-03-13 16:44:06 -07:00
parent b369b723c8
commit 376eb14006
3 changed files with 1241 additions and 467 deletions
+1155 -439
View File
File diff suppressed because it is too large Load Diff
+13 -2
View File
@@ -22,8 +22,19 @@ import "crypto/x509"
func init() {
p := x509.NewCertPool()
for _, c := range bundle {
p.AddCert(c)
for _, c := range parsedCertificates {
if len(c.constraints) == 0 {
p.AddCert(c.cert)
} else {
p.AddCertWithConstraint(c.cert, func(chain []*x509.Certificate) error {
for _, constraint := range c.constraints {
if err := constraint(chain); err != nil {
return err
}
}
return nil
})
}
}
x509.SetFallbackRoots(p)
}
+73 -26
View File
@@ -21,6 +21,7 @@ import (
"net/http"
"os"
"sort"
"time"
"golang.org/x/crypto/x509roots/nss"
)
@@ -29,36 +30,68 @@ const tmpl = `// Code generated by gen_fallback_bundle.go; DO NOT EDIT.
package fallback
import "crypto/x509"
import "encoding/pem"
import (
"crypto/x509"
"encoding/pem"
"fmt"
"time"
)
func mustParse(b []byte) []*x509.Certificate {
var roots []*x509.Certificate
for len(b) > 0 {
var block *pem.Block
block, b = pem.Decode(b)
type unparsedCertificate struct {
cn string
sha256Hash string
pem string
// possible constraints
distrustAfter string
}
type parsedCertificate struct {
cert *x509.Certificate
constraints []func([]*x509.Certificate) error
}
func mustParse(unparsedCerts []unparsedCertificate) []parsedCertificate {
var b []parsedCertificate
for _, unparsed := range unparsedCerts {
block, rest := pem.Decode([]byte(unparsed.pem))
if block == nil {
break
panic(fmt.Sprintf("unexpected nil PEM block for %q", unparsed.cn))
}
if len(rest) != 0 {
panic(fmt.Sprintf("unexpected trailing data in PEM for %q", unparsed.cn))
}
if block.Type != "CERTIFICATE" {
panic("unexpected PEM block type: " + block.Type)
panic(fmt.Sprintf("unexpected PEM block type for %q: %s", unparsed.cn, block.Type))
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
roots = append(roots, cert)
parsed := parsedCertificate{cert: cert}
// parse possible constraints, this should check all fields of unparsedCertificate.
if unparsed.distrustAfter != "" {
distrustAfter, err := time.Parse(time.RFC3339, unparsed.distrustAfter)
if err != nil {
panic(fmt.Sprintf("failed to parse distrustAfter %q: %s", unparsed.distrustAfter, err))
}
parsed.constraints = append(parsed.constraints, func(chain []*x509.Certificate) error {
for _, c := range chain {
if c.NotBefore.After(distrustAfter) {
return fmt.Errorf("certificate issued after distrust-after date %q", distrustAfter)
}
}
return nil
})
}
b = append(b, parsed)
}
return roots
return b
}
var bundle = mustParse([]byte(pemRoots))
// Format of the PEM list is:
// * Subject common name
// * SHA256 hash
// * PEM block
var parsedCertificates = mustParse(unparsedCertificates)
var unparsedCertificates = []unparsedCertificate{
`
var (
@@ -122,19 +155,33 @@ func main() {
b := new(bytes.Buffer)
b.WriteString(tmpl)
fmt.Fprintln(b, "const pemRoots = `")
for _, c := range certs {
if len(c.Constraints) > 0 {
// Until the constrained roots API lands, skip anything that has any
// additional constraints. Once that API is available, we can add
// build constraints that support both the current version and the
// new version.
var constraints []string
var skip bool
for _, constraint := range c.Constraints {
switch t := constraint.(type) {
case nss.DistrustAfter:
constraints = append(constraints, fmt.Sprintf("distrustAfter: \"%s\",", time.Time(t).Format(time.RFC3339)))
default:
// If we encounter any constraints we don't support, skip the certificate.
skip = true
break
}
}
if skip {
continue
}
fmt.Fprintf(b, "# %s\n# %x\n", c.X509.Subject.String(), sha256.Sum256(c.X509.Raw))
pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: c.X509.Raw})
fmt.Fprintf(b, "{\ncn: %q,\nsha256Hash: \"%x\",\npem: `%s`,\n",
c.X509.Subject.String(),
sha256.Sum256(c.X509.Raw),
string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.X509.Raw})),
)
for _, constraint := range constraints {
fmt.Fprintln(b, constraint)
}
fmt.Fprintln(b, "},")
}
fmt.Fprintln(b, "`")
fmt.Fprintln(b, "}")
formatted, err := format.Source(b.Bytes())
if err != nil {