|
| 1 | +// Copyright 2025 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 | +//go:build linux |
| 6 | + |
| 7 | +package x509 |
| 8 | + |
| 9 | +import ( |
| 10 | + "encoding/pem" |
| 11 | + "fmt" |
| 12 | + "internal/testenv" |
| 13 | + "os" |
| 14 | + "os/exec" |
| 15 | + "syscall" |
| 16 | + "testing" |
| 17 | +) |
| 18 | + |
| 19 | +func TestSetFallbackRoots(t *testing.T) { |
| 20 | + if testing.Short() { |
| 21 | + t.Skip("skipping test in short mode") |
| 22 | + } |
| 23 | + testenv.MustHaveExec(t) |
| 24 | + |
| 25 | + test := func(t *testing.T, name string, f func(t *testing.T)) { |
| 26 | + t.Run(name, func(t *testing.T) { |
| 27 | + if os.Getenv("CRYPTO_X509_SETFALLBACKROOTS_TEST") != "1" { |
| 28 | + // Execute test in a separate process with CRYPTO_X509_SETFALBACKROOTS_TEST env. |
| 29 | + cmd := exec.Command(os.Args[0], fmt.Sprintf("-test.run=^%v$", t.Name())) |
| 30 | + cmd.Env = append(os.Environ(), "CRYPTO_X509_SETFALLBACKROOTS_TEST=1") |
| 31 | + cmd.SysProcAttr = &syscall.SysProcAttr{ |
| 32 | + Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER, |
| 33 | + UidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: os.Getuid(), Size: 1}}, |
| 34 | + GidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: os.Getgid(), Size: 1}}, |
| 35 | + } |
| 36 | + out, err := cmd.CombinedOutput() |
| 37 | + if err != nil { |
| 38 | + if testenv.SyscallIsNotSupported(err) { |
| 39 | + t.Skipf("skipping: could not start process with CLONE_NEWNS and CLONE_NEWUSER: %v", err) |
| 40 | + } |
| 41 | + t.Errorf("%v\n%s", err, out) |
| 42 | + } |
| 43 | + return |
| 44 | + } |
| 45 | + |
| 46 | + // This test is executed in a separate user and mount namespace, thus |
| 47 | + // we can mount a separate "/etc" empty bind mount, without the need for root access. |
| 48 | + // On linux all certs reside in /etc, so as we bind an empty dir, we |
| 49 | + // get a full control over the system CAs, required for this test. |
| 50 | + if err := syscall.Mount(t.TempDir(), "/etc", "", syscall.MS_BIND, ""); err != nil { |
| 51 | + if testenv.SyscallIsNotSupported(err) { |
| 52 | + t.Skipf("Failed to mount /etc: %v", err) |
| 53 | + } |
| 54 | + t.Fatalf("Failed to mount /etc: %v", err) |
| 55 | + } |
| 56 | + |
| 57 | + t.Cleanup(func() { |
| 58 | + if err := syscall.Unmount("/etc", 0); err != nil { |
| 59 | + t.Errorf("failed to unmount /etc: %v", err) |
| 60 | + } |
| 61 | + }) |
| 62 | + |
| 63 | + f(t) |
| 64 | + }) |
| 65 | + } |
| 66 | + |
| 67 | + newFallbackCertPool := func(t *testing.T) *CertPool { |
| 68 | + t.Helper() |
| 69 | + |
| 70 | + const fallbackCert = `-----BEGIN CERTIFICATE----- |
| 71 | +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw |
| 72 | +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg |
| 73 | +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 |
| 74 | +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT |
| 75 | +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw |
| 76 | +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW |
| 77 | ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 |
| 78 | +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T |
| 79 | +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI |
| 80 | +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW |
| 81 | +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 |
| 82 | +/q4AaOeMSQ+2b1tbFfLn |
| 83 | +-----END CERTIFICATE----- |
| 84 | +` |
| 85 | + b, _ := pem.Decode([]byte(fallbackCert)) |
| 86 | + cert, err := ParseCertificate(b.Bytes) |
| 87 | + if err != nil { |
| 88 | + t.Fatal(err) |
| 89 | + } |
| 90 | + p := NewCertPool() |
| 91 | + p.AddCert(cert) |
| 92 | + return p |
| 93 | + } |
| 94 | + |
| 95 | + installSystemRootCAs := func(t *testing.T) { |
| 96 | + t.Helper() |
| 97 | + |
| 98 | + const systemCAs = `-----BEGIN CERTIFICATE----- |
| 99 | +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 |
| 128 | +-----END CERTIFICATE----- |
| 129 | +` |
| 130 | + if err := os.MkdirAll("/etc/ssl/certs", 06660); err != nil { |
| 131 | + t.Fatal(err) |
| 132 | + } |
| 133 | + |
| 134 | + if err := os.WriteFile("/etc/ssl/certs/ca-certificates.crt", []byte(systemCAs), 0666); err != nil { |
| 135 | + t.Fatal(err) |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + test(t, "after_first_load_no_system_CAs", func(t *testing.T) { |
| 140 | + SystemCertPool() // load system certs, before setting fallbacks |
| 141 | + fallback := newFallbackCertPool(t) |
| 142 | + SetFallbackRoots(fallback) |
| 143 | + got, err := SystemCertPool() |
| 144 | + if err != nil { |
| 145 | + t.Fatal(err) |
| 146 | + } |
| 147 | + if !got.Equal(fallback) { |
| 148 | + t.Fatal("SystemCertPool returned a non-fallback CertPool") |
| 149 | + } |
| 150 | + }) |
| 151 | + |
| 152 | + test(t, "after_first_load_system_CA_read_error", func(t *testing.T) { |
| 153 | + // This will fail to load in SystemCertPool since this is a directory, |
| 154 | + // rather than a file with certificates. |
| 155 | + if err := os.MkdirAll("/etc/ssl/certs/ca-certificates.crt", 0666); err != nil { |
| 156 | + t.Fatal(err) |
| 157 | + } |
| 158 | + |
| 159 | + _, err := SystemCertPool() // load system certs, before setting fallbacks |
| 160 | + if err == nil { |
| 161 | + t.Fatal("unexpected success") |
| 162 | + } |
| 163 | + |
| 164 | + fallback := newFallbackCertPool(t) |
| 165 | + SetFallbackRoots(fallback) |
| 166 | + got, err := SystemCertPool() |
| 167 | + if err != nil { |
| 168 | + t.Fatal(err) |
| 169 | + } |
| 170 | + if !got.Equal(fallback) { |
| 171 | + t.Fatal("SystemCertPool returned a non-fallback CertPool") |
| 172 | + } |
| 173 | + }) |
| 174 | + |
| 175 | + test(t, "after_first_load_with_system_CAs", func(t *testing.T) { |
| 176 | + installSystemRootCAs(t) |
| 177 | + |
| 178 | + SystemCertPool() // load system certs, before setting fallbacks |
| 179 | + |
| 180 | + fallback := newFallbackCertPool(t) |
| 181 | + SetFallbackRoots(fallback) |
| 182 | + got, err := SystemCertPool() |
| 183 | + if err != nil { |
| 184 | + t.Fatal(err) |
| 185 | + } |
| 186 | + if got.Equal(fallback) { |
| 187 | + t.Fatal("SystemCertPool returned the fallback CertPool") |
| 188 | + } |
| 189 | + }) |
| 190 | + |
| 191 | + test(t, "before_first_load_no_system_CAs", func(t *testing.T) { |
| 192 | + fallback := newFallbackCertPool(t) |
| 193 | + SetFallbackRoots(fallback) |
| 194 | + got, err := SystemCertPool() |
| 195 | + if err != nil { |
| 196 | + t.Fatal(err) |
| 197 | + } |
| 198 | + if !got.Equal(fallback) { |
| 199 | + t.Fatal("SystemCertPool returned a non-fallback CertPool") |
| 200 | + } |
| 201 | + }) |
| 202 | + |
| 203 | + test(t, "before_first_load_system_CA_read_error", func(t *testing.T) { |
| 204 | + // This will fail to load in SystemCertPool since this is a directory, |
| 205 | + // rather than a file with certificates. |
| 206 | + if err := os.MkdirAll("/etc/ssl/certs/ca-certificates.crt", 0666); err != nil { |
| 207 | + t.Fatal(err) |
| 208 | + } |
| 209 | + |
| 210 | + fallback := newFallbackCertPool(t) |
| 211 | + SetFallbackRoots(fallback) |
| 212 | + got, err := SystemCertPool() |
| 213 | + if err != nil { |
| 214 | + t.Fatal(err) |
| 215 | + } |
| 216 | + if !got.Equal(fallback) { |
| 217 | + t.Fatal("SystemCertPool returned a non-fallback CertPool") |
| 218 | + } |
| 219 | + }) |
| 220 | + |
| 221 | + test(t, "before_first_load_with_system_CAs", func(t *testing.T) { |
| 222 | + installSystemRootCAs(t) |
| 223 | + |
| 224 | + fallback := newFallbackCertPool(t) |
| 225 | + SetFallbackRoots(fallback) |
| 226 | + got, err := SystemCertPool() |
| 227 | + if err != nil { |
| 228 | + t.Fatal(err) |
| 229 | + } |
| 230 | + if got.Equal(fallback) { |
| 231 | + t.Fatal("SystemCertPool returned the fallback CertPool") |
| 232 | + } |
| 233 | + }) |
| 234 | + |
| 235 | + test(t, "before_first_load_force_godebug", func(t *testing.T) { |
| 236 | + if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil { |
| 237 | + t.Fatal(err) |
| 238 | + } |
| 239 | + |
| 240 | + installSystemRootCAs(t) |
| 241 | + |
| 242 | + fallback := newFallbackCertPool(t) |
| 243 | + SetFallbackRoots(fallback) |
| 244 | + got, err := SystemCertPool() |
| 245 | + if err != nil { |
| 246 | + t.Fatal(err) |
| 247 | + } |
| 248 | + if !got.Equal(fallback) { |
| 249 | + t.Fatal("SystemCertPool returned a non-fallback CertPool") |
| 250 | + } |
| 251 | + }) |
| 252 | + |
| 253 | + test(t, "after_first_load_force_godebug", func(t *testing.T) { |
| 254 | + if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil { |
| 255 | + t.Fatal(err) |
| 256 | + } |
| 257 | + |
| 258 | + installSystemRootCAs(t) |
| 259 | + SystemCertPool() // load system certs, before setting fallbacks |
| 260 | + |
| 261 | + fallback := newFallbackCertPool(t) |
| 262 | + SetFallbackRoots(fallback) |
| 263 | + got, err := SystemCertPool() |
| 264 | + if err != nil { |
| 265 | + t.Fatal(err) |
| 266 | + } |
| 267 | + if !got.Equal(fallback) { |
| 268 | + t.Fatal("SystemCertPool returned a non-fallback CertPool") |
| 269 | + } |
| 270 | + }) |
| 271 | + |
| 272 | + test(t, "after_first_load_force_godebug_no_system_certs", func(t *testing.T) { |
| 273 | + if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil { |
| 274 | + t.Fatal(err) |
| 275 | + } |
| 276 | + |
| 277 | + SystemCertPool() // load system certs, before setting fallbacks |
| 278 | + |
| 279 | + fallback := newFallbackCertPool(t) |
| 280 | + SetFallbackRoots(fallback) |
| 281 | + got, err := SystemCertPool() |
| 282 | + if err != nil { |
| 283 | + t.Fatal(err) |
| 284 | + } |
| 285 | + if !got.Equal(fallback) { |
| 286 | + t.Fatal("SystemCertPool returned a non-fallback CertPool") |
| 287 | + } |
| 288 | + }) |
| 289 | +} |
0 commit comments