...

Source file src/golang.org/x/crypto/x509roots/gen_fallback_bundle.go

Documentation: golang.org/x/crypto/x509roots

     1  // Copyright 2023 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  //go:build generate
     6  
     7  //go:generate go run gen_fallback_bundle.go
     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  		// Sort based on the stringified subject (which may not be unique), and
   114  		// break any ties by just sorting on the raw DER (which will be unique,
   115  		// but is expensive). This should produce a stable sorting, which should
   116  		// be mostly readable by a human looking for a specific root or set of
   117  		// roots.
   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  			// Until the constrained roots API lands, skip anything that has any
   131  			// additional constraints. Once that API is available, we can add
   132  			// build constraints that support both the current version and the
   133  			// new version.
   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