[go] crypto/x509: honor SSL_CERT_{FILE,DIR} on windows/darwin

1 view
Skip to first unread message

Roland Shoemaker (Gerrit)

unread,
4:19 PM (7 hours ago) 4:19 PM
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Roland Shoemaker has uploaded the change for review

Commit message

crypto/x509: honor SSL_CERT_{FILE,DIR} on windows/darwin

When these env vars are set, load roots from on-disk, and don't use the
platform verifier.

Fixes #77865
Change-Id: I3183d1636238bed924252fe1288cfa263d17f61d

Change diff

diff --git a/doc/godebug.md b/doc/godebug.md
index 4935d64..c9199de 100644
--- a/doc/godebug.md
+++ b/doc/godebug.md
@@ -170,6 +170,12 @@
labels acquire sensitive information that shouldn't be made available in
tracebacks.

+Go 1.27 added a new `x509sslcertoverrideplatform` setting that controls whether
+crypto/x509 will load roots from disk on Windows and Darwin when `SSL_CERT_FILE`
+or `SSL_CERT_DIR` are set. The default value `x509sslcertoverrideplatform=1` will
+cause roots to be loaded from disk when these environment variables are set.
+Setting `x509sslcertoverrideplatform=0` disables this behavior.
+
### Go 1.26

Go 1.26 added a new `httpcookiemaxnum` setting that controls the maximum number
diff --git a/src/crypto/x509/root.go b/src/crypto/x509/root.go
index 600f759..2e7e1cd 100644
--- a/src/crypto/x509/root.go
+++ b/src/crypto/x509/root.go
@@ -6,6 +6,11 @@

import (
"internal/godebug"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
"sync"
_ "unsafe" // for linkname
)
@@ -115,3 +120,115 @@

systemRoots, systemRootsErr = roots, nil
}
+
+const (
+ // certFileEnv is the environment variable which identifies where to locate
+ // the SSL certificate file. If set this overrides the system default.
+ certFileEnv = "SSL_CERT_FILE"
+
+ // certDirEnv is the environment variable which identifies which directory
+ // to check for SSL certificate files. If set this overrides the system default.
+ // It is a colon separated list of directories.
+ // See https://www.openssl.org/docs/man1.0.2/man1/c_rehash.html.
+ certDirEnv = "SSL_CERT_DIR"
+)
+
+var x509sslcertoverrideplatform = godebug.New("x509sslcertoverrideplatform")
+
+func loadSystemRoots() (*CertPool, error) {
+ certFilePath, certDirPath := os.Getenv(certFileEnv), os.Getenv(certDirEnv)
+
+ if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
+ if certFilePath == "" && certDirPath == "" {
+ return &CertPool{systemPool: true}, nil
+ }
+ if x509sslcertoverrideplatform.Value() == "0" {
+ x509sslcertoverrideplatform.IncNonDefault()
+ return &CertPool{systemPool: true}, nil
+ }
+ }
+
+ return loadOnDiskRoots(certFilePath, certDirPath)
+}
+
+func loadOnDiskRoots(certFilePath, certDirPath string) (*CertPool, error) {
+ roots := NewCertPool()
+
+ files := certFiles
+ if certFilePath != "" {
+ files = []string{certFilePath}
+ }
+
+ var firstErr error
+ for _, file := range files {
+ data, err := os.ReadFile(file)
+ if err == nil {
+ roots.AppendCertsFromPEM(data)
+ break
+ }
+ if firstErr == nil && !os.IsNotExist(err) {
+ firstErr = err
+ }
+ }
+
+ if runtime.GOOS == "plan9" {
+ return roots, firstErr
+ }
+
+ dirs := certDirectories
+ if certDirPath != "" {
+ // OpenSSL and BoringSSL both use ":" as the SSL_CERT_DIR separator.
+ // See:
+ // * https://golang.org/issue/35325
+ // * https://www.openssl.org/docs/man1.0.2/man1/c_rehash.html
+ dirs = strings.Split(certDirPath, ":")
+ }
+
+ for _, directory := range dirs {
+ fis, err := readUniqueDirectoryEntries(directory)
+ if err != nil {
+ if firstErr == nil && !os.IsNotExist(err) {
+ firstErr = err
+ }
+ continue
+ }
+ for _, fi := range fis {
+ data, err := os.ReadFile(directory + "/" + fi.Name())
+ if err == nil {
+ roots.AppendCertsFromPEM(data)
+ }
+ }
+ }
+
+ if roots.len() > 0 || firstErr == nil {
+ return roots, nil
+ }
+
+ return nil, firstErr
+}
+
+// readUniqueDirectoryEntries is like os.ReadDir but omits
+// symlinks that point within the directory.
+func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
+ files, err := os.ReadDir(dir)
+ if err != nil {
+ return nil, err
+ }
+ uniq := files[:0]
+ for _, f := range files {
+ if !isSameDirSymlink(f, dir) {
+ uniq = append(uniq, f)
+ }
+ }
+ return uniq, nil
+}
+
+// isSameDirSymlink reports whether fi in dir is a symlink with a
+// target not containing a slash.
+func isSameDirSymlink(f fs.DirEntry, dir string) bool {
+ if f.Type()&fs.ModeSymlink == 0 {
+ return false
+ }
+ target, err := os.Readlink(filepath.Join(dir, f.Name()))
+ return err == nil && !strings.Contains(target, "/")
+}
diff --git a/src/crypto/x509/root_darwin.go b/src/crypto/x509/root_darwin.go
index 3e9aa1b..1e6514d 100644
--- a/src/crypto/x509/root_darwin.go
+++ b/src/crypto/x509/root_darwin.go
@@ -10,6 +10,11 @@
"fmt"
)

