1
2
3
4
5
6
7
8
9 package main
10
11 import (
12 "bytes"
13 "crypto/sha256"
14 "encoding/pem"
15 "flag"
16 "fmt"
17 "go/format"
18 "io"
19 "log"
20 "mime"
21 "net/http"
22 "os"
23 "sort"
24
25 "golang.org/x/crypto/x509roots/nss"
26 )
27
28 const tmpl = `// Code generated by gen_fallback_bundle.go; DO NOT EDIT.
29
30 //go:build go1.20
31
32 package fallback
33
34 import "crypto/x509"
35 import "encoding/pem"
36
37 func mustParse(b []byte) []*x509.Certificate {
38 var roots []*x509.Certificate
39 for len(b) > 0 {
40 var block *pem.Block
41 block, b = pem.Decode(b)
42 if block == nil {
43 break
44 }
45 if block.Type != "CERTIFICATE" {
46 panic("unexpected PEM block type: " + block.Type)
47 }
48 cert, err := x509.ParseCertificate(block.Bytes)
49 if err != nil {
50 panic(err)
51 }
52 roots = append(roots, cert)
53 }
54 return roots
55 }
56
57 var bundle = mustParse([]byte(pemRoots))
58
59 // Format of the PEM list is:
60 // * Subject common name
61 // * SHA256 hash
62 // * PEM block
63
64 `
65
66 var (
67 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)")
68 certDataPath = flag.String("certdata-path", "", "Path to the NSS certdata.txt file to parse (this overrides certdata-url, if provided)")
69 output = flag.String("output", "fallback/bundle.go", "Path to file to write output to")
70 )
71
72 func main() {
73 flag.Parse()
74
75 var certdata io.Reader
76
77 if *certDataPath != "" {
78 f, err := os.Open(*certDataPath)
79 if err != nil {
80 log.Fatalf("unable to open %q: %s", *certDataPath, err)
81 }
82 defer f.Close()
83 certdata = f
84 } else {
85 resp, err := http.Get(*certDataURL)
86 if err != nil {
87 log.Fatalf("failed to request %q: %s", *certDataURL, err)
88 }
89 defer resp.Body.Close()
90 if resp.StatusCode != http.StatusOK {
91 body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
92 log.Fatalf("got non-200 OK status code: %v body: %q", resp.Status, body)
93 } else if ct, want := resp.Header.Get("Content-Type"), `text/plain; charset="UTF-8"`; ct != want {
94 if mediaType, _, err := mime.ParseMediaType(ct); err != nil {
95 log.Fatalf("bad Content-Type header %q: %v", ct, err)
96 } else if mediaType != "text/plain" {
97 log.Fatalf("got media type %q, want %q", mediaType, "text/plain")
98 }
99 }
100 certdata = resp.Body
101 }
102
103 certs, err := nss.Parse(certdata)
104 if err != nil {
105 log.Fatalf("failed to parse %q: %s", *certDataPath, err)
106 }
107
108 if len(certs) == 0 {
109 log.Fatal("certdata.txt appears to contain zero roots")
110 }
111
112 sort.Slice(certs, func(i, j int) bool {
113
114
115
116
117
118 subjI, subjJ := certs[i].X509.Subject.String(), certs[j].X509.Subject.String()
119 if subjI == subjJ {
120 return string(certs[i].X509.Raw) < string(certs[j].X509.Raw)
121 }
122 return subjI < subjJ
123 })
124
125 b := new(bytes.Buffer)
126 b.WriteString(tmpl)
127 fmt.Fprintln(b, "const pemRoots = `")
128 for _, c := range certs {
129 if len(c.Constraints) > 0 {
130
131
132
133
134 continue
135 }
136 fmt.Fprintf(b, "# %s\n# %x\n", c.X509.Subject.String(), sha256.Sum256(c.X509.Raw))
137 pem.Encode(b, &pem.Block{Type: "CERTIFICATE", Bytes: c.X509.Raw})
138 }
139 fmt.Fprintln(b, "`")
140
141 formatted, err := format.Source(b.Bytes())
142 if err != nil {
143 log.Fatalf("failed to format source: %s", err)
144 }
145
146 if err := os.WriteFile(*output, formatted, 0644); err != nil {
147 log.Fatalf("failed to write to %q: %s", *output, err)
148 }
149 }
150
View as plain text