| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build ignore |
| // +build ignore |
| |
| // Generates root_ios.go. |
| // |
| // As of iOS 13, there is no API for querying the system trusted X.509 root |
| // certificates. |
| // |
| // Apple publishes the trusted root certificates for iOS and macOS on |
| // opensource.apple.com so we embed them into the x509 package. |
| // |
| // Note that this ignores distrusted and revoked certificates. |
| package main |
| |
| import ( |
| "archive/tar" |
| "bytes" |
| "compress/gzip" |
| "crypto/sha256" |
| "crypto/tls" |
| "crypto/x509" |
| "encoding/pem" |
| "flag" |
| "fmt" |
| "go/format" |
| "io" |
| "log" |
| "net/http" |
| "os" |
| "path" |
| "sort" |
| "strings" |
| "time" |
| ) |
| |
| func main() { |
| var output = flag.String("output", "root_ios.go", "file name to write") |
| var version = flag.String("version", "", "security_certificates version") |
| flag.Parse() |
| if *version == "" { |
| log.Fatal("Select the latest security_certificates version from " + |
| "https://opensource.apple.com/source/security_certificates/") |
| } |
| |
| url := "https://opensource.apple.com/tarballs/security_certificates/security_certificates-%s.tar.gz" |
| hc := &http.Client{Timeout: 1 * time.Minute} |
| resp, err := hc.Get(fmt.Sprintf(url, *version)) |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer resp.Body.Close() |
| if resp.StatusCode != http.StatusOK { |
| log.Fatalf("HTTP status not OK: %s", resp.Status) |
| } |
| |
| zr, err := gzip.NewReader(resp.Body) |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer zr.Close() |
| |
| var certs []*x509.Certificate |
| pool := x509.NewCertPool() |
| |
| tr := tar.NewReader(zr) |
| for { |
| hdr, err := tr.Next() |
| if err == io.EOF { |
| break |
| } |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| rootsDirectory := fmt.Sprintf("security_certificates-%s/certificates/roots/", *version) |
| if dir, file := path.Split(hdr.Name); hdr.Typeflag != tar.TypeReg || |
| dir != rootsDirectory || strings.HasPrefix(file, ".") { |
| continue |
| } |
| |
| der, err := io.ReadAll(tr) |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| c, err := x509.ParseCertificate(der) |
| if err != nil { |
| log.Printf("Failed to parse certificate %q: %v", hdr.Name, err) |
| continue |
| } |
| |
| certs = append(certs, c) |
| pool.AddCert(c) |
| } |
| |
| // Quick smoke test to check the pool is well formed, and that we didn't end |
| // up trusting roots in the removed folder. |
| for _, c := range certs { |
| if c.Subject.CommonName == "Symantec Class 2 Public Primary Certification Authority - G4" { |
| log.Fatal("The pool includes a removed root!") |
| } |
| } |
| conn, err := tls.Dial("tcp", "mail.google.com:443", &tls.Config{ |
| RootCAs: pool, |
| }) |
| if err != nil { |
| log.Fatal(err) |
| } |
| conn.Close() |
| |
| certName := func(c *x509.Certificate) string { |
| if c.Subject.CommonName != "" { |
| return c.Subject.CommonName |
| } |
| if len(c.Subject.OrganizationalUnit) > 0 { |
| return c.Subject.OrganizationalUnit[0] |
| } |
| return c.Subject.Organization[0] |
| } |
| sort.Slice(certs, func(i, j int) bool { |
| if strings.ToLower(certName(certs[i])) != strings.ToLower(certName(certs[j])) { |
| return strings.ToLower(certName(certs[i])) < strings.ToLower(certName(certs[j])) |
| } |
| if !certs[i].NotBefore.Equal(certs[j].NotBefore) { |
| return certs[i].NotBefore.Before(certs[j].NotBefore) |
| } |
| fi, fj := sha256.Sum256(certs[i].Raw), sha256.Sum256(certs[j].Raw) |
| return bytes.Compare(fi[:], fj[:]) < 0 |
| }) |
| |
| out := new(bytes.Buffer) |
| fmt.Fprintf(out, header, *version) |
| fmt.Fprintf(out, "const systemRootsPEM = `\n") |
| |
| for _, c := range certs { |
| fmt.Fprintf(out, "# %q\n", certName(c)) |
| h := sha256.Sum256(c.Raw) |
| fmt.Fprintf(out, "# % X\n", h[:len(h)/2]) |
| fmt.Fprintf(out, "# % X\n", h[len(h)/2:]) |
| b := &pem.Block{ |
| Type: "CERTIFICATE", |
| Bytes: c.Raw, |
| } |
| if err := pem.Encode(out, b); err != nil { |
| log.Fatal(err) |
| } |
| } |
| |
| fmt.Fprintf(out, "`") |
| |
| source, err := format.Source(out.Bytes()) |
| if err != nil { |
| log.Fatal(err) |
| } |
| if err := os.WriteFile(*output, source, 0644); err != nil { |
| log.Fatal(err) |
| } |
| } |
| |
| const header = `// Code generated by root_ios_gen.go -version %s; DO NOT EDIT. |
| // Update the version in root.go and regenerate with "go generate". |
| |
| // +build ios |
| // +build !x509omitbundledroots |
| |
| package x509 |
| |
| func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) { |
| return nil, nil |
| } |
| |
| func loadSystemRoots() (*CertPool, error) { |
| p := NewCertPool() |
| p.AppendCertsFromPEM([]byte(systemRootsPEM)) |
| return p, nil |
| } |
| ` |