Skip to content

Commit 96dc232

Browse files
stapelberggopherbot
authored andcommitted
x509roots/fallback/bundle: add bundle package to export root certs
Fixes golang/go#69898 Change-Id: Idbb1bbe48016a622414c84a56fe26f48bfe712c8 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/687155 Reviewed-by: Roland Shoemaker <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Roland Shoemaker <[email protected]> Reviewed-by: Mateusz Poliwczak <[email protected]>
1 parent 8c9ba31 commit 96dc232

File tree

7 files changed

+105
-64
lines changed

7 files changed

+105
-64
lines changed
File renamed without changes.

x509roots/fallback/bundle.go renamed to x509roots/fallback/bundle/bundle.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x509roots/fallback/bundle_test.go renamed to x509roots/fallback/bundle/bundle_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
package fallback
5+
package bundle
66

77
import (
88
"crypto/sha256"

x509roots/fallback/bundle/roots.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package bundle contains the bundle of root certificates parsed from the NSS
6+
// trust store, using x509roots/nss.
7+
package bundle
8+
9+
import (
10+
"crypto/x509"
11+
_ "embed"
12+
"fmt"
13+
"iter"
14+
"time"
15+
)
16+
17+
//go:embed bundle.der
18+
var rawCerts []byte
19+
20+
// Root represents a root certificate parsed from the NSS trust store.
21+
type Root struct {
22+
// Certificate is the DER-encoded certificate (read-only; do not modify!).
23+
Certificate []byte
24+
25+
// Constraint is nil if the root is unconstrained. If Constraint is non-nil,
26+
// the certificate has additional constraints that cannot be encoded in
27+
// X.509, and when building a certificate chain anchored with this root the
28+
// chain should be passed to this function to check its validity. If using a
29+
// [crypto/x509.CertPool] the root should be added using
30+
// [crypto/x509.CertPool.AddCertWithConstraint].
31+
Constraint func([]*x509.Certificate) error
32+
}
33+
34+
// Roots returns the bundle of root certificates from the NSS trust store. The
35+
// [Root.Certificate] slice must be treated as read-only and should not be
36+
// modified.
37+
func Roots() iter.Seq[Root] {
38+
return func(yield func(Root) bool) {
39+
for _, unparsed := range unparsedCertificates {
40+
root := Root{
41+
Certificate: rawCerts[unparsed.certStartOff : unparsed.certStartOff+unparsed.certLength],
42+
}
43+
// parse possible constraints, this should check all fields of unparsedCertificate.
44+
if unparsed.distrustAfter != "" {
45+
distrustAfter, err := time.Parse(time.RFC3339, unparsed.distrustAfter)
46+
if err != nil {
47+
panic(fmt.Sprintf("failed to parse distrustAfter %q: %s", unparsed.distrustAfter, err))
48+
}
49+
root.Constraint = func(chain []*x509.Certificate) error {
50+
for _, c := range chain {
51+
if c.NotBefore.After(distrustAfter) {
52+
return fmt.Errorf("certificate issued after distrust-after date %q", distrustAfter)
53+
}
54+
}
55+
return nil
56+
}
57+
}
58+
if !yield(root) {
59+
return
60+
}
61+
}
62+
}
63+
}
64+
65+
type unparsedCertificate struct {
66+
cn string
67+
sha256Hash string
68+
certStartOff int
69+
certLength int
70+
71+
// possible constraints
72+
distrustAfter string
73+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package bundle
6+
7+
import (
8+
"crypto/x509"
9+
"testing"
10+
)
11+
12+
func TestRootsCanBeParsed(t *testing.T) {
13+
for root := range Roots() {
14+
if _, err := x509.ParseCertificate(root.Certificate); err != nil {
15+
t.Fatalf("Could not parse root certificate: %v", err)
16+
}
17+
}
18+
}

x509roots/fallback/fallback.go

Lines changed: 9 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,76 +20,26 @@ package fallback
2020

2121
import (
2222
"crypto/x509"
23-
_ "embed"
24-
"fmt"
25-
"time"
26-
)
2723

28-
//go:embed bundle.der
29-
var rawCerts []byte
24+
"golang.org/x/crypto/x509roots/fallback/bundle"
25+
)
3026

3127
func init() {
3228
x509.SetFallbackRoots(newFallbackCertPool())
3329
}
3430

3531
func newFallbackCertPool() *x509.CertPool {
3632
p := x509.NewCertPool()
37-
for _, c := range mustParse(unparsedCertificates) {
38-
if len(c.constraints) == 0 {
39-
p.AddCert(c.cert)
40-
} else {
41-
p.AddCertWithConstraint(c.cert, func(chain []*x509.Certificate) error {
42-
for _, constraint := range c.constraints {
43-
if err := constraint(chain); err != nil {
44-
return err
45-
}
46-
}
47-
return nil
48-
})
49-
}
50-
}
51-
return p
52-
}
53-
54-
type unparsedCertificate struct {
55-
cn string
56-
sha256Hash string
57-
certStartOff int
58-
certLength int
59-
60-
// possible constraints
61-
distrustAfter string
62-
}
63-
64-
type parsedCertificate struct {
65-
cert *x509.Certificate
66-
constraints []func([]*x509.Certificate) error
67-
}
68-
69-
func mustParse(unparsedCerts []unparsedCertificate) []parsedCertificate {
70-
b := make([]parsedCertificate, 0, len(unparsedCerts))
71-
for _, unparsed := range unparsedCerts {
72-
cert, err := x509.ParseCertificate(rawCerts[unparsed.certStartOff : unparsed.certStartOff+unparsed.certLength])
33+
for c := range bundle.Roots() {
34+
cert, err := x509.ParseCertificate(c.Certificate)
7335
if err != nil {
7436
panic(err)
7537
}
76-
parsed := parsedCertificate{cert: cert}
77-
// parse possible constraints, this should check all fields of unparsedCertificate.
78-
if unparsed.distrustAfter != "" {
79-
distrustAfter, err := time.Parse(time.RFC3339, unparsed.distrustAfter)
80-
if err != nil {
81-
panic(fmt.Sprintf("failed to parse distrustAfter %q: %s", unparsed.distrustAfter, err))
82-
}
83-
parsed.constraints = append(parsed.constraints, func(chain []*x509.Certificate) error {
84-
for _, c := range chain {
85-
if c.NotBefore.After(distrustAfter) {
86-
return fmt.Errorf("certificate issued after distrust-after date %q", distrustAfter)
87-
}
88-
}
89-
return nil
90-
})
38+
if c.Constraint == nil {
39+
p.AddCert(cert)
40+
} else {
41+
p.AddCertWithConstraint(cert, c.Constraint)
9142
}
92-
b = append(b, parsed)
9343
}
94-
return b
44+
return p
9545
}

x509roots/gen_fallback_bundle.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ import (
2727

2828
const tmpl = `// Code generated by gen_fallback_bundle.go; DO NOT EDIT.
2929
30-
package fallback
30+
package bundle
3131
3232
var unparsedCertificates = []unparsedCertificate{
3333
`
3434

3535
var (
3636
certDataURL = flag.String("certdata-url", "https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt", "URL to the raw certdata.txt file to parse (certdata-path overrides this, if provided)")
3737
certDataPath = flag.String("certdata-path", "", "Path to the NSS certdata.txt file to parse (this overrides certdata-url, if provided)")
38-
output = flag.String("output", "fallback/bundle.go", "Path to file to write output to")
39-
derOutput = flag.String("deroutput", "fallback/bundle.der", "Path to file to write output to (DER certificate bundle)")
38+
output = flag.String("output", "fallback/bundle/bundle.go", "Path to file to write output to")
39+
derOutput = flag.String("deroutput", "fallback/bundle/bundle.der", "Path to file to write output to (DER certificate bundle)")
4040
)
4141

4242
func main() {

0 commit comments

Comments
 (0)