tempfork/x509: store certs for iOS compressed in binary, parse lazily

This commit is contained in:
Brad Fitzpatrick 2020-04-25 08:52:53 -07:00
parent 8fd8fc9c7d
commit 28c632c97b
7 changed files with 4533 additions and 4409 deletions

View File

@ -143,12 +143,9 @@ func (s *CertPool) AddCert(cert *Certificate) {
if cert == nil { if cert == nil {
panic("adding nil Certificate to CertPool") panic("adding nil Certificate to CertPool")
} }
err := s.AddCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), string(cert.SubjectKeyId), func() (*Certificate, error) { s.AddCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), string(cert.SubjectKeyId), func() (*Certificate, error) {
return cert, nil return cert, nil
}) })
if err != nil {
panic(err.Error())
}
} }
// AddCertFunc adds metadata about a certificate to a pool, along with // AddCertFunc adds metadata about a certificate to a pool, along with
@ -157,17 +154,19 @@ func (s *CertPool) AddCert(cert *Certificate) {
// The rawSubject is Certificate.RawSubject and must be non-empty. // The rawSubject is Certificate.RawSubject and must be non-empty.
// The subjectKeyID is Certificate.SubjectKeyId and may be empty. // The subjectKeyID is Certificate.SubjectKeyId and may be empty.
// The getCert func may be called 0 or more times. // The getCert func may be called 0 or more times.
func (s *CertPool) AddCertFunc(rawSum224 sum224, rawSubject, subjectKeyID string, getCert func() (*Certificate, error)) error { func (s *CertPool) AddCertFunc(rawSum224 sum224, rawSubject, subjectKeyID string, getCert func() (*Certificate, error)) {
// Check that the certificate isn't being added twice.
if s.haveSum[rawSum224] {
return
}
s.haveSum[rawSum224] = true
s.addCertFuncNotDup(rawSubject, subjectKeyID, getCert)
}
func (s *CertPool) addCertFuncNotDup(rawSubject, subjectKeyID string, getCert func() (*Certificate, error)) {
if getCert == nil { if getCert == nil {
panic("getCert can't be nil") panic("getCert can't be nil")
} }
// Check that the certificate isn't being added twice.
if s.haveSum[rawSum224] {
return nil
}
s.haveSum[rawSum224] = true
n := len(s.getCert) n := len(s.getCert)
s.getCert = append(s.getCert, getCert) s.getCert = append(s.getCert, getCert)
@ -176,7 +175,6 @@ func (s *CertPool) AddCertFunc(rawSum224 sum224, rawSubject, subjectKeyID string
} }
s.byName[rawSubject] = append(s.byName[rawSubject], n) s.byName[rawSubject] = append(s.byName[rawSubject], n)
s.rawSubjects = append(s.rawSubjects, []byte(rawSubject)) s.rawSubjects = append(s.rawSubjects, []byte(rawSubject))
return nil
} }
// AppendCertsFromPEM attempts to parse a series of PEM encoded certificates. // AppendCertsFromPEM attempts to parse a series of PEM encoded certificates.

4293
tempfork/x509/certs.pem Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
// Copyright 2020 Tailscale Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !x509omitbundledroots
package x509
import (
"compress/gzip"
"io/ioutil"
"strings"
"sync"
)
func certUncompressor(zcertBytes string) func() (*Certificate, error) {
var once sync.Once
var c *Certificate
var err error
return func() (*Certificate, error) {
once.Do(func() {
var certBytes []byte
var zr *gzip.Reader
zr, err = gzip.NewReader(strings.NewReader(zcertBytes))
if err != nil {
return
}
certBytes, err = ioutil.ReadAll(zr)
if err != nil {
return
}
c, err = ParseCertificate(certBytes)
})
return c, err
}
}

View File

