Source file src/crypto/x509/root_linux_test.go

     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  	"syscall"
    15  	"testing"
    16  )
    17  
    18  func TestSetFallbackRoots(t *testing.T) {
    19  	if testing.Short() {
    20  		t.Skip("skipping test in short mode")
    21  	}
    22  
    23  	test := func(t *testing.T, name string, f func(t *testing.T)) {
    24  		t.Run(name, func(t *testing.T) {
    25  			if os.Getenv("CRYPTO_X509_SETFALLBACKROOTS_TEST") != "1" {
    26  				// Execute test in a separate process with CRYPTO_X509_SETFALBACKROOTS_TEST env.
    27  				cmd := testenv.Command(t, testenv.Executable(t), fmt.Sprintf("-test.run=^%v$", t.Name()), "-test.v")
    28  				cmd.Env = append(os.Environ(), "CRYPTO_X509_SETFALLBACKROOTS_TEST=1")
    29  				cmd.SysProcAttr = &syscall.SysProcAttr{
    30  					Cloneflags:  syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
    31  					UidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: os.Getuid(), Size: 1}},
    32  					GidMappings: []syscall.SysProcIDMap{{ContainerID: 0, HostID: os.Getgid(), Size: 1}},
    33  				}
    34  				out, err := cmd.CombinedOutput()
    35  				if testenv.SyscallIsNotSupported(err) {
    36  					t.Skipf("skipping: could not start process with CLONE_NEWNS and CLONE_NEWUSER: %v", err)
    37  				}
    38  				t.Logf("running with CRYPTO_X509_SETFALLBACKROOTS_TEST=1:\n%s", out)
    39  				if err != nil {
    40  					t.Errorf("CRYPTO_X509_SETFALLBACKROOTS_TEST=1 subprocess failed: %v", err)
    41  				}
    42  				return
    43  			}
    44  
    45  			// This test is executed in a separate user and mount namespace, thus
    46  			// we can mount a separate "/etc" empty bind mount, without the need for root access.
    47  			// On linux all certs reside in /etc, so as we bind an empty dir, we
    48  			// get a full control over the system CAs, required for this test.
    49  			if err := syscall.Mount(t.TempDir(), "/etc", "", syscall.MS_BIND, ""); err != nil {
    50  				if testenv.SyscallIsNotSupported(err) {
    51  					t.Skipf("Failed to mount /etc: %v", err)
    52  				}
    53  				t.Fatalf("Failed to mount /etc: %v", err)
    54  			}
    55  
    56  			t.Cleanup(func() {
    57  				if err := syscall.Unmount("/etc", 0); err != nil {
    58  					t.Errorf("failed to unmount /etc: %v", err)
    59  				}
    60  			})
    61  
    62  			f(t)
    63  		})
    64  	}
    65  
    66  	newFallbackCertPool := func(t *testing.T) *CertPool {
    67  		t.Helper()
    68  
    69  		const fallbackCert = `-----BEGIN CERTIFICATE-----
    70  MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
    71  CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
    72  R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
    73  MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
    74  ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
    75  EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
    76  +1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
    77  ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
    78  AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
    79  zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
    80  tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
    81  /q4AaOeMSQ+2b1tbFfLn
    82  -----END CERTIFICATE-----
    83  `
    84  		b, _ := pem.Decode([]byte(fallbackCert))
    85  		cert, err := ParseCertificate(b.Bytes)
    86  		if err != nil {
    87  			t.Fatal(err)
    88  		}
    89  		p := NewCertPool()
    90  		p.AddCert(cert)
    91  		return p
    92  	}
    93  
    94  	installSystemRootCAs := func(t *testing.T) {
    95  		t.Helper()
    96  
    97  		const systemCAs = `-----BEGIN CERTIFICATE-----
    98  MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
    99  TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
   100  cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
   101  WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
   102  ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
   103  MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
   104  h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
   105  0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
   106  A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
   107  T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
   108  B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
   109  B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
   110  KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
   111  OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
   112  jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
   113  qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
   114  rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
   115  HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
   116  hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
   117  ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
   118  3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
   119  NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
   120  ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
   121  TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
   122  jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
   123  oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
   124  4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
   125  mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
   126  emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
   127  -----END CERTIFICATE-----
   128  `
   129  		if err := os.MkdirAll("/etc/ssl/certs", 06660); err != nil {
   130  			t.Fatal(err)
   131  		}
   132  
   133  		if err := os.WriteFile("/etc/ssl/certs/ca-certificates.crt", []byte(systemCAs), 0666); err != nil {
   134  			t.Fatal(err)
   135  		}
   136  	}
   137  
   138  	test(t, "after_first_load_no_system_CAs", func(t *testing.T) {
   139  		SystemCertPool() // load system certs, before setting fallbacks
   140  		fallback := newFallbackCertPool(t)
   141  		SetFallbackRoots(fallback)
   142  		got, err := SystemCertPool()
   143  		if err != nil {
   144  			t.Fatal(err)
   145  		}
   146  		if !got.Equal(fallback) {
   147  			t.Fatal("SystemCertPool returned a non-fallback CertPool")
   148  		}
   149  	})
   150  
   151  	test(t, "after_first_load_system_CA_read_error", func(t *testing.T) {
   152  		// This will fail to load in SystemCertPool since this is a directory,
   153  		// rather than a file with certificates.
   154  		if err := os.MkdirAll("/etc/ssl/certs/ca-certificates.crt", 0666); err != nil {
   155  			t.Fatal(err)
   156  		}
   157  
   158  		_, err := SystemCertPool() // load system certs, before setting fallbacks
   159  		if err == nil {
   160  			t.Fatal("unexpected success")
   161  		}
   162  
   163  		fallback := newFallbackCertPool(t)
   164  		SetFallbackRoots(fallback)
   165  		got, err := SystemCertPool()
   166  		if err != nil {
   167  			t.Fatal(err)
   168  		}
   169  		if !got.Equal(fallback) {
   170  			t.Fatal("SystemCertPool returned a non-fallback CertPool")
   171  		}
   172  	})
   173  
   174  	test(t, "after_first_load_with_system_CAs", func(t *testing.T) {
   175  		installSystemRootCAs(t)
   176  
   177  		SystemCertPool() // load system certs, before setting fallbacks
   178  
   179  		fallback := newFallbackCertPool(t)
   180  		SetFallbackRoots(fallback)
   181  		got, err := SystemCertPool()
   182  		if err != nil {
   183  			t.Fatal(err)
   184  		}
   185  		if got.Equal(fallback) {
   186  			t.Fatal("SystemCertPool returned the fallback CertPool")
   187  		}
   188  	})
   189  
   190  	test(t, "before_first_load_no_system_CAs", func(t *testing.T) {
   191  		fallback := newFallbackCertPool(t)
   192  		SetFallbackRoots(fallback)
   193  		got, err := SystemCertPool()
   194  		if err != nil {
   195  			t.Fatal(err)
   196  		}
   197  		if !got.Equal(fallback) {
   198  			t.Fatal("SystemCertPool returned a non-fallback CertPool")
   199  		}
   200  	})
   201  
   202  	test(t, "before_first_load_system_CA_read_error", func(t *testing.T) {
   203  		// This will fail to load in SystemCertPool since this is a directory,
   204  		// rather than a file with certificates.
   205  		if err := os.MkdirAll("/etc/ssl/certs/ca-certificates.crt", 0666); err != nil {
   206  			t.Fatal(err)
   207  		}
   208  
   209  		fallback := newFallbackCertPool(t)
   210  		SetFallbackRoots(fallback)
   211  		got, err := SystemCertPool()
   212  		if err != nil {
   213  			t.Fatal(err)
   214  		}
   215  		if !got.Equal(fallback) {
   216  			t.Fatal("SystemCertPool returned a non-fallback CertPool")
   217  		}
   218  	})
   219  
   220  	test(t, "before_first_load_with_system_CAs", func(t *testing.T) {
   221  		installSystemRootCAs(t)
   222  
   223  		fallback := newFallbackCertPool(t)
   224  		SetFallbackRoots(fallback)
   225  		got, err := SystemCertPool()
   226  		if err != nil {
   227  			t.Fatal(err)
   228  		}
   229  		if got.Equal(fallback) {
   230  			t.Fatal("SystemCertPool returned the fallback CertPool")
   231  		}
   232  	})
   233  
   234  	test(t, "before_first_load_force_godebug", func(t *testing.T) {
   235  		if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil {
   236  			t.Fatal(err)
   237  		}
   238  
   239  		installSystemRootCAs(t)
   240  
   241  		fallback := newFallbackCertPool(t)
   242  		SetFallbackRoots(fallback)
   243  		got, err := SystemCertPool()
   244  		if err != nil {
   245  			t.Fatal(err)
   246  		}
   247  		if !got.Equal(fallback) {
   248  			t.Fatal("SystemCertPool returned a non-fallback CertPool")
   249  		}
   250  	})
   251  
   252  	test(t, "after_first_load_force_godebug", func(t *testing.T) {
   253  		if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil {
   254  			t.Fatal(err)
   255  		}
   256  
   257  		installSystemRootCAs(t)
   258  		SystemCertPool() // load system certs, before setting fallbacks
   259  
   260  		fallback := newFallbackCertPool(t)
   261  		SetFallbackRoots(fallback)
   262  		got, err := SystemCertPool()
   263  		if err != nil {
   264  			t.Fatal(err)
   265  		}
   266  		if !got.Equal(fallback) {
   267  			t.Fatal("SystemCertPool returned a non-fallback CertPool")
   268  		}
   269  	})
   270  
   271  	test(t, "after_first_load_force_godebug_no_system_certs", func(t *testing.T) {
   272  		if err := os.Setenv("GODEBUG", "x509usefallbackroots=1"); err != nil {
   273  			t.Fatal(err)
   274  		}
   275  
   276  		SystemCertPool() // load system certs, before setting fallbacks
   277  
   278  		fallback := newFallbackCertPool(t)
   279  		SetFallbackRoots(fallback)
   280  		got, err := SystemCertPool()
   281  		if err != nil {
   282  			t.Fatal(err)
   283  		}
   284  		if !got.Equal(fallback) {
   285  			t.Fatal("SystemCertPool returned a non-fallback CertPool")
   286  		}
   287  	})
   288  }
   289  

View as plain text