Skip to content

Commit 51e4cb9

Browse files
committed
crypto/x509: store iOS root certs in compressed form
This change combines two changes from elsewhere: tailscale/tailscale@28c632c https://go-review.googlesource.com/c/go/+/229917
1 parent 0b16353 commit 51e4cb9

18 files changed

+1078
-203
lines changed

src/crypto/x509/cert_pool.go

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,65 @@
55
package x509
66

77
import (
8+
"crypto/sha256"
89
"encoding/pem"
910
"errors"
1011
"runtime"
12+
"sync"
1113
)
1214

15+
type sum224 [sha256.Size224]byte
16+
1317
// CertPool is a set of certificates.
1418
type CertPool struct {
15-
bySubjectKeyId map[string][]int
16-
byName map[string][]int
17-
certs []*Certificate
19+
bySubjectKeyId map[string][]int // cert.SubjectKeyId => getCert index(es)
20+
byName map[string][]int // cert.RawSubject => getCert index(es)
21+
22+
// haveSum maps from sum224(cert.Raw) to true. It's used only
23+
// for AddCert duplicate detection, to avoid CertPool.contains
24+
// calls in the AddCert path (because the contains method can
25+
// call getCert and otherwise negate savings from lazy getCert
26+
// funcs).
27+
haveSum map[sum224]bool
28+
29+
// getCert contains funcs that return the certificates.
30+
getCert []func() (*Certificate, error)
31+
32+
// rawSubjects is each cert's RawSubject field.
33+
// Its indexes correspond to the getCert indexes.
34+
rawSubjects [][]byte
1835
}
1936

2037
// NewCertPool returns a new, empty CertPool.
2138
func NewCertPool() *CertPool {
2239
return &CertPool{
2340
bySubjectKeyId: make(map[string][]int),
2441
byName: make(map[string][]int),
42+
haveSum: make(map[sum224]bool),
43+
}
44+
}
45+
46+
// len returns the number of certs in the set.
47+
// A nil set is a valid empty set.
48+
func (s *CertPool) len() int {
49+
if s == nil {
50+
return 0
2551
}
52+
return len(s.getCert)
53+
}
54+
55+
// cert returns cert index n in s.
56+
func (s *CertPool) cert(n int) (*Certificate, error) {
57+
return s.getCert[n]()
2658
}
2759