+var (
+ certFiles = []string{}
+ certDirectories = []string{}
+)
+
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
certs := macos.CFArrayCreateMutable()
defer macos.ReleaseCFArray(certs)
@@ -125,7 +130,3 @@
}
return ParseCertificate(data)
}
-
-func loadSystemRoots() (*CertPool, error) {
- return &CertPool{systemPool: true}, nil
-}
diff --git a/src/crypto/x509/root_plan9.go b/src/crypto/x509/root_plan9.go
index 3bd06fe..564e093 100644
--- a/src/crypto/x509/root_plan9.go
+++ b/src/crypto/x509/root_plan9.go
@@ -6,34 +6,13 @@

package x509

-import (
- "os"
-)
-
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/sys/lib/tls/ca.pem",
}

+var certDirectories = []string{}
+
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
return nil, nil
}
-
-func loadSystemRoots() (*CertPool, error) {
- roots := NewCertPool()
- var bestErr error
- for _, file := range certFiles {
- data, err := os.ReadFile(file)
- if err == nil {
- roots.AppendCertsFromPEM(data)
- return roots, nil
- }
- if bestErr == nil || (os.IsNotExist(bestErr) && !os.IsNotExist(err)) {
- bestErr = err
- }
- }
- if bestErr == nil {
- return roots, nil
- }
- return nil, bestErr
-}
diff --git a/src/crypto/x509/root_test.go b/src/crypto/x509/root_test.go
index 218d2b6..130383c 100644
--- a/src/crypto/x509/root_test.go
+++ b/src/crypto/x509/root_test.go
@@ -5,6 +5,14 @@
package x509

import (
+ "bytes"
+ "fmt"
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "runtime"
+ "slices"
+ "strings"
"testing"
)

