Skip to content

Commit 3aea422

Browse files
rolandshoemakergopherbot
authored andcommitted
crypto/x509: use synthetic root for platform testing
Rather than using the external network and real-world chains for testing the integrations with platform verifiers, use a synthetic test root. This changes adds a constrained root and key pair to the tree, and adds a test suite that verifies certificates issued from that root. These tests are only executed if the root is detected in the trust store. For reference, the script used to generate the root and key is attached to the bottom of this commit message. This change leaves the existing windows/darwin TestPlatformVerifier tests in place, since the trybots do not currently have the test root in place, and as such cannot run the suite. Once the builder images have the root integrated, we can remove the old flaky tests, and the trybots will begin running the new suite automatically. Updates #52108 -- gen.go -- package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "flag" "log" "math/big" "net" "os" "time" ) func writePEM(pemType string, der []byte, path string) error { enc := pem.EncodeToMemory(&pem.Block{ Type: pemType, Bytes: der, }) return os.WriteFile(path, enc, 0666) } func main() { certPath := flag.String("cert-path", "platform_root_cert.pem", "Path to write certificate PEM") keyPath := flag.String("key-path", "platform_root_key.pem", "Path to write key PEM") flag.Parse() key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { log.Fatalf("ecdsa.GenerateKey failed: %s", err) } now := time.Now() tmpl := &x509.Certificate{ SerialNumber: big.NewInt(9009), Subject: pkix.Name{ CommonName: "Go platform verifier testing root", }, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour * 24 * 365 * 5), IsCA: true, BasicConstraintsValid: true, PermittedDNSDomainsCritical: true, // PermittedDNSDomains restricts the names in certificates issued from this root to *.testing.golang.invalid. // The .invalid TLD is, per RFC 2606, reserved for testing, and as such anything issued for this certificate // should never be valid in the real world. PermittedDNSDomains: []string{"testing.golang.invalid"}, // ExcludedIPRanges prevents any certificate issued from this root that contains an IP address in both the full // IPv4 and IPv6 ranges from being considered valid. ExcludedIPRanges: []*net.IPNet{{IP: make([]byte, 4), Mask: make([]byte, 4)}, {IP: make([]byte, 16), Mask: make([]byte, 16)}}, KeyUsage: x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, } certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) if err != nil { log.Fatalf("x509.CreateCertificate failed: %s", err) } keyDER, err := x509.MarshalECPrivateKey(key) if err != nil { log.Fatalf("x509.MarshalECPrivateKey failed: %s", err) } if err := writePEM("CERTIFICATE", certDER, *certPath); err != nil { log.Fatalf("failed to write certificate PEM: %s", err) } if err := writePEM("EC PRIVATE KEY", keyDER, *keyPath); err != nil { log.Fatalf("failed to write key PEM: %s", err) } } Change-Id: If7c4a9f18466662a60fea5443e603232a9260026 Reviewed-on: https://go-review.googlesource.com/c/go/+/488855 Reviewed-by: Filippo Valsorda <[email protected]> Auto-Submit: Roland Shoemaker <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Run-TryBot: Roland Shoemaker <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 0a48e5c commit 3aea422

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIB/DCCAaOgAwIBAgICIzEwCgYIKoZIzj0EAwIwLDEqMCgGA1UEAxMhR28gcGxh
3+
dGZvcm0gdmVyaWZpZXIgdGVzdGluZyByb290MB4XDTIzMDUyNjE3NDQwMVoXDTI4
4+
MDUyNDE4NDQwMVowLDEqMCgGA1UEAxMhR28gcGxhdGZvcm0gdmVyaWZpZXIgdGVz
5+
dGluZyByb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5dNQY4FY29i2g3xx
6+
7FyH4XiZz0C0AM4uyPUsXCZNb7CsctHDLhLtzABWSfFz76j+oVhq+qKrwIHsLX+7
7+
f6YTQqOBtDCBsTAOBgNVHQ8BAf8EBAMCAgQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
8+
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUEJInRbtQR6xTUSwvtdAe9A4XHwQw
9+
WgYDVR0eAQH/BFAwTqAaMBiCFnRlc3RpbmcuZ29sYW5nLmludmFsaWShMDAKhwgA
10+
AAAAAAAAADAihyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAKBggq
11+
hkjOPQQDAgNHADBEAiBgzgLyQm4rK1AuIcElH3MdRqlteq3nzZCxKOI4xHXYjQIg
12+
BCSzaCb1+/AK+mhRubrdebFYlUdveTH98wAfKQHaw64=
13+
-----END CERTIFICATE-----

