blob: b425e02f3bc144ee00a82c0112d0472e43264cd0 [file] [log] [blame]
// 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
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)
}
})
}
}