@@ -108,3 +116,252 @@
})
}
}
+
+const (
+ testDirCN = "test-dir"
+ testFile = "test-file.crt"
+ testFileCN = "test-file"
+ testMissing = "missing"
+)
+
+func TestEnvVars(t *testing.T) {
+ tmpDir := t.TempDir()
+ testCert, err := os.ReadFile("testdata/test-dir.crt")
+ if err != nil {
+ t.Fatalf("failed to read test cert: %s", err)
+ }
+ if err := os.WriteFile(filepath.Join(tmpDir, testFile), testCert, 0644); err != nil {
+ t.Fatalf("failed to write test cert: %s", err)
+ }
+
+ testCases := []struct {
+ name string
+ fileEnv string
+ dirEnv string
+ files []string
+ dirs []string
+ cns []string
+ }{
+ {
+ // Environment variables override the default locations preventing fall through.
+ name: "override-defaults",
+ fileEnv: testMissing,
+ dirEnv: testMissing,
+ files: []string{testFile},
+ dirs: []string{tmpDir},
+ cns: nil,
+ },
+ {
+ // File environment overrides default file locations.
+ name: "file",
+ fileEnv: testFile,
+ dirEnv: "",
+ files: nil,
+ dirs: nil,
+ cns: []string{testFileCN},
+ },
+ {
+ // Directory environment overrides default directory locations.
+ name: "dir",
+ fileEnv: "",
+ dirEnv: tmpDir,
+ files: nil,
+ dirs: nil,
+ cns: []string{testDirCN},
+ },
+ {
+ // File & directory environment overrides both default locations.
+ name: "file+dir",
+ fileEnv: testFile,
+ dirEnv: tmpDir,
+ files: nil,
+ dirs: nil,
+ cns: []string{testFileCN, testDirCN},
+ },
+ {
+ // Environment variable empty / unset uses default locations.
+ name: "empty-fall-through",
+ fileEnv: "",
+ dirEnv: "",
+ files: []string{testFile},
+ dirs: []string{tmpDir},
+ cns: []string{testFileCN, testDirCN},
+ },
+ }
+
+ // Save old settings so we can restore before the test ends.
+ origCertFiles, origCertDirectories := certFiles, certDirectories
+ origFile, origDir := os.Getenv(certFileEnv), os.Getenv(certDirEnv)
+ defer func() {
+ certFiles = origCertFiles
+ certDirectories = origCertDirectories
+ os.Setenv(certFileEnv, origFile)
+ os.Setenv(certDirEnv, origDir)
+ }()
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if err := os.Setenv(certFileEnv, tc.fileEnv); err != nil {
+ t.Fatalf("setenv %q failed: %v", certFileEnv, err)
+ }
+ if err := os.Setenv(certDirEnv, tc.dirEnv); err != nil {
+ t.Fatalf("setenv %q failed: %v", certDirEnv, err)
+ }
+
+ certFiles, certDirectories = tc.files, tc.dirs
+
+ r, err := loadSystemRoots()
+ if err != nil {
+ t.Fatal("unexpected failure:", err)
+ }
+
+ if r == nil {
+ t.Fatal("nil roots")
+ }
+
+ wantSystemPool := (runtime.GOOS == "darwin" || runtime.GOOS == "windows") && tc.dirEnv == "" && tc.fileEnv == ""
+
+ if wantSystemPool {
+ if !r.systemPool {
+ t.Fatal("expected returned cert pool to be a system pool")
+ }
+ if r.len() != 0 {
+ t.Fatalf("expected empty system pool, pool has %d roots", r.len())
+ }
+ return
+ }
+
+ // Verify that the returned certs match, otherwise report where the mismatch is.
+ for i, cn := range tc.cns {
+ if i >= r.len() {
+ t.Errorf("missing cert %v @ %v", cn, i)
+ } else if r.mustCert(t, i).Subject.CommonName != cn {
+ fmt.Printf("%#v\n", r.mustCert(t, 0).Subject)
+ t.Errorf("unexpected cert common name %q, want %q", r.mustCert(t, i).Subject.CommonName, cn)
+ }
+ }
+ if r.len() > len(tc.cns) {
+ t.Errorf("got %v certs, which is more than %v wanted", r.len(), len(tc.cns))
+ }
+ })
+ }
+}
+
+// Ensure that "SSL_CERT_DIR" when used as the environment
+// variable delimited by colons, allows loadSystemRoots to
+// load all the roots from the respective directories.
+// See https://golang.org/issue/35325.
+func TestLoadSystemCertsLoadColonSeparatedDirs(t *testing.T) {
+ origFile, origDir := os.Getenv(certFileEnv), os.Getenv(certDirEnv)
+ origCertFiles := certFiles[:]
+
+ // To prevent any other certs from being loaded in
+ // through "SSL_CERT_FILE" or from known "certFiles",
+ // clear them all, and they'll be reverting on defer.
+ certFiles = certFiles[:0]
+ os.Setenv(certFileEnv, "")
+
+ defer func() {
+ certFiles = origCertFiles[:]
+ os.Setenv(certDirEnv, origDir)
+ os.Setenv(certFileEnv, origFile)
+ }()
+
+ tmpDir := t.TempDir()
+
+ rootPEMs := []string{
+ gtsRoot,
+ googleLeaf,
+ }
+
+ var certDirs []string
+ for i, certPEM := range rootPEMs {
+ certDir := filepath.Join(tmpDir, fmt.Sprintf("cert-%d", i))
+ if err := os.MkdirAll(certDir, 0755); err != nil {
+ t.Fatalf("Failed to create certificate dir: %v", err)
+ }
+ certOutFile := filepath.Join(certDir, "cert.crt")
+ if err := os.WriteFile(certOutFile, []byte(certPEM), 0655); err != nil {
+ t.Fatalf("Failed to write certificate to file: %v", err)
+ }
+ certDirs = append(certDirs, certDir)
+ }
+
+ // Sanity check: the number of certDirs should be equal to the number of roots.
+ if g, w := len(certDirs), len(rootPEMs); g != w {
+ t.Fatalf("Failed sanity check: len(certsDir)=%d is not equal to len(rootsPEMS)=%d", g, w)
+ }
+
+ // Now finally concatenate them with a colon.
+ colonConcatCertDirs := strings.Join(certDirs, ":")
+ os.Setenv(certDirEnv, colonConcatCertDirs)
+ gotPool, err := loadSystemRoots()
+ if err != nil {
+ t.Fatalf("Failed to load system roots: %v", err)
+ }
+ subjects := gotPool.Subjects()
+ // We expect exactly len(rootPEMs) subjects back.
+ if g, w := len(subjects), len(rootPEMs); g != w {
+ t.Fatalf("Invalid number of subjects: got %d want %d", g, w)
+ }
+
+ wantPool := NewCertPool()
+ for _, certPEM := range rootPEMs {
+ wantPool.AppendCertsFromPEM([]byte(certPEM))
+ }
+ strCertPool := func(p *CertPool) string {
+ return string(bytes.Join(p.Subjects(), []byte("\n")))
+ }
+
+ if !certPoolEqual(gotPool, wantPool) {
+ g, w := strCertPool(gotPool), strCertPool(wantPool)
+ t.Fatalf("Mismatched certPools\nGot:\n%s\n\nWant:\n%s", g, w)
+ }
+}
+
+func TestReadUniqueDirectoryEntries(t *testing.T) {
+ tmp := t.TempDir()
+ temp := func(base string) string { return filepath.Join(tmp, base) }
+ if f, err := os.Create(temp("file")); err != nil {
+ t.Fatal(err)
+ } else {
+ f.Close()
+ }
+ if err := os.Symlink("target-in", temp("link-in")); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Symlink("../target-out", temp("link-out")); err != nil {
+ t.Fatal(err)
+ }
+ got, err := readUniqueDirectoryEntries(tmp)
+ if err != nil {
+ t.Fatal(err)
+ }
+ gotNames := []string{}
+ for _, fi := range got {
+ gotNames = append(gotNames, fi.Name())
+ }
+ wantNames := []string{"file", "link-out"}
+ if !slices.Equal(gotNames, wantNames) {
+ t.Errorf("got %q; want %q", gotNames, wantNames)
+ }
+}
+
+func TestSSLCertEnvOverride(t *testing.T) {
+ testenv.SetGODEBUG(t, "x509sslcertoverrideplatform=0")
+ t.Setenv(certFileEnv, "/tmp/nope")
+ t.Setenv(certDirEnv, "/tmp/nope")
+
+ p, err := loadSystemRoots()
+ if err != nil {
+ t.Fatalf("unexpected failure: %s", err)
+ }
+
+ if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
+ if !p.systemPool {
+ t.Fatal("x509sslcertoverrideplatform did not override SSL_CERT_{FILE,DIR}")
+ }
+ } else {
+ t.Fatal("x509sslcertoverrideplatform caused a systemPool to be returned on OS other than windows or darwin")
+ }
+}
diff --git a/src/crypto/x509/root_unix.go b/src/crypto/x509/root_unix.go
index c513b20..3f84b55 100644
--- a/src/crypto/x509/root_unix.go
+++ b/src/crypto/x509/root_unix.go
@@ -6,103 +6,6 @@

