1// Copyright 2012 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
5package x509
6
7import (
8	"internal/godebug"
9	"sync"
10	_ "unsafe" // for linkname
11)
12
13// systemRoots should be an internal detail,
14// but widely used packages access it using linkname.
15// Notable members of the hall of shame include:
16//   - github.com/breml/rootcerts
17//
18// Do not remove or change the type signature.
19// See go.dev/issue/67401.
20//
21//go:linkname systemRoots
22var (
23	once           sync.Once
24	systemRootsMu  sync.RWMutex
25	systemRoots    *CertPool
26	systemRootsErr error
27	fallbacksSet   bool
28)
29
30func systemRootsPool() *CertPool {
31	once.Do(initSystemRoots)
32	systemRootsMu.RLock()
33	defer systemRootsMu.RUnlock()
34	return systemRoots
35}
36
37func initSystemRoots() {
38	systemRootsMu.Lock()
39	defer systemRootsMu.Unlock()
40	systemRoots, systemRootsErr = loadSystemRoots()
41	if systemRootsErr != nil {
42		systemRoots = nil
43	}
44}
45
46var x509usefallbackroots = godebug.New("x509usefallbackroots")
47
48// SetFallbackRoots sets the roots to use during certificate verification, if no
49// custom roots are specified and a platform verifier or a system certificate
50// pool is not available (for instance in a container which does not have a root
51// certificate bundle). SetFallbackRoots will panic if roots is nil.
52//
53// SetFallbackRoots may only be called once, if called multiple times it will
54// panic.
55//
56// The fallback behavior can be forced on all platforms, even when there is a
57// system certificate pool, by setting GODEBUG=x509usefallbackroots=1 (note that
58// on Windows and macOS this will disable usage of the platform verification
59// APIs and cause the pure Go verifier to be used). Setting
60// x509usefallbackroots=1 without calling SetFallbackRoots has no effect.
61func SetFallbackRoots(roots *CertPool) {
62	if roots == nil {
63		panic("roots must be non-nil")
64	}
65
66	// trigger initSystemRoots if it hasn't already been called before we
67	// take the lock
68	_ = systemRootsPool()
69
70	systemRootsMu.Lock()
71	defer systemRootsMu.Unlock()
72
73	if fallbacksSet {
74		panic("SetFallbackRoots has already been called")
75	}
76	fallbacksSet = true
77
78	if systemRoots != nil && (systemRoots.len() > 0 || systemRoots.systemPool) {
79		if x509usefallbackroots.Value() != "1" {
80			return
81		}
82		x509usefallbackroots.IncNonDefault()
83	}
84	systemRoots, systemRootsErr = roots, nil
85}
86