2860
func (s *CertPool) copy() *CertPool {
2961
p := &CertPool{
3062
bySubjectKeyId: make(map[string][]int, len(s.bySubjectKeyId)),
3163
byName: make(map[string][]int, len(s.byName)),
32-
certs: make([]*Certificate, len(s.certs)),
64+
haveSum: make(map[sum224]bool, len(s.haveSum)),
65+
getCert: make([]func() (*Certificate, error), len(s.getCert)),
66+
rawSubjects: make([][]byte, len(s.rawSubjects)),
3367
}
3468
for k, v := range s.bySubjectKeyId {
3569
indexes := make([]int, len(v))
@@ -41,7 +75,11 @@ func (s *CertPool) copy() *CertPool {
4175
copy(indexes, v)
4276
p.byName[k] = indexes
4377
}
44-
copy(p.certs, s.certs)
78+
for k := range s.haveSum {
79+
p.haveSum[k] = true
80+
}
81+
copy(p.getCert, s.getCert)
82+
copy(p.rawSubjects, s.rawSubjects)
4583
return p
4684
}
4785

@@ -82,41 +120,61 @@ func (s *CertPool) findPotentialParents(cert *Certificate) []int {
82120
return candidates
83121
}
84122

85-
func (s *CertPool) contains(cert *Certificate) bool {
123+
func (s *CertPool) contains(cert *Certificate) (bool, error) {
86124
if s == nil {
87-
return false
125+
return false, nil
88126
}
89-
90127
candidates := s.byName[string(cert.RawSubject)]
91-
for _, c := range candidates {
92-
if s.certs[c].Equal(cert) {
93-
return true
128+
for _, i := range candidates {
129+
c, err := s.cert(i)
130+
if err != nil {
131+
return false, err
132+
}
133+
if c.Equal(cert) {
134+
return true, nil
94135
}
95136
}
96137

97-
return false
138+
return false, nil
98139
}
99140

100141
// AddCert adds a certificate to a pool.
101142
func (s *CertPool) AddCert(cert *Certificate) {
102143
if cert == nil {
103144
panic("adding nil Certificate to CertPool")
104145
}
146+
s.addCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), string(cert.SubjectKeyId), func() (*Certificate, error) {
147+
return cert, nil
148+
})
149+
}
105150

151+
// addCertFunc adds metadata about a certificate to a pool, along with
152+
// a func to fetch that certificate later when needed.
153+
//
154+
// The rawSubject is Certificate.RawSubject and must be non-empty.
155+
// The subjectKeyID is Certificate.SubjectKeyId and may be empty.
156+
// The getCert func may be called 0 or more times.
157+
func (s *CertPool) addCertFunc(rawSum224 sum224, rawSubject, subjectKeyID string, getCert func() (*Certificate, error)) {
106158
// Check that the certificate isn't being added twice.
107-
if s.contains(cert) {
159+
if s.haveSum[rawSum224] {
108160
return
109161
}
162+
s.haveSum[rawSum224] = true
163+
s.addCertFuncNotDup(rawSubject, subjectKeyID, getCert)
164+
}
110165

111-
n := len(s.certs)
112-
s.certs = append(s.certs, cert)
166+
func (s *CertPool) addCertFuncNotDup(rawSubject, subjectKeyID string, getCert func() (*Certificate, error)) {
167+
if getCert == nil {
168+
panic("getCert can't be nil")
169+
}
170+
n := len(s.getCert)
171+
s.getCert = append(s.getCert, getCert)
113172

114-
if len(cert.SubjectKeyId) > 0 {
115-
keyId := string(cert.SubjectKeyId)
116-
s.bySubjectKeyId[keyId] = append(s.bySubjectKeyId[keyId], n)
173+
if subjectKeyID != "" {
174+
s.bySubjectKeyId[subjectKeyID] = append(s.bySubjectKeyId[subjectKeyID], n)
117175
}
118-
name := string(cert.RawSubject)
119-
s.byName[name] = append(s.byName[name], n)
176+
s.byName[rawSubject] = append(s.byName[rawSubject], n)
177+
s.rawSubjects = append(s.rawSubjects, []byte(rawSubject))
120178
}
121179

122180
// AppendCertsFromPEM attempts to parse a series of PEM encoded certificates.
@@ -136,24 +194,34 @@ func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
136194
continue
137195
}
138196

139-
cert, err := ParseCertificate(block.Bytes)
197+
certBytes := block.Bytes
198+
cert, err := ParseCertificate(certBytes)
140199
if err != nil {
141200
continue
142201
}
143-
144-
s.AddCert(cert)
202+
var lazyCert struct {
203+
sync.Once
204+
v *Certificate
205+
}
206+
s.addCertFunc(sha256.Sum224(cert.Raw), string(cert.RawSubject), string(cert.SubjectKeyId), func() (*Certificate, error) {
207+
lazyCert.Do(func() {
208+
// This can't fail, as the same bytes already parsed above.
209+
lazyCert.v, _ = ParseCertificate(certBytes)
210+
certBytes = nil
211+
})
212+
return lazyCert.v, nil
213+
})
145214
ok = true
146215
}
147-
148-
return
216+
return ok
149217
}
150218

151219
// Subjects returns a list of the DER-encoded subjects of
152220
// all of the certificates in the pool.
153221
func (s *CertPool) Subjects() [][]byte {
154-
res := make([][]byte, len(s.certs))
155-
for i, c := range s.certs {
156-
res[i] = c.RawSubject
222+
res := make([][]byte, s.len())
223+
for i, s := range s.rawSubjects {
224+
res[i] = s
157225
}
158226
return res
159227
}

src/crypto/x509/root_darwin_armx.go renamed to src/crypto/x509/certs.pem

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,3 @@
1-
// Code generated by root_darwin_arm_gen --output root_darwin_armx.go; DO NOT EDIT.
2-
3-
// Copyright 2015 The Go Authors. All rights reserved.
4-
// Use of this source code is governed by a BSD-style
5-
// license that can be found in the LICENSE file.
6-
7-
// +build cgo
8-
// +build darwin
9-
// +build arm arm64 ios
10-
// +build !x509omitbundledroots
11-
12-
package x509
13-
14-
func loadSystemRoots() (*CertPool, error) {
15-
p := NewCertPool()
16-
p.AppendCertsFromPEM([]byte(systemRootsPEM))
17-
return p, nil
18-
}
19-
20-
const systemRootsPEM = `
211
-----BEGIN CERTIFICATE-----
222
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
233
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
@@ -4311,4 +4291,3 @@ IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
43114291
i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
43124292
O+7ETPTsJ3xCwnR8gooJybQDJbw=
43134293
-----END CERTIFICATE-----
4314-
`

src/crypto/x509/name_constraints_test.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,7 +1993,7 @@ func TestConstraintCases(t *testing.T) {
19931993
pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
19941994
return buf.String()
19951995
}
1996-
t.Errorf("#%d: root:\n%s", i, certAsPEM(rootPool.certs[0]))
1996+
t.Errorf("#%d: root:\n%s", i, certAsPEM(rootPool.mustCert(0)))
19971997
t.Errorf("#%d: leaf:\n%s", i, certAsPEM(leafCert))
19981998
}
19991999

@@ -2019,19 +2019,27 @@ func writePEMsToTempFile(certs []*Certificate) *os.File {
20192019
return file
20202020
}
20212021

2022+
func allCerts(p *CertPool) []*Certificate {
2023+
all := make([]*Certificate, p.len())
2024+
for i := range all {
2025+
all[i] = p.mustCert(i)
2026+
}
2027+
return all
2028+
}
2029+
20222030
func testChainAgainstOpenSSL(leaf *Certificate, intermediates, roots *CertPool) (string, error) {
20232031
args := []string{"verify", "-no_check_time"}
20242032

2025-
rootsFile := writePEMsToTempFile(roots.certs)
2033+
rootsFile := writePEMsToTempFile(allCerts(roots))
20262034
if debugOpenSSLFailure {
20272035
println("roots file:", rootsFile.Name())
20282036
} else {
20292037
defer os.Remove(rootsFile.Name())
20302038
}
20312039
args = append(args, "-CAfile", rootsFile.Name())
20322040

2033-
if len(intermediates.certs) > 0 {
2034-
intermediatesFile := writePEMsToTempFile(intermediates.certs)
2041+
if intermediates.len() > 0 {
2042+
intermediatesFile := writePEMsToTempFile(allCerts(intermediates))
20352043
if debugOpenSSLFailure {
20362044
println("intermediates file:", intermediatesFile.Name())
20372045
} else {

src/crypto/x509/omit_system_roots.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2020 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+
// +build x509omitsystemroots
6+
7+
package x509
8+
9+
func loadSystemRoots() (*CertPool, error) {
10+
p := NewCertPool()
11+
return p, nil
12+
}

src/crypto/x509/pem_decrypt.go

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

5-
// +build !omitpemdecrypt
6-
75
package x509
86

97
// RFC 1423 describes the encryption of PEM blocks. The algorithm used to

src/crypto/x509/pool_darwin_arm64.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2020 Tailscale Inc. 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+
// +build !x509omitbundledroots
6+
7+
package x509
8+
9+
import (
10+
"compress/gzip"
11+
"io/ioutil"
12+
"strings"
13+
"sync"
14+
)
15+
16+
func certUncompressor(zcertBytes string) func() (*Certificate, error) {
17+
var once sync.Once
18+
var c *Certificate
19+
var err error
20+
return func() (*Certificate, error) {
21+
once.Do(func() {
22+
var certBytes []byte
23+
var zr *gzip.Reader
24+
zr, err = gzip.NewReader(strings.NewReader(zcertBytes))
25+
if err != nil {
26+
return
27+
}
28+
certBytes, err = ioutil.ReadAll(zr)
29+
if err != nil {
30+
return
31+
}
32+
c, err = ParseCertificate(certBytes)
33+
})
34+
return c, err
35+
}
36+
}

src/crypto/x509/root_cgo_darwin.go

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

5-
// +build cgo,!arm,!arm64,!ios
5+
// +build cgo,!arm64,!ios
66

77
package x509
88

99
/*
10-
#cgo CFLAGS: -mmacosx-version-min=10.10 -D__MAC_OS_X_VERSION_MAX_ALLOWED=101300
10+
#cgo CFLAGS: -mmacosx-version-min=10.11
1111
#cgo LDFLAGS: -framework CoreFoundation -framework Security
1212
1313
#include <errno.h>
@@ -305,8 +305,16 @@ func loadSystemRoots() (*CertPool, error) {
305305
untrustedRoots.AppendCertsFromPEM(buf)
306306

307307
trustedRoots := NewCertPool()
308-
for _, c := range roots.certs {
309-
if !untrustedRoots.contains(c) {
308+
for i := 0; i < roots.len(); i++ {
309+
c, err := roots.cert(i)
310+
if err != nil {
311+
return nil, err
312+
}
313+
contains, err := untrustedRoots.contains(c)
314+
if err != nil {
315+
return nil, err
316+
}
317+
if !contains {
310318
trustedRoots.AddCert(c)
311319
}
312320
}

0 commit comments

Comments
 (0)