package x509

-import (
- "io/fs"
- "os"
- "path/filepath"
- "strings"
-)
-
-const (
- // certFileEnv is the environment variable which identifies where to locate
- // the SSL certificate file. If set this overrides the system default.
- certFileEnv = "SSL_CERT_FILE"
-
- // certDirEnv is the environment variable which identifies which directory
- // to check for SSL certificate files. If set this overrides the system default.
- // It is a colon separated list of directories.
- // See https://www.openssl.org/docs/man1.0.2/man1/c_rehash.html.
- certDirEnv = "SSL_CERT_DIR"
-)
-
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
return nil, nil
}
-
-func loadSystemRoots() (*CertPool, error) {
- roots := NewCertPool()
-
- files := certFiles
- if f := os.Getenv(certFileEnv); f != "" {
- files = []string{f}
- }
-
- var firstErr error
- for _, file := range files {
- data, err := os.ReadFile(file)
- if err == nil {
- roots.AppendCertsFromPEM(data)
- break
- }
- if firstErr == nil && !os.IsNotExist(err) {
- firstErr = err
- }
- }
-
- dirs := certDirectories
- if d := os.Getenv(certDirEnv); d != "" {
- // OpenSSL and BoringSSL both use ":" as the SSL_CERT_DIR separator.
- // See:
- // * https://golang.org/issue/35325
- // * https://www.openssl.org/docs/man1.0.2/man1/c_rehash.html
- dirs = strings.Split(d, ":")
- }
-
- for _, directory := range dirs {
- fis, err := readUniqueDirectoryEntries(directory)
- if err != nil {
- if firstErr == nil && !os.IsNotExist(err) {
- firstErr = err
- }
- continue
- }
- for _, fi := range fis {
- data, err := os.ReadFile(directory + "/" + fi.Name())
- if err == nil {
- roots.AppendCertsFromPEM(data)
- }
- }
- }
-
- if roots.len() > 0 || firstErr == nil {
- return roots, nil
- }
-
- return nil, firstErr
-}
-
-// readUniqueDirectoryEntries is like os.ReadDir but omits
-// symlinks that point within the directory.
-func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
- files, err := os.ReadDir(dir)
- if err != nil {
- return nil, err
- }
- uniq := files[:0]
- for _, f := range files {
- if !isSameDirSymlink(f, dir) {
- uniq = append(uniq, f)
- }
- }
- return uniq, nil
-}
-
-// isSameDirSymlink reports whether fi in dir is a symlink with a
-// target not containing a slash.
-func isSameDirSymlink(f fs.DirEntry, dir string) bool {
- if f.Type()&fs.ModeSymlink == 0 {
- return false
- }
- target, err := os.Readlink(filepath.Join(dir, f.Name()))
- return err == nil && !strings.Contains(target, "/")
-}
diff --git a/src/crypto/x509/root_unix_test.go b/src/crypto/x509/root_unix_test.go
deleted file mode 100644
index b04f09d..0000000
--- a/src/crypto/x509/root_unix_test.go
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2017 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
-
-package x509
-
-import (
- "bytes"
- "fmt"
- "os"
- "path/filepath"
- "slices"
- "strings"
- "testing"
-)
-
-const (
- testDirCN = "test-dir"
- testFile = "test-file.crt"
- testFileCN = "test-file"
- testMissing = "missing"
-)
-
-func TestEnvVars(t *testing.T) {
- tmpDir := t.TempDir()
- testCert, err := os.ReadFile("testdata/test-dir.crt")
- if err != nil {
- t.Fatalf("failed to read test cert: %s", err)
- }
- if err := os.WriteFile(filepath.Join(tmpDir, testFile), testCert, 0644); err != nil {
- t.Fatalf("failed to write test cert: %s", err)
- }
-
- testCases := []struct {
- name string
- fileEnv string
- dirEnv string
- files []string
- dirs []string
- cns []string
- }{
- {
- // Environment variables override the default locations preventing fall through.
- name: "override-defaults",
- fileEnv: testMissing,
- dirEnv: testMissing,
- files: []string{testFile},
- dirs: []string{tmpDir},
- cns: nil,
- },
- {
- // File environment overrides default file locations.
- name: "file",
- fileEnv: testFile,
- dirEnv: "",
- files: nil,
- dirs: nil,
- cns: []string{testFileCN},
- },
- {
- // Directory environment overrides default directory locations.
- name: "dir",
- fileEnv: "",
- dirEnv: tmpDir,
- files: nil,
- dirs: nil,
- cns: []string{testDirCN},
- },
- {
- // File & directory environment overrides both default locations.
- name: "file+dir",
- fileEnv: testFile,
- dirEnv: tmpDir,
- files: nil,
- dirs: nil,
- cns: []string{testFileCN, testDirCN},
- },
- {
- // Environment variable empty / unset uses default locations.
- name: "empty-fall-through",
- fileEnv: "",
- dirEnv: "",
- files: []string{testFile},
- dirs: []string{tmpDir},
- cns: []string{testFileCN, testDirCN},
- },
- }
-
- // Save old settings so we can restore before the test ends.
- origCertFiles, origCertDirectories := certFiles, certDirectories
- origFile, origDir := os.Getenv(certFileEnv), os.Getenv(certDirEnv)
- defer func() {
- certFiles = origCertFiles
- certDirectories = origCertDirectories
- os.Setenv(certFileEnv, origFile)
- os.Setenv(certDirEnv, origDir)
- }()
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if err := os.Setenv(certFileEnv, tc.fileEnv); err != nil {
- t.Fatalf("setenv %q failed: %v", certFileEnv, err)
- }
- if err := os.Setenv(certDirEnv, tc.dirEnv); err != nil {
- t.Fatalf("setenv %q failed: %v", certDirEnv, err)
- }
-
- certFiles, certDirectories = tc.files, tc.dirs
-
- r, err := loadSystemRoots()
- if err != nil {
- t.Fatal("unexpected failure:", err)
- }
-
- if r == nil {
- t.Fatal("nil roots")
- }
-
- // Verify that the returned certs match, otherwise report where the mismatch is.
- for i, cn := range tc.cns {
- if i >= r.len() {
- t.Errorf("missing cert %v @ %v", cn, i)
- } else if r.mustCert(t, i).Subject.CommonName != cn {
- fmt.Printf("%#v\n", r.mustCert(t, 0).Subject)
- t.Errorf("unexpected cert common name %q, want %q", r.mustCert(t, i).Subject.CommonName, cn)
- }
- }
- if r.len() > len(tc.cns) {
- t.Errorf("got %v certs, which is more than %v wanted", r.len(), len(tc.cns))
- }
- })
- }
-}
-
-// Ensure that "SSL_CERT_DIR" when used as the environment
-// variable delimited by colons, allows loadSystemRoots to
-// load all the roots from the respective directories.
-// See https://golang.org/issue/35325.
-func TestLoadSystemCertsLoadColonSeparatedDirs(t *testing.T) {
- origFile, origDir := os.Getenv(certFileEnv), os.Getenv(certDirEnv)
- origCertFiles := certFiles[:]
-
- // To prevent any other certs from being loaded in
- // through "SSL_CERT_FILE" or from known "certFiles",
- // clear them all, and they'll be reverting on defer.
- certFiles = certFiles[:0]
- os.Setenv(certFileEnv, "")
-
- defer func() {
- certFiles = origCertFiles[:]
- os.Setenv(certDirEnv, origDir)
- os.Setenv(certFileEnv, origFile)
- }()
-
- tmpDir := t.TempDir()
-
- rootPEMs := []string{
- gtsRoot,
- googleLeaf,
- }
-
- var certDirs []string
- for i, certPEM := range rootPEMs {
- certDir := filepath.Join(tmpDir, fmt.Sprintf("cert-%d", i))
- if err := os.MkdirAll(certDir, 0755); err != nil {
- t.Fatalf("Failed to create certificate dir: %v", err)
- }
- certOutFile := filepath.Join(certDir, "cert.crt")
- if err := os.WriteFile(certOutFile, []byte(certPEM), 0655); err != nil {
- t.Fatalf("Failed to write certificate to file: %v", err)
- }
- certDirs = append(certDirs, certDir)
- }
-
- // Sanity check: the number of certDirs should be equal to the number of roots.
- if g, w := len(certDirs), len(rootPEMs); g != w {
- t.Fatalf("Failed sanity check: len(certsDir)=%d is not equal to len(rootsPEMS)=%d", g, w)
- }
-
- // Now finally concatenate them with a colon.
- colonConcatCertDirs := strings.Join(certDirs, ":")
- os.Setenv(certDirEnv, colonConcatCertDirs)
- gotPool, err := loadSystemRoots()
- if err != nil {
- t.Fatalf("Failed to load system roots: %v", err)
- }
- subjects := gotPool.Subjects()
- // We expect exactly len(rootPEMs) subjects back.
- if g, w := len(subjects), len(rootPEMs); g != w {
- t.Fatalf("Invalid number of subjects: got %d want %d", g, w)
- }
-
- wantPool := NewCertPool()
- for _, certPEM := range rootPEMs {
- wantPool.AppendCertsFromPEM([]byte(certPEM))
- }
- strCertPool := func(p *CertPool) string {
- return string(bytes.Join(p.Subjects(), []byte("\n")))
- }
-
- if !certPoolEqual(gotPool, wantPool) {
- g, w := strCertPool(gotPool), strCertPool(wantPool)
- t.Fatalf("Mismatched certPools\nGot:\n%s\n\nWant:\n%s", g, w)
- }
-}
-
-func TestReadUniqueDirectoryEntries(t *testing.T) {
- tmp := t.TempDir()
- temp := func(base string) string { return filepath.Join(tmp, base) }
- if f, err := os.Create(temp("file")); err != nil {
- t.Fatal(err)
- } else {
- f.Close()
- }
- if err := os.Symlink("target-in", temp("link-in")); err != nil {
- t.Fatal(err)
- }
- if err := os.Symlink("../target-out", temp("link-out")); err != nil {
- t.Fatal(err)
- }
- got, err := readUniqueDirectoryEntries(tmp)
- if err != nil {
- t.Fatal(err)
- }
- gotNames := []string{}
- for _, fi := range got {
- gotNames = append(gotNames, fi.Name())
- }
- wantNames := []string{"file", "link-out"}
- if !slices.Equal(gotNames, wantNames) {
- t.Errorf("got %q; want %q", gotNames, wantNames)
- }
-}
diff --git a/src/crypto/x509/root_windows.go b/src/crypto/x509/root_windows.go
index 4bea108..62a3cd0 100644
--- a/src/crypto/x509/root_windows.go
+++ b/src/crypto/x509/root_windows.go
@@ -12,9 +12,10 @@
"unsafe"
)

