Skip to content

Commit 04d6aa6

Browse files
rolandshoemakergopherbot
authored andcommitted
crypto/x509: implement SetFallbackRoots
Adds a method which allows users to set a fallback certificate pool for usage during verification if the system certificate pool is empty. Updates #43958 Change-Id: I279dd2f753743bce19790f2ae29f063c89c9359d Reviewed-on: https://go-review.googlesource.com/c/go/+/449235 Run-TryBot: Roland Shoemaker <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Auto-Submit: Roland Shoemaker <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]>
1 parent c824448 commit 04d6aa6

File tree

4 files changed

+161
-9
lines changed

4 files changed

+161
-9
lines changed

api/next/43958.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg crypto/x509, func SetFallbackRoots(*CertPool) #43958

src/crypto/x509/root.go

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,69 @@
44

55
package x509
66

7-
// To update the embedded iOS root store, update the -version
8-
// argument to the latest security_certificates version from
9-
// https://opensource.apple.com/source/security_certificates/
10-
// and run "go generate". See https://golang.org/issue/38843.
11-
//
12-
//go:generate go run root_ios_gen.go -version 55188.120.1.0.1
13-
14-
import "sync"
7+
import (
8+
"internal/godebug"
9+
"sync"
10+
)
1511

1612
var (
1713
once sync.Once
14+
systemRootsMu sync.RWMutex
1815
systemRoots *CertPool
1916
systemRootsErr error
17+
fallbacksSet bool
2018
)
2119

2220
func systemRootsPool() *CertPool {
2321
once.Do(initSystemRoots)
22+
systemRootsMu.RLock()
23+
defer systemRootsMu.RUnlock()
2424
return systemRoots
2525
}
2626

2727
func initSystemRoots() {
28+
systemRootsMu.Lock()
29+
defer systemRootsMu.Unlock()
2830
systemRoots, systemRootsErr = loadSystemRoots()
2931
if systemRootsErr != nil {
3032
systemRoots = nil
3133
}
3234
}
35+
36+
var forceFallback = godebug.New("x509usefallbackroots")
37+
38+
// SetFallbackRoots sets the roots to use during certificate verification, if no
39+
// custom roots are specified and a platform verifier or a system certificate
40+
// pool is not available (for instance in a container which does not have a root
41+
// certificate bundle). SetFallbackRoots will panic if roots is nil.
42+
//
43+
// SetFallbackRoots may only be called once, if called multiple times it will
44+
// panic.
45+
//
46+
// The fallback behavior can be forced on all platforms, even when there is a
47+
// system certificate pool, by setting GODEBUG=x509usefallbackroots=1 (note that
48+
// on Windows and macOS this will disable usage of the platform verification
49+
// APIs and cause the pure Go verifier to be used). Setting
50+
// x509usefallbackroots=1 without calling SetFallbackRoots has no effect.
51+
func SetFallbackRoots(roots *CertPool) {
52+
if roots == nil {
53+
panic("roots must be non-nil")
54+
}
55+
56+
// trigger initSystemRoots if it hasn't already been called before we
57+
// take the lock
58+
_ = systemRootsPool()
59+
60+
systemRootsMu.Lock()
61+
defer systemRootsMu.Unlock()
62+
63+
if fallbacksSet {
64+
panic("SetFallbackRoots has already been called")
65+
}
66+
fallbacksSet = true
67+
68+
if systemRoots != nil && (systemRoots.len() > 0 || systemRoots.systemPool) && forceFallback.Value() != "1" {
69+
return
70+
}
71+
systemRoots, systemRootsErr = roots, nil
72+
}

src/crypto/x509/root_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2022 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+
package x509
6+
7+
import (
8+
"testing"
9+
)
10+
11+
func TestFallbackPanic(t *testing.T) {
12+
defer func() {
13+
if recover() == nil {
14+
t.Fatal("Multiple calls to SetFallbackRoots should panic")
15+
}
16+
}()
17+
SetFallbackRoots(nil)
18+
SetFallbackRoots(nil)
19+
}
20+
21+
func TestFallback(t *testing.T) {
22+
// call systemRootsPool so that the sync.Once is triggered, and we can
23+
// manipulate systemRoots without worrying about our working being overwritten
24+
systemRootsPool()
25+
if systemRoots != nil {
26+
originalSystemRoots := *systemRoots
27+
defer func() { systemRoots = &originalSystemRoots }()
28+
}
29+
30+
tests := []struct {
31+
name string
32+
systemRoots *CertPool
33+
systemPool bool
34+
poolContent []*Certificate
35+
forceFallback bool
36+
returnsFallback bool
37+
}{
38+
{
39+
name: "nil systemRoots",
40+
returnsFallback: true,
41+
},
42+
{
43+
name: "empty systemRoots",
44+
systemRoots: NewCertPool(),
45+
returnsFallback: true,
46+
},
47+
{
48+
name: "empty systemRoots system pool",
49+
systemRoots: NewCertPool(),
50+
systemPool: true,
51+
},
52+
{
53+
name: "filled systemRoots system pool",
54+
systemRoots: NewCertPool(),
55+
poolContent: []*Certificate{{}},
56+
systemPool: true,
57+
},
58+
{
59+
name: "filled systemRoots",
60+
systemRoots: NewCertPool(),
61+
poolContent: []*Certificate{{}},
62+
},
63+
{
64+
name: "filled systemRoots, force fallback",
65+
systemRoots: NewCertPool(),
66+
poolContent: []*Certificate{{}},
67+
forceFallback: true,
68+
returnsFallback: true,
69+
},
70+
{
71+
name: "filled systemRoot system pool, force fallback",
72+
systemRoots: NewCertPool(),
73+
poolContent: []*Certificate{{}},
74+
systemPool: true,
75+
forceFallback: true,
76+
returnsFallback: true,
77+
},
78+
}
79+
80+
for _, tc := range tests {
81+
t.Run(tc.name, func(t *testing.T) {
82+
fallbacksSet = false
83+
systemRoots = tc.systemRoots
84+
if systemRoots != nil {
85+
systemRoots.systemPool = tc.systemPool
86+
}
87+
for _, c := range tc.poolContent {
88+
systemRoots.AddCert(c)
89+
}
90+
if tc.forceFallback {
91+
t.Setenv("GODEBUG", "x509usefallbackroots=1")
92+
} else {
93+
t.Setenv("GODEBUG", "x509usefallbackroots=0")
94+
}
95+
96+
fallbackPool := NewCertPool()
97+
SetFallbackRoots(fallbackPool)
98+
99+
systemPoolIsFallback := systemRoots == fallbackPool
100+
101+
if tc.returnsFallback && !systemPoolIsFallback {
102+
t.Error("systemRoots was not set to fallback pool")
103+
} else if !tc.returnsFallback && systemPoolIsFallback {
104+
t.Error("systemRoots was set to fallback pool when it shouldn't have been")
105+
}
106+
})
107+
}
108+
}

src/crypto/x509/verify.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,10 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
766766

767767
// Use platform verifiers, where available, if Roots is from SystemCertPool.
768768
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
769-
if opts.Roots == nil {
769+
// Don't use the system verifier if the system pool was replaced with a non-system pool,
770+
// i.e. if SetFallbackRoots was called with x509usefallbackroots=1.
771+
systemPool := systemRootsPool()
772+
if opts.Roots == nil && (systemPool == nil || systemPool.systemPool) {
770773
return c.systemVerify(&opts)
771774
}
772775
if opts.Roots != nil && opts.Roots.systemPool {

0 commit comments

Comments
 (0)