Source file src/crypto/x509/root.go

     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  
     5  package x509
     6  
     7  import (
     8  	"internal/godebug"
     9  	"io/fs"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"sync"
    15  	_ "unsafe" // for linkname
    16  )
    17  
    18  // systemRoots should be an internal detail,
    19  // but widely used packages access it using linkname.
    20  // Notable members of the hall of shame include:
    21  //   - github.com/breml/rootcerts
    22  //
    23  // Do not remove or change the type signature.
    24  // See go.dev/issue/67401.
    25  //
    26  //go:linkname systemRoots
    27  var (
    28  	once             sync.Once
    29  	systemRootsMu    sync.RWMutex
    30  	systemRoots      *CertPool
    31  	systemRootsErr   error
    32  	fallbacksSet     bool
    33  	useFallbackRoots bool
    34  )
    35  
    36  func systemRootsPool() *CertPool {
    37  	once.Do(initSystemRoots)
    38  	systemRootsMu.RLock()
    39  	defer systemRootsMu.RUnlock()
    40  	return systemRoots
    41  }
    42  
    43  func initSystemRoots() {
    44  	systemRootsMu.Lock()
    45  	defer systemRootsMu.Unlock()
    46  
    47  	fallbackRoots := systemRoots
    48  	systemRoots, systemRootsErr = loadSystemRoots()
    49  	if systemRootsErr != nil {
    50  		systemRoots = nil
    51  	}
    52  
    53  	if fallbackRoots == nil {
    54  		return // no fallbacks to try
    55  	}
    56  
    57  	systemCertsAvail := systemRoots != nil && (systemRoots.len() > 0 || systemRoots.systemPool)
    58  
    59  	if !useFallbackRoots && systemCertsAvail {
    60  		return
    61  	}
    62  
    63  	if useFallbackRoots && systemCertsAvail {
    64  		x509usefallbackroots.IncNonDefault() // overriding system certs with fallback certs.
    65  	}
    66  
    67  	systemRoots, systemRootsErr = fallbackRoots, nil
    68  }
    69  
    70  var x509usefallbackroots = godebug.New("x509usefallbackroots")
    71  
    72  // SetFallbackRoots sets the roots to use during certificate verification, if no
    73  // custom roots are specified and a platform verifier or a system certificate
    74  // pool is not available (for instance in a container which does not have a root
    75  // certificate bundle). SetFallbackRoots will panic if roots is nil.
    76  //
    77  // SetFallbackRoots may only be called once, if called multiple times it will
    78  // panic.
    79  //
    80  // The fallback behavior can be forced on all platforms, even when there is a
    81  // system certificate pool, by setting GODEBUG=x509usefallbackroots=1 (note that
    82  // on Windows and macOS this will disable usage of the platform verification
    83  // APIs and cause the pure Go verifier to be used). Setting
    84  // x509usefallbackroots=1 without calling SetFallbackRoots has no effect.
    85  func SetFallbackRoots(roots *CertPool) {
    86  	if roots == nil {
    87  		panic("roots must be non-nil")
    88  	}
    89  
    90  	systemRootsMu.Lock()
    91  	defer systemRootsMu.Unlock()
    92  
    93  	if fallbacksSet {
    94  		panic("SetFallbackRoots has already been called")
    95  	}
    96  	fallbacksSet = true
    97  
    98  	// Handle case when initSystemRoots was not yet executed.
    99  	// We handle that specially instead of calling loadSystemRoots, to avoid
   100  	// spending excessive amount of cpu here, since the SetFallbackRoots in most cases
   101  	// is going to be called at program startup.
   102  	if systemRoots == nil && systemRootsErr == nil {
   103  		systemRoots = roots
   104  		useFallbackRoots = x509usefallbackroots.Value() == "1"
   105  		return
   106  	}
   107  
   108  	once.Do(func() { panic("unreachable") }) // asserts that system roots were indeed loaded before.
   109  
   110  	forceFallbackRoots := x509usefallbackroots.Value() == "1"
   111  	systemCertsAvail := systemRoots != nil && (systemRoots.len() > 0 || systemRoots.systemPool)
   112  
   113  	if !forceFallbackRoots && systemCertsAvail {
   114  		return
   115  	}
   116  
   117  	if forceFallbackRoots && systemCertsAvail {
   118  		x509usefallbackroots.IncNonDefault() // overriding system certs with fallback certs.
   119  	}
   120  
   121  	systemRoots, systemRootsErr = roots, nil
   122  }
   123  
   124  const (
   125  	// certFileEnv is the environment variable which identifies where to locate
   126  	// the SSL certificate file. If set this overrides the system default.
   127  	certFileEnv = "SSL_CERT_FILE"
   128  
   129  	// certDirEnv is the environment variable which identifies which directory
   130  	// to check for SSL certificate files. If set this overrides the system default.
   131  	// See https://docs.openssl.org/4.0/man1/openssl-rehash/#environment.
   132  	certDirEnv = "SSL_CERT_DIR"
   133  )
   134  
   135  var x509sslcertoverrideplatform = godebug.New("x509sslcertoverrideplatform")
   136  
   137  func loadSystemRoots() (*CertPool, error) {
   138  	certFilePath, certDirPath := os.Getenv(certFileEnv), os.Getenv(certDirEnv)
   139  
   140  	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
   141  		if certFilePath == "" && certDirPath == "" {
   142  			return &CertPool{systemPool: true}, nil
   143  		}
   144  		if x509sslcertoverrideplatform.Value() == "0" {
   145  			x509sslcertoverrideplatform.IncNonDefault()
   146  			return &CertPool{systemPool: true}, nil
   147  		}
   148  	}
   149  
   150  	return loadOnDiskRoots(certFilePath, certDirPath)
   151  }
   152  
   153  func loadOnDiskRoots(certFilePath, certDirPath string) (*CertPool, error) {
   154  	roots := NewCertPool()
   155  
   156  	files := certFiles
   157  	if certFilePath != "" {
   158  		files = []string{certFilePath}
   159  	}
   160  
   161  	var firstErr error
   162  	for _, file := range files {
   163  		data, err := os.ReadFile(file)
   164  		if err == nil {
   165  			roots.AppendCertsFromPEM(data)
   166  			break
   167  		}
   168  		if firstErr == nil && !os.IsNotExist(err) {
   169  			firstErr = err
   170  		}
   171  	}
   172  
   173  	dirs := certDirectories
   174  	if certDirPath != "" {
   175  		// OpenSSL and BoringSSL both use ":" as the SSL_CERT_DIR separator on
   176  		// Unix-like systems, and ";" on Windows.
   177  		// See:
   178  		//  * https://golang.org/issue/35325
   179  		//  * https://docs.openssl.org/4.0/man1/openssl-rehash/#environment
   180  		dirs = filepath.SplitList(certDirPath)
   181  	}
   182  
   183  	for _, directory := range dirs {
   184  		fis, err := readUniqueDirectoryEntries(directory)
   185  		if err != nil {
   186  			if firstErr == nil && !os.IsNotExist(err) {
   187  				firstErr = err
   188  			}
   189  			continue
   190  		}
   191  		for _, fi := range fis {
   192  			data, err := os.ReadFile(filepath.Join(directory, fi.Name()))
   193  			if err == nil {
   194  				roots.AppendCertsFromPEM(data)
   195  			}
   196  		}
   197  	}
   198  
   199  	if roots.len() > 0 || firstErr == nil {
   200  		return roots, nil
   201  	}
   202  
   203  	return nil, firstErr
   204  }
   205  
   206  // readUniqueDirectoryEntries is like os.ReadDir but omits
   207  // symlinks that point within the directory.
   208  func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
   209  	files, err := os.ReadDir(dir)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	uniq := files[:0]
   214  	for _, f := range files {
   215  		if !isSameDirSymlink(f, dir) {
   216  			uniq = append(uniq, f)
   217  		}
   218  	}
   219  	return uniq, nil
   220  }
   221  
   222  // isSameDirSymlink reports whether f in dir is a symlink with a
   223  // target not containing a slash.
   224  func isSameDirSymlink(f fs.DirEntry, dir string) bool {
   225  	if f.Type()&fs.ModeSymlink == 0 {
   226  		return false
   227  	}
   228  	target, err := os.Readlink(filepath.Join(dir, f.Name()))
   229  	return err == nil && !strings.ContainsRune(target, filepath.Separator)
   230  }
   231  

View as plain text