-func loadSystemRoots() (*CertPool, error) {
- return &CertPool{systemPool: true}, nil
-}
+var (
+ certFiles = []string{}
+ certDirectories = []string{}
+)

// Creates a new *syscall.CertContext representing the leaf certificate in an in-memory
// certificate store containing itself and all of the intermediate certificates specified
diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go
index 7ddfad1..56a1390 100644
--- a/src/internal/godebugs/table.go
+++ b/src/internal/godebugs/table.go
@@ -81,6 +81,7 @@
{Name: "x509negativeserial", Package: "crypto/x509", Changed: 23, Old: "1"},
{Name: "x509rsacrt", Package: "crypto/x509", Changed: 24, Old: "0"},
{Name: "x509sha256skid", Package: "crypto/x509", Changed: 25, Old: "0"},
+ {Name: "x509sslcertoverrideplatform", Package: "crypto/x509", Changed: 27, Old: "0"},
{Name: "x509usefallbackroots", Package: "crypto/x509"},
{Name: "x509usepolicies", Package: "crypto/x509", Changed: 24, Old: "0"},
{Name: "zipinsecurepath", Package: "archive/zip"},
diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go
index 1e87b0a..edd9559 100644
--- a/src/runtime/metrics/doc.go
+++ b/src/runtime/metrics/doc.go
@@ -441,6 +441,11 @@
The number of non-default behaviors executed by the crypto/x509
package due to a non-default GODEBUG=x509sha256skid=... setting.