src/crypto/x509/platform_root_key.pem

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIHhv8LVzb9gqJzAY0P442+FW0oqbfBrLnfqxyyAujOFSoAoGCCqGSM49
3+
AwEHoUQDQgAE5dNQY4FY29i2g3xx7FyH4XiZz0C0AM4uyPUsXCZNb7CsctHDLhLt
4+
zABWSfFz76j+oVhq+qKrwIHsLX+7f6YTQg==
5+
-----END EC PRIVATE KEY-----

src/crypto/x509/platform_test.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// Copyright 2023 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+
//go:generate go run gen_testing_root.go
8+
9+
import (
10+
"crypto/ecdsa"
11+
"crypto/elliptic"
12+
"crypto/rand"
13+
"encoding/pem"
14+
"math/big"
15+
"os"
16+
"runtime"
17+
"strings"
18+
"testing"
19+
"time"
20+
)
21+
22+
// In order to run this test suite locally, you need to insert the test root, at
23+
// the path below, into your trust store. This root is constrained such that it
24+
// should not be dangerous to local developers to trust, but care should be
25+
// taken when inserting it into the trust store not to give it increased
26+
// permissions.
27+
//
28+
// On macOS the certificate can be further constrained to only be valid for
29+
// 'SSL' in the certificate properties pane of the 'Keychain Access' program.
30+
//
31+
// On Windows the certificate can also be constrained to only server
32+
// authentication in the properties pane of the certificate in the
33+
// "Certificates" snap-in of mmc.exe.
34+
35+
const (
36+
rootCertPath = "platform_root_cert.pem"
37+
rootKeyPath = "platform_root_key.pem"
38+
)
39+
40+
func TestPlatformVerifier(t *testing.T) {
41+
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
42+
t.Skip("only tested on windows and darwin")
43+
}
44+
45+
der, err := os.ReadFile(rootCertPath)
46+
if err != nil {
47+
t.Fatalf("failed to read test root: %s", err)
48+
}
49+
b, _ := pem.Decode(der)
50+
testRoot, err := ParseCertificate(b.Bytes)
51+
if err != nil {
52+
t.Fatalf("failed to parse test root: %s", err)
53+
}
54+
55+
der, err = os.ReadFile(rootKeyPath)
56+
if err != nil {
57+
t.Fatalf("failed to read test key: %s", err)
58+
}
59+
b, _ = pem.Decode(der)
60+
testRootKey, err := ParseECPrivateKey(b.Bytes)
61+
if err != nil {
62+
t.Fatalf("failed to parse test key: %s", err)
63+
}
64+
65+
if _, err := testRoot.Verify(VerifyOptions{}); err != nil {
66+
t.Skipf("test root is not in trust store, skipping (err: %q)", err)
67+
}
68+
69+
now := time.Now()
70+
71+
tests := []struct {
72+
name string
73+
cert *Certificate
74+
selfSigned bool
75+
dnsName string
76+
time time.Time
77+
eku []ExtKeyUsage
78+
79+
expectedErr string
80+
windowsErr string
81+
macosErr string
82+
}{
83+
{
84+
name: "valid",
85+
cert: &Certificate{
86+
SerialNumber: big.NewInt(1),
87+
DNSNames: []string{"valid.testing.golang.invalid"},
88+
NotBefore: now.Add(-time.Hour),
89+
NotAfter: now.Add(time.Hour),
90+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
91+
},
92+
},
93+
{
94+
name: "valid (with name)",
95+
cert: &Certificate{
96+
SerialNumber: big.NewInt(1),
97+
DNSNames: []string{"valid.testing.golang.invalid"},
98+
NotBefore: now.Add(-time.Hour),
99+
NotAfter: now.Add(time.Hour),
100+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
101+
},
102+
dnsName: "valid.testing.golang.invalid",
103+
},
104+
{
105+
name: "valid (with time)",
106+
cert: &Certificate{
107+
SerialNumber: big.NewInt(1),
108+
DNSNames: []string{"valid.testing.golang.invalid"},
109+
NotBefore: now.Add(-time.Hour),
110+
NotAfter: now.Add(time.Hour),
111+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
112+
},
113+
time: now.Add(time.Minute * 30),
114+
},
115+
{
116+
name: "valid (with eku)",
117+
cert: &Certificate{
118+
SerialNumber: big.NewInt(1),
119+
DNSNames: []string{"valid.testing.golang.invalid"},
120+
NotBefore: now.Add(-time.Hour),
121+
NotAfter: now.Add(time.Hour),
122+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
123+
},
124+
eku: []ExtKeyUsage{ExtKeyUsageServerAuth},
125+
},
126+
{
127+
name: "wrong name",
128+
cert: &Certificate{
129+
SerialNumber: big.NewInt(1),
130+
DNSNames: []string{"valid.testing.golang.invalid"},
131+
NotBefore: now.Add(-time.Hour),
132+
NotAfter: now.Add(time.Hour),
133+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
134+
},
135+
dnsName: "invalid.testing.golang.invalid",
136+
expectedErr: "x509: certificate is valid for valid.testing.golang.invalid, not invalid.testing.golang.invalid",
137+
},
138+
{
139+
name: "expired (future)",
140+
cert: &Certificate{
141+
SerialNumber: big.NewInt(1),
142+
DNSNames: []string{"valid.testing.golang.invalid"},
143+
NotBefore: now.Add(-time.Hour),
144+
NotAfter: now.Add(time.Hour),
145+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
146+
},
147+
time: now.Add(time.Hour * 2),
148+
expectedErr: "x509: certificate has expired or is not yet valid",
149+
},
150+
{
151+
name: "expired (past)",
152+
cert: &Certificate{
153+
SerialNumber: big.NewInt(1),
154+
DNSNames: []string{"valid.testing.golang.invalid"},
155+
NotBefore: now.Add(-time.Hour),
156+
NotAfter: now.Add(time.Hour),
157+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
158+
},
159+
time: now.Add(time.Hour * 2),
160+
expectedErr: "x509: certificate has expired or is not yet valid",
161+
},
162+
{
163+
name: "self-signed",
164+
cert: &Certificate{
165+
SerialNumber: big.NewInt(1),
166+
DNSNames: []string{"valid.testing.golang.invalid"},
167+
NotBefore: now.Add(-time.Hour),
168+
NotAfter: now.Add(time.Hour),
169+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
170+
},
171+
selfSigned: true,
172+
macosErr: "x509: “valid.testing.golang.invalid” certificate is not trusted",
173+
windowsErr: "x509: certificate signed by unknown authority",
174+
},
175+
{
176+
name: "non-specified KU",
177+
cert: &Certificate{
178+
SerialNumber: big.NewInt(1),
179+
DNSNames: []string{"valid.testing.golang.invalid"},
180+
NotBefore: now.Add(-time.Hour),
181+
NotAfter: now.Add(time.Hour),
182+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
183+
},
184+
eku: []ExtKeyUsage{ExtKeyUsageEmailProtection},
185+
expectedErr: "x509: certificate specifies an incompatible key usage",
186+
},
187+
{
188+
name: "non-nested KU",
189+
cert: &Certificate{
190+
SerialNumber: big.NewInt(1),
191+
DNSNames: []string{"valid.testing.golang.invalid"},
192+
NotBefore: now.Add(-time.Hour),
193+
NotAfter: now.Add(time.Hour),
194+
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageEmailProtection},
195+
},
196+
macosErr: "x509: “valid.testing.golang.invalid” certificate is not permitted for this usage",
197+
windowsErr: "x509: certificate specifies an incompatible key usage",
198+
},
199+
}
200+
201+
leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
202+
if err != nil {
203+
t.Fatalf("ecdsa.GenerateKey failed: %s", err)
204+
}
205+
206+
for _, tc := range tests {
207+
tc := tc
208+
t.Run(tc.name, func(t *testing.T) {
209+
t.Parallel()
210+
parent := testRoot
211+
if tc.selfSigned {
212+
parent = tc.cert
213+
}
214+
certDER, err := CreateCertificate(rand.Reader, tc.cert, parent, leafKey.Public(), testRootKey)
215+
if err != nil {
216+
t.Fatalf("CreateCertificate failed: %s", err)
217+
}
218+
cert, err := ParseCertificate(certDER)
219+
if err != nil {
220+
t.Fatalf("ParseCertificate failed: %s", err)
221+
}
222+
223+
var opts VerifyOptions
224+
if tc.dnsName != "" {
225+
opts.DNSName = tc.dnsName
226+
}
227+
if !tc.time.IsZero() {
228+
opts.CurrentTime = tc.time
229+
}
230+
if len(tc.eku) > 0 {
231+
opts.KeyUsages = tc.eku
232+
}
233+
234+
expectedErr := tc.expectedErr
235+
if runtime.GOOS == "darwin" && tc.macosErr != "" {
236+
expectedErr = tc.macosErr
237+
} else if runtime.GOOS == "windows" && tc.windowsErr != "" {
238+
expectedErr = tc.windowsErr
239+
}
240+
241+
_, err = cert.Verify(opts)
242+
if err != nil && expectedErr == "" {
243+
t.Errorf("unexpected verification error: %s", err)
244+
} else if err != nil && !strings.HasPrefix(err.Error(), expectedErr) {
245+
t.Errorf("unexpected verification error: got %q, want %q", err.Error(), expectedErr)
246+
} else if err == nil && expectedErr != "" {
247+
t.Errorf("unexpected verification success: want %q", expectedErr)
248+
}
249+
})
250+
}
251+
}

0 commit comments

Comments
 (0)