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

View as plain text