+ /godebug/non-default-behavior/x509sslcertoverrideplatform:events
+ The number of non-default behaviors executed by
+ the crypto/x509 package due to a non-default
+ GODEBUG=x509sslcertoverrideplatform=... setting.
+
/godebug/non-default-behavior/x509usefallbackroots:events
The number of non-default behaviors executed by the crypto/x509
package due to a non-default GODEBUG=x509usefallbackroots=...

Change information

Files:
  • M doc/godebug.md
  • M src/crypto/x509/root.go
  • M src/crypto/x509/root_darwin.go
  • M src/crypto/x509/root_plan9.go
  • M src/crypto/x509/root_test.go
  • M src/crypto/x509/root_unix.go
  • D src/crypto/x509/root_unix_test.go
  • M src/crypto/x509/root_windows.go
  • M src/internal/godebugs/table.go
  • M src/runtime/metrics/doc.go
Change size: L
Delta: 10 files changed, 397 insertions(+), 362 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I3183d1636238bed924252fe1288cfa263d17f61d
Gerrit-Change-Number: 776940
Gerrit-PatchSet: 1
Gerrit-Owner: Roland Shoemaker <rol...@golang.org>
unsatisfied_requirement
satisfied_requirement
open
diffy

Roland Shoemaker (Gerrit)

