// Copyright 2023 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. package x509 //go:generate go run gen_testing_root.go import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/pem" "math/big" "os" "runtime" "strings" "testing" "time" ) // In order to run this test suite locally, you need to insert the test root, at // the path below, into your trust store. This root is constrained such that it // should not be dangerous to local developers to trust, but care should be // taken when inserting it into the trust store not to give it increased // permissions. // // On macOS the certificate can be further constrained to only be valid for // 'SSL' in the certificate properties pane of the 'Keychain Access' program. // // On Windows the certificate can also be constrained to only server // authentication in the properties pane of the certificate in the // "Certificates" snap-in of mmc.exe. const ( rootCertPath = "platform_root_cert.pem" rootKeyPath = "platform_root_key.pem" ) func TestPlatformVerifier(t *testing.T) { if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { t.Skip("only tested on windows and darwin") } der, err := os.ReadFile(rootCertPath) if err != nil { t.Fatalf("failed to read test root: %s", err) } b, _ := pem.Decode(der) testRoot, err := ParseCertificate(b.Bytes) if err != nil { t.Fatalf("failed to parse test root: %s", err) } der, err = os.ReadFile(rootKeyPath) if err != nil { t.Fatalf("failed to read test key: %s", err) } b, _ = pem.Decode(der) testRootKey, err := ParseECPrivateKey(b.Bytes) if err != nil { t.Fatalf("failed to parse test key: %s", err) } if _, err := testRoot.Verify(VerifyOptions{}); err != nil { t.Skipf("test root is not in trust store, skipping (err: %q)", err) } now := time.Now() tests := []struct { name string cert *Certificate selfSigned bool dnsName string time time.Time eku []ExtKeyUsage expectedErr string windowsErr string macosErr string }{ { name: "valid", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, }, { name: "valid (with name)", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, dnsName: "valid.testing.golang.invalid", }, { name: "valid (with time)", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, time: now.Add(time.Minute * 30), }, { name: "valid (with eku)", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, eku: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, { name: "wrong name", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, dnsName: "invalid.testing.golang.invalid", expectedErr: "x509: certificate is valid for valid.testing.golang.invalid, not invalid.testing.golang.invalid", }, { name: "expired (future)", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, time: now.Add(time.Hour * 2), expectedErr: "x509: certificate has expired or is not yet valid", }, { name: "expired (past)", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, time: now.Add(time.Hour * 2), expectedErr: "x509: certificate has expired or is not yet valid", }, { name: "self-signed", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, selfSigned: true, macosErr: "x509: “valid.testing.golang.invalid” certificate is not trusted", windowsErr: "x509: certificate signed by unknown authority", }, { name: "non-specified KU", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, }, eku: []ExtKeyUsage{ExtKeyUsageEmailProtection}, expectedErr: "x509: certificate specifies an incompatible key usage", }, { name: "non-nested KU", cert: &Certificate{ SerialNumber: big.NewInt(1), DNSNames: []string{"valid.testing.golang.invalid"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageEmailProtection}, }, macosErr: "x509: “valid.testing.golang.invalid” certificate is not permitted for this usage", windowsErr: "x509: certificate specifies an incompatible key usage", }, } leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("ecdsa.GenerateKey failed: %s", err) } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() parent := testRoot if tc.selfSigned { parent = tc.cert } certDER, err := CreateCertificate(rand.Reader, tc.cert, parent, leafKey.Public(), testRootKey) if err != nil { t.Fatalf("CreateCertificate failed: %s", err) } cert, err := ParseCertificate(certDER) if err != nil { t.Fatalf("ParseCertificate failed: %s", err) } var opts VerifyOptions if tc.dnsName != "" { opts.DNSName = tc.dnsName } if !tc.time.IsZero() { opts.CurrentTime = tc.time } if len(tc.eku) > 0 { opts.KeyUsages = tc.eku } expectedErr := tc.expectedErr if runtime.GOOS == "darwin" && tc.macosErr != "" { expectedErr = tc.macosErr } else if runtime.GOOS == "windows" && tc.windowsErr != "" { expectedErr = tc.windowsErr } _, err = cert.Verify(opts) if err != nil && expectedErr == "" { t.Errorf("unexpected verification error: %s", err) } else if err != nil && !strings.HasPrefix(err.Error(), expectedErr) { t.Errorf("unexpected verification error: got %q, want %q", err.Error(), expectedErr) } else if err == nil && expectedErr != "" { t.Errorf("unexpected verification success: want %q", expectedErr) } }) } }