@ -306,7 +306,10 @@ func loadSystemRoots() (*CertPool, error) {
trustedRoots := NewCertPool() trustedRoots := NewCertPool()
for i := 0; i < roots.len(); i++ { for i := 0; i < roots.len(); i++ {
c := roots.mustCert(i) c, err := roots.cert(i)
if err != nil {
return nil, err
}
contains, err := untrustedRoots.contains(c) contains, err := untrustedRoots.contains(c)
if err != nil { if err != nil {
return nil, err return nil, err

File diff suppressed because one or more lines are too long

View File

@ -18,19 +18,14 @@
import ( import (
"bytes" "bytes"
"crypto/sha256" "compress/gzip"
"crypto/x509" "crypto/x509"
"encoding/hex"
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"go/format" "go/format"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http"
"os/exec"
"regexp"
"strings"
) )
var output = flag.String("output", "root_darwin_arm64.go", "file name to write") var output = flag.String("output", "root_darwin_arm64.go", "file name to write")
@ -45,18 +40,24 @@ func main() {
fmt.Fprintf(buf, "// Code generated by root_darwin_arm_gen --output %s; DO NOT EDIT.\n", *output) fmt.Fprintf(buf, "// Code generated by root_darwin_arm_gen --output %s; DO NOT EDIT.\n", *output)
fmt.Fprintf(buf, "%s", header) fmt.Fprintf(buf, "%s", header)
fmt.Fprintf(buf, "const systemRootsPEM = `\n")
for _, cert := range certs { for _, cert := range certs {
b := &pem.Block{ gzbuf := new(bytes.Buffer)
Type: "CERTIFICATE", zw, err := gzip.NewWriterLevel(gzbuf, gzip.BestCompression)
Bytes: cert.Raw, if err != nil {
}
if err := pem.Encode(buf, b); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if _, err := zw.Write(cert.Raw); err != nil {
log.Fatal(err)
} }
fmt.Fprintf(buf, "`") if err := zw.Close(); err != nil {
log.Fatal(err)
}
fmt.Fprintf(buf, "p.addCertFuncNotDup(%q, %q, certUncompressor(%q))\n",
cert.RawSubject,
cert.SubjectKeyId,
gzbuf.Bytes())
}
fmt.Fprintf(buf, "%s", footer)
source, err := format.Source(buf.Bytes()) source, err := format.Source(buf.Bytes())
if err != nil { if err != nil {
@ -67,38 +68,14 @@ func main() {
} }
} }
func selectCerts() ([]*x509.Certificate, error) { func selectCerts() (certs []*x509.Certificate, err error) {
ids, err := fetchCertIDs() pemCerts, err := ioutil.ReadFile("certs.pem")
if err != nil { if err != nil {
return nil, err return nil, err
} }
for len(pemCerts) > 0 {
scerts, err := sysCerts()
if err != nil {
return nil, err
}
var certs []*x509.Certificate
for _, id := range ids {
if c, ok := scerts[id.fingerprint]; ok {
certs = append(certs, c)
} else {
fmt.Printf("WARNING: cannot find certificate: %s (fingerprint: %s)\n", id.name, id.fingerprint)
}
}
return certs, nil
}
func sysCerts() (certs map[string]*x509.Certificate, err error) {
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
data, err := cmd.Output()
if err != nil {
return nil, err
}
certs = make(map[string]*x509.Certificate)
for len(data) > 0 {
var block *pem.Block var block *pem.Block
block, data = pem.Decode(data) block, pemCerts = pem.Decode(pemCerts)
if block == nil { if block == nil {
break break
} }
@ -108,65 +85,13 @@ func sysCerts() (certs map[string]*x509.Certificate, err error) {
cert, err := x509.ParseCertificate(block.Bytes) cert, err := x509.ParseCertificate(block.Bytes)
if err != nil { if err != nil {
continue return nil, err
} }
certs = append(certs, cert)
fingerprint := sha256.Sum256(cert.Raw)
certs[hex.EncodeToString(fingerprint[:])] = cert
} }
return certs, nil return certs, nil
} }
type certID struct {
name string
fingerprint string
}
// fetchCertIDs fetches IDs of iOS X509 certificates from apple.com.
func fetchCertIDs() ([]certID, error) {
// Download the iOS 11 support page. The index for all iOS versions is here:
// https://support.apple.com/en-us/HT204132
resp, err := http.Get("https://support.apple.com/en-us/HT208125")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
text := string(body)
text = text[strings.Index(text, "<div id=trusted"):]
text = text[:strings.Index(text, "</div>")]
var ids []certID
cols := make(map[string]int)
for i, rowmatch := range regexp.MustCompile("(?s)<tr>(.*?)</tr>").FindAllStringSubmatch(text, -1) {
row := rowmatch[1]
if i == 0 {
// Parse table header row to extract column names
for i, match := range regexp.MustCompile("(?s)<th>(.*?)</th>").FindAllStringSubmatch(row, -1) {
cols[match[1]] = i
}
continue
}
values := regexp.MustCompile("(?s)<td>(.*?)</td>").FindAllStringSubmatch(row, -1)
name := values[cols["Certificate name"]][1]
fingerprint := values[cols["Fingerprint (SHA-256)"]][1]
fingerprint = strings.ReplaceAll(fingerprint, "<br>", "")
fingerprint = strings.ReplaceAll(fingerprint, "\n", "")
fingerprint = strings.ReplaceAll(fingerprint, " ", "")
fingerprint = strings.ToLower(fingerprint)
ids = append(ids, certID{
name: name,
fingerprint: fingerprint,
})
}
return ids, nil
}
const header = ` const header = `
// Copyright 2015 The Go Authors. All rights reserved. // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
@ -178,7 +103,9 @@ func fetchCertIDs() ([]certID, error) {
func loadSystemRoots() (*CertPool, error) { func loadSystemRoots() (*CertPool, error) {
p := NewCertPool() p := NewCertPool()
p.AppendCertsFromPEM([]byte(systemRootsPEM)) `
const footer = `
return p, nil return p, nil
} }
` `

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !cgo // +build !cgo,!arm64
package x509 package x509