unread,
4:27 PM (6 hours ago) 4:27 PM
to goph...@pubsubhelper.golang.org, Neal Patel, Daniel McCarney, golang-co...@googlegroups.com
Attention needed from Daniel McCarney and Neal Patel

Roland Shoemaker voted Commit-Queue+1

Commit-Queue+1
Open in Gerrit

Related details

Attention is currently required from:
  • Daniel McCarney
  • Neal Patel
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I3183d1636238bed924252fe1288cfa263d17f61d
Gerrit-Change-Number: 776940
Gerrit-PatchSet: 1
Gerrit-Owner: Roland Shoemaker <rol...@golang.org>
Gerrit-Reviewer: Daniel McCarney <dan...@binaryparadox.net>
Gerrit-Reviewer: Neal Patel <neal...@google.com>
Gerrit-Reviewer: Roland Shoemaker <rol...@golang.org>
Gerrit-Attention: Neal Patel <neal...@google.com>
Gerrit-Attention: Daniel McCarney <dan...@binaryparadox.net>
Gerrit-Comment-Date: Mon, 11 May 2026 20:26:55 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
unsatisfied_requirement
satisfied_requirement
open
diffy

Mateusz Poliwczak (Gerrit)

unread,
5:02 PM (6 hours ago) 5:02 PM
to Roland Shoemaker, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, Neal Patel, Daniel McCarney, golang-co...@googlegroups.com
Attention needed from Daniel McCarney, Neal Patel and Roland Shoemaker

Mateusz Poliwczak added 1 comment

File src/crypto/x509/cert_pool.go
Open in Gerrit

Related details

Attention is currently required from:
  • Daniel McCarney
  • Neal Patel
  • Roland Shoemaker
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: I3183d1636238bed924252fe1288cfa263d17f61d
    Gerrit-Change-Number: 776940
    Gerrit-PatchSet: 1
    Gerrit-Owner: Roland Shoemaker <rol...@golang.org>
    Gerrit-Reviewer: Daniel McCarney <dan...@binaryparadox.net>
    Gerrit-Reviewer: Neal Patel <neal...@google.com>
    Gerrit-Reviewer: Roland Shoemaker <rol...@golang.org>
    Gerrit-Attention: Neal Patel <neal...@google.com>
    Gerrit-Attention: Daniel McCarney <dan...@binaryparadox.net>
    Gerrit-Comment-Date: Mon, 11 May 2026 21:02:36 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy
    Reply all
    Reply to author
    Forward
    0 new messages