1// Copyright 2011 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_test
6
7import (
8	"crypto/ecdsa"
9	"crypto/elliptic"
10	"crypto/rand"
11	"crypto/tls"
12	"crypto/x509"
13	"crypto/x509/pkix"
14	"internal/testenv"
15	"math/big"
16	"runtime"
17	"testing"
18	"time"
19)
20
21func TestHybridPool(t *testing.T) {
22	t.Parallel()
23	if !(runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "ios") {
24		t.Skipf("platform verifier not available on %s", runtime.GOOS)
25	}
26	if !testenv.HasExternalNetwork() {
27		t.Skip()
28	}
29	if runtime.GOOS == "windows" {
30		// NOTE(#51599): on the Windows builders we sometimes see that the state
31		// of the root pool is not fully initialized, causing an expected
32		// platform verification to fail. In part this is because Windows
33		// dynamically populates roots into its local trust store at time of
34		// use. We can attempt to prime the pool by attempting TLS connections
35		// to google.com until it works, suggesting the pool has been properly
36		// updated. If after we hit the deadline, the pool has _still_ not been
37		// populated with the expected root, it's unlikely we are ever going to
38		// get into a good state, and so we just fail the test. #52108 suggests
39		// a better possible long term solution.
40
41		deadline := time.Now().Add(time.Second * 10)
42		nextSleep := 10 * time.Millisecond
43		for i := 0; ; i++ {
44			c, err := tls.Dial("tcp", "google.com:443", nil)
45			if err == nil {
46				c.Close()
47				break
48			}
49			nextSleep = nextSleep * time.Duration(i)
50			if time.Until(deadline) < nextSleep {
51				t.Fatal("windows root pool appears to be in an uninitialized state (missing root that chains to google.com)")
52			}
53			time.Sleep(nextSleep)
54		}
55	}
56
57	// Get the google.com chain, which should be valid on all platforms we
58	// are testing
59	c, err := tls.Dial("tcp", "google.com:443", &tls.Config{InsecureSkipVerify: true})
60	if err != nil {
61		t.Fatalf("tls connection failed: %s", err)
62	}
63	googChain := c.ConnectionState().PeerCertificates
64
65	rootTmpl := &x509.Certificate{
66		SerialNumber:          big.NewInt(1),
67		Subject:               pkix.Name{CommonName: "Go test root"},
68		IsCA:                  true,
69		BasicConstraintsValid: true,
70		NotBefore:             time.Now().Add(-time.Hour),
71		NotAfter:              time.Now().Add(time.Hour * 10),
72	}
73	k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
74	if err != nil {
75		t.Fatalf("failed to generate test key: %s", err)
76	}
77	rootDER, err := x509.CreateCertificate(rand.Reader, rootTmpl, rootTmpl, k.Public(), k)
78	if err != nil {
79		t.Fatalf("failed to create test cert: %s", err)
80	}
81	root, err := x509.ParseCertificate(rootDER)
82	if err != nil {
83		t.Fatalf("failed to parse test cert: %s", err)
84	}
85
86	pool, err := x509.SystemCertPool()
87	if err != nil {
88		t.Fatalf("SystemCertPool failed: %s", err)
89	}
90	opts := x509.VerifyOptions{Roots: pool}
91
92	_, err = googChain[0].Verify(opts)
93	if err != nil {
94		t.Fatalf("verification failed for google.com chain (system only pool): %s", err)
95	}
96
97	pool.AddCert(root)
98
99	_, err = googChain[0].Verify(opts)
100	if err != nil {
101		t.Fatalf("verification failed for google.com chain (hybrid pool): %s", err)
102	}
103
104	certTmpl := &x509.Certificate{
105		SerialNumber: big.NewInt(1),
106		NotBefore:    time.Now().Add(-time.Hour),
107		NotAfter:     time.Now().Add(time.Hour * 10),
108		DNSNames:     []string{"example.com"},
109	}
110	certDER, err := x509.CreateCertificate(rand.Reader, certTmpl, rootTmpl, k.Public(), k)
111	if err != nil {
112		t.Fatalf("failed to create test cert: %s", err)
113	}
114	cert, err := x509.ParseCertificate(certDER)
115	if err != nil {
116		t.Fatalf("failed to parse test cert: %s", err)
117	}
118
119	_, err = cert.Verify(opts)
120	if err != nil {
121		t.Fatalf("verification failed for custom chain (hybrid pool): %s", err)
122	}
123}
124