Source file src/crypto/x509/platform_test.go

     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  }
   252  

View as plain text