Source file src/crypto/x509/parser_test.go

     1  // Copyright 2021 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/asn1"
    12  	"encoding/pem"
    13  	"os"
    14  	"strings"
    15  	"testing"
    16  
    17  	cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
    18  )
    19  
    20  func TestParseASN1String(t *testing.T) {
    21  	tests := []struct {
    22  		name        string
    23  		tag         cryptobyte_asn1.Tag
    24  		value       []byte
    25  		expected    string
    26  		expectedErr string
    27  	}{
    28  		{
    29  			name:     "T61String",
    30  			tag:      cryptobyte_asn1.T61String,
    31  			value:    []byte{0xbf, 0x61, 0x3f},
    32  			expected: string("¿a?"),
    33  		},
    34  		{
    35  			name:     "PrintableString",
    36  			tag:      cryptobyte_asn1.PrintableString,
    37  			value:    []byte{80, 81, 82},
    38  			expected: string("PQR"),
    39  		},
    40  		{
    41  			name:        "PrintableString (invalid)",
    42  			tag:         cryptobyte_asn1.PrintableString,
    43  			value:       []byte{1, 2, 3},
    44  			expectedErr: "invalid PrintableString",
    45  		},
    46  		{
    47  			name:     "UTF8String",
    48  			tag:      cryptobyte_asn1.UTF8String,
    49  			value:    []byte{80, 81, 82},
    50  			expected: string("PQR"),
    51  		},
    52  		{
    53  			name:        "UTF8String (invalid)",
    54  			tag:         cryptobyte_asn1.UTF8String,
    55  			value:       []byte{255},
    56  			expectedErr: "invalid UTF-8 string",
    57  		},
    58  		{
    59  			name:     "BMPString",
    60  			tag:      cryptobyte_asn1.Tag(asn1.TagBMPString),
    61  			value:    []byte{80, 81},
    62  			expected: string("偑"),
    63  		},
    64  		{
    65  			name:        "BMPString (invalid length)",
    66  			tag:         cryptobyte_asn1.Tag(asn1.TagBMPString),
    67  			value:       []byte{255},
    68  			expectedErr: "invalid BMPString",
    69  		},
    70  		{
    71  			name:        "BMPString (invalid surrogate)",
    72  			tag:         cryptobyte_asn1.Tag(asn1.TagBMPString),
    73  			value:       []byte{80, 81, 216, 1},
    74  			expectedErr: "invalid BMPString",
    75  		},
    76  		{
    77  			name:        "BMPString (invalid noncharacter 0xfdd1)",
    78  			tag:         cryptobyte_asn1.Tag(asn1.TagBMPString),
    79  			value:       []byte{80, 81, 253, 209},
    80  			expectedErr: "invalid BMPString",
    81  		},
    82  		{
    83  			name:        "BMPString (invalid noncharacter 0xffff)",
    84  			tag:         cryptobyte_asn1.Tag(asn1.TagBMPString),
    85  			value:       []byte{80, 81, 255, 255},
    86  			expectedErr: "invalid BMPString",
    87  		},
    88  		{
    89  			name:        "BMPString (invalid noncharacter 0xfffe)",
    90  			tag:         cryptobyte_asn1.Tag(asn1.TagBMPString),
    91  			value:       []byte{80, 81, 255, 254},
    92  			expectedErr: "invalid BMPString",
    93  		},
    94  		{
    95  			name:     "IA5String",
    96  			tag:      cryptobyte_asn1.IA5String,
    97  			value:    []byte{80, 81},
    98  			expected: string("PQ"),
    99  		},
   100  		{
   101  			name:        "IA5String (invalid)",
   102  			tag:         cryptobyte_asn1.IA5String,
   103  			value:       []byte{255},
   104  			expectedErr: "invalid IA5String",
   105  		},
   106  		{
   107  			name:     "NumericString",
   108  			tag:      cryptobyte_asn1.Tag(asn1.TagNumericString),
   109  			value:    []byte{49, 50},
   110  			expected: string("12"),
   111  		},
   112  		{
   113  			name:        "NumericString (invalid)",
   114  			tag:         cryptobyte_asn1.Tag(asn1.TagNumericString),
   115  			value:       []byte{80},
   116  			expectedErr: "invalid NumericString",
   117  		},
   118  	}
   119  
   120  	for _, tc := range tests {
   121  		t.Run(tc.name, func(t *testing.T) {
   122  			out, err := parseASN1String(tc.tag, tc.value)
   123  			if err != nil && err.Error() != tc.expectedErr {
   124  				t.Fatalf("parseASN1String returned unexpected error: got %q, want %q", err, tc.expectedErr)
   125  			} else if err == nil && tc.expectedErr != "" {
   126  				t.Fatalf("parseASN1String didn't fail, expected: %s", tc.expectedErr)
   127  			}
   128  			if out != tc.expected {
   129  				t.Fatalf("parseASN1String returned unexpected value: got %q, want %q", out, tc.expected)
   130  			}
   131  		})
   132  	}
   133  }
   134  
   135  const policyPEM = `-----BEGIN CERTIFICATE-----
   136  MIIGeDCCBWCgAwIBAgIUED9KQBi0ScBDoufB2mgAJ63G5uIwDQYJKoZIhvcNAQEL
   137  BQAwVTELMAkGA1UEBhMCVVMxGDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDENMAsG
   138  A1UECxMERlBLSTEdMBsGA1UEAxMURmVkZXJhbCBCcmlkZ2UgQ0EgRzQwHhcNMjAx
   139  MDIyMTcwNDE5WhcNMjMxMDIyMTcwNDE5WjCBgTELMAkGA1UEBhMCVVMxHTAbBgNV
   140  BAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVz
   141  dCBOZXR3b3JrMTIwMAYDVQQDEylTeW1hbnRlYyBDbGFzcyAzIFNTUCBJbnRlcm1l
   142  ZGlhdGUgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2p
   143  75cMpx86sS2aH4r+0o8r+m/KTrPrknWP0RA9Kp6sewAzkNa7BVwg0jOhyamiv1iP
   144  Cns10usoH93nxYbXLWF54vOLRdYU/53KEPNmgkj2ipMaTLuaReBghNibikWSnAmy
   145  S8RItaDMs8tdF2goKPI4xWiamNwqe92VC+pic2tq0Nva3Y4kvMDJjtyje3uduTtL
   146  oyoaaHkrX7i7gE67psnMKj1THUtre1JV1ohl9+oOuyot4p3eSxVlrMWiiwb11bnk
   147  CakecOz/mP2DHMGg6pZ/BeJ+ThaLUylAXECARIqHc9UwRPKC9BfLaCX4edIoeYiB
   148  loRs4KdqLdg/I9eTwKkCAwEAAaOCAxEwggMNMB0GA1UdDgQWBBQ1Jn1QleGhwb0F
   149  1cOdd0LHDBOWjDAfBgNVHSMEGDAWgBR58ABJ6393wl1BAmU0ipAjmx4HbzAOBgNV
   150  HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zCBiAYDVR0gBIGAMH4wDAYKYIZI
   151  AWUDAgEDAzAMBgpghkgBZQMCAQMMMAwGCmCGSAFlAwIBAw4wDAYKYIZIAWUDAgED
   152  DzAMBgpghkgBZQMCAQMSMAwGCmCGSAFlAwIBAxMwDAYKYIZIAWUDAgEDFDAMBgpg
   153  hkgBZQMCAQMlMAwGCmCGSAFlAwIBAyYwggESBgNVHSEEggEJMIIBBTAbBgpghkgB
   154  ZQMCAQMDBg1ghkgBhvhFAQcXAwEGMBsGCmCGSAFlAwIBAwwGDWCGSAGG+EUBBxcD
   155  AQcwGwYKYIZIAWUDAgEDDgYNYIZIAYb4RQEHFwMBDjAbBgpghkgBZQMCAQMPBg1g
   156  hkgBhvhFAQcXAwEPMBsGCmCGSAFlAwIBAxIGDWCGSAGG+EUBBxcDARIwGwYKYIZI
   157  AWUDAgEDEwYNYIZIAYb4RQEHFwMBETAbBgpghkgBZQMCAQMUBg1ghkgBhvhFAQcX
   158  AwEUMBsGCmCGSAFlAwIBAyUGDWCGSAGG+EUBBxcDAQgwGwYKYIZIAWUDAgEDJgYN
   159  YIZIAYb4RQEHFwMBJDBgBggrBgEFBQcBCwRUMFIwUAYIKwYBBQUHMAWGRGh0dHA6
   160  Ly9zc3Atc2lhLnN5bWF1dGguY29tL1NUTlNTUC9DZXJ0c19Jc3N1ZWRfYnlfQ2xh
   161  c3MzU1NQQ0EtRzMucDdjMA8GA1UdJAQIMAaAAQCBAQAwCgYDVR02BAMCAQAwUQYI
   162  KwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVodHRwOi8vcmVwby5mcGtpLmdvdi9i
   163  cmlkZ2UvY2FDZXJ0c0lzc3VlZFRvZmJjYWc0LnA3YzA3BgNVHR8EMDAuMCygKqAo
   164  hiZodHRwOi8vcmVwby5mcGtpLmdvdi9icmlkZ2UvZmJjYWc0LmNybDANBgkqhkiG
   165  9w0BAQsFAAOCAQEAA751TycC1f/WTkHmedF9ZWxP58Jstmwvkyo8bKueJ0eF7LTG
   166  BgQlzE2B9vke4sFhd4V+BdgOPGE1dsGzllYKCWg0BhkCBs5kIJ7F6Ay6G1TBuGU1
   167  Ie8247GL+P9pcC5TVvXHC/62R2w3DuD/vAPLbYEbSQjobXlsqt8Kmtd6yK/jVuDV
   168  BTZMdZmvoNtjemqmgcBXHsf0ctVm0m6tH5uYqyVxu8tfyUis6Cf303PHj+spWP1k
   169  gc5PYnVF0ot7qAmNFENIpbKg3BdusBkF9rGxLaDSUBvSc7+s9iQz9d/iRuAebrYu
   170  +eqUlJ2lsjS1U8qyPmlH+spfPNbAEQEsuP32Aw==
   171  -----END CERTIFICATE-----
   172  `
   173  
   174  func TestPolicyParse(t *testing.T) {
   175  	b, _ := pem.Decode([]byte(policyPEM))
   176  	c, err := ParseCertificate(b.Bytes)
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	if len(c.Policies) != 9 {
   181  		t.Errorf("unexpected number of policies: got %d, want %d", len(c.Policies), 9)
   182  	}
   183  	if len(c.PolicyMappings) != 9 {
   184  		t.Errorf("unexpected number of policy mappings: got %d, want %d", len(c.PolicyMappings), 9)
   185  	}
   186  	if !c.RequireExplicitPolicyZero {
   187  		t.Error("expected RequireExplicitPolicyZero to be set")
   188  	}
   189  	if !c.InhibitPolicyMappingZero {
   190  		t.Error("expected InhibitPolicyMappingZero to be set")
   191  	}
   192  	if !c.InhibitAnyPolicyZero {
   193  		t.Error("expected InhibitAnyPolicyZero to be set")
   194  	}
   195  }
   196  
   197  func TestParsePolicies(t *testing.T) {
   198  	for _, tc := range []string{
   199  		"testdata/policy_leaf_duplicate.pem",
   200  		"testdata/policy_leaf_invalid.pem",
   201  	} {
   202  		t.Run(tc, func(t *testing.T) {
   203  			b, err := os.ReadFile(tc)
   204  			if err != nil {
   205  				t.Fatal(err)
   206  			}
   207  			p, _ := pem.Decode(b)
   208  			_, err = ParseCertificate(p.Bytes)
   209  			if err == nil {
   210  				t.Error("parsing should've failed")
   211  			}
   212  		})
   213  	}
   214  }
   215  
   216  func TestParseCertificateNegativeMaxPathLength(t *testing.T) {
   217  	certs := []string{
   218  		// Certificate with MaxPathLen set to -1.
   219  		`
   220  -----BEGIN CERTIFICATE-----
   221  MIIByTCCATKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRURVNU
   222  MB4XDTcwMDEwMTAwMTY0MFoXDTcwMDEwMjAzNDY0MFowDzENMAsGA1UEAxMEVEVT
   223  VDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsaHglFuSicTT8TKfipgsSi3N
   224  Wb/TcvuAhanFF1VGB+vS95kO7yFqyfRgX3GgOwT0KlJVsVjPjghEGR9RGTSLqkTD
   225  UFbiBgm8+VEPMOrUtIHIHXhl+ye44AkOEStxfz7gjN/EAS2h8ffPKhvDTHOlShKw
   226  Y3LQlxR0LdeJXq3eSqUCAwEAAaM1MDMwEgYDVR0TAQH/BAgwBgEB/wIB/zAdBgNV
   227  HQ4EFgQUrbrk0tqQAEsce8uYifP0BIVhuFAwDQYJKoZIhvcNAQELBQADgYEAIkhV
   228  ZBj1ThT+eyh50XsoU570NUysTg3Nj/3lbkEolzdcE+wu0CPXvgxLRM6Y62u1ey82
   229  8d5VQHstzF4dXgc3W+O9UySa+CKdcHx/q7o7seOGXdysT0IJtAY3w66mFkuF7PIn
   230  y9b7M5t6pmWjb7N0QqGuWeNqi4ZvS8gLKmVEgGY=
   231  -----END CERTIFICATE-----
   232  `,
   233  		// Certificate with MaxPathLen set to -2.
   234  		`
   235  -----BEGIN CERTIFICATE-----
   236  MIIByTCCATKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRURVNU
   237  MB4XDTcwMDEwMTAwMTY0MFoXDTcwMDEwMjAzNDY0MFowDzENMAsGA1UEAxMEVEVT
   238  VDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsaHglFuSicTT8TKfipgsSi3N
   239  Wb/TcvuAhanFF1VGB+vS95kO7yFqyfRgX3GgOwT0KlJVsVjPjghEGR9RGTSLqkTD
   240  UFbiBgm8+VEPMOrUtIHIHXhl+ye44AkOEStxfz7gjN/EAS2h8ffPKhvDTHOlShKw
   241  Y3LQlxR0LdeJXq3eSqUCAwEAAaM1MDMwEgYDVR0TAQH/BAgwBgEB/wIB/jAdBgNV
   242  HQ4EFgQUrbrk0tqQAEsce8uYifP0BIVhuFAwDQYJKoZIhvcNAQELBQADgYEAGjIr
   243  YGQc7Ods+BuKck7p+vpAMONM8SLEuUtKorCP3ecsO51MoA4/niLbgMHaOGNHwzMp
   244  ajg0zLbY0Dj6Ml0VZ+lS3rjgTEhYXc626eZkoQqgUzL1jhe3S0ZbSxxmHMBKjJFl
   245  d5l1tRhScKu2NBgm74nYmJxJYgvuTA38wGhRrGU=
   246  -----END CERTIFICATE-----
   247  `,
   248  	}
   249  
   250  	for _, cert := range certs {
   251  		b, _ := pem.Decode([]byte(cert))
   252  		_, err := ParseCertificate(b.Bytes)
   253  		if err == nil || err.Error() != "x509: invalid basic constraints" {
   254  			t.Errorf(`ParseCertificate() = %v; want = "x509: invalid basic constraints"`, err)
   255  		}
   256  	}
   257  }
   258  
   259  func TestDomainNameValid(t *testing.T) {
   260  	for _, tc := range []struct {
   261  		name       string
   262  		dnsName    string
   263  		constraint bool
   264  		valid      bool
   265  	}{
   266  		// TODO(#75835): these tests are for stricter name validation, which we
   267  		// had to disable. Once we reenable these strict checks, behind a
   268  		// GODEBUG, we should add them back in.
   269  		// {"empty name, name", "", false, false},
   270  		// {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false},
   271  		// {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false},
   272  		// {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false},
   273  		// {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false},
   274  		// {"64 char single label, name", strings.Repeat("a", 64), false, false},
   275  		// {"64 char single label, constraint", strings.Repeat("a", 64), true, false},
   276  		// {"64 char label, name", "a." + strings.Repeat("a", 64), false, false},
   277  		// {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false},
   278  
   279  		// TODO(#75835): these are the inverse of the tests above, they should be removed
   280  		// once the strict checking is enabled.
   281  		{"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, true},
   282  		{"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, true},
   283  		{"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, true},
   284  		{"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, true},
   285  		{"64 char single label, name", strings.Repeat("a", 64), false, true},
   286  		{"64 char single label, constraint", strings.Repeat("a", 64), true, true},
   287  		{"64 char label, name", "a." + strings.Repeat("a", 64), false, true},
   288  		{"64 char label, constraint", "a." + strings.Repeat("a", 64), true, true},
   289  
   290  		// Check we properly enforce properties of domain names.
   291  		{"empty name, constraint", "", true, true},
   292  		{"empty label, name", "a..a", false, false},
   293  		{"empty label, constraint", "a..a", true, false},
   294  		{"period, name", ".", false, false},
   295  		{"period, constraint", ".", true, false}, // TODO(roland): not entirely clear if this is a valid constraint (require at least one label?)
   296  		{"valid, name", "a.b.c", false, true},
   297  		{"valid, constraint", "a.b.c", true, true},
   298  		{"leading period, name", ".a.b.c", false, false},
   299  		{"leading period, constraint", ".a.b.c", true, true},
   300  		{"trailing period, name", "a.", false, false},
   301  		{"trailing period, constraint", "a.", true, false},
   302  		{"bare label, name", "a", false, true},
   303  		{"bare label, constraint", "a", true, true},
   304  		{"63 char single label, name", strings.Repeat("a", 63), false, true},
   305  		{"63 char single label, constraint", strings.Repeat("a", 63), true, true},
   306  		{"63 char label, name", "a." + strings.Repeat("a", 63), false, true},
   307  		{"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true},
   308  	} {
   309  		t.Run(tc.name, func(t *testing.T) {
   310  			valid := domainNameValid(tc.dnsName, tc.constraint)
   311  			if tc.valid != valid {
   312  				t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid)
   313  			}
   314  			// Also check that we enforce the same properties as domainToReverseLabels
   315  			trimmedName := tc.dnsName
   316  			if tc.constraint && len(trimmedName) > 1 && trimmedName[0] == '.' {
   317  				trimmedName = trimmedName[1:]
   318  			}
   319  			_, revValid := domainToReverseLabels(trimmedName)
   320  			if valid != revValid {
   321  				t.Errorf("domainNameValid(%q, %t) = %t != domainToReverseLabels(%q) = %t", tc.dnsName, tc.constraint, valid, trimmedName, revValid)
   322  			}
   323  		})
   324  	}
   325  }
   326  
   327  func TestRoundtripWeirdSANs(t *testing.T) {
   328  	// TODO(#75835): check that certificates we create with CreateCertificate that have malformed SAN values
   329  	// can be parsed by ParseCertificate. We should eventually restrict this, but for now we have to maintain
   330  	// this property as people have been relying on it.
   331  	k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  	badNames := []string{
   336  		"baredomain",
   337  		"baredomain.",
   338  		strings.Repeat("a", 255),
   339  		strings.Repeat("a", 65) + ".com",
   340  	}
   341  	tmpl := &Certificate{
   342  		EmailAddresses: badNames,
   343  		DNSNames:       badNames,
   344  	}
   345  	b, err := CreateCertificate(rand.Reader, tmpl, tmpl, &k.PublicKey, k)
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	_, err = ParseCertificate(b)
   350  	if err != nil {
   351  		t.Fatalf("Couldn't roundtrip certificate: %v", err)
   352  	}
   353  }
   354  
   355  func FuzzDomainNameValid(f *testing.F) {
   356  	f.Fuzz(func(t *testing.T, data string) {
   357  		domainNameValid(data, false)
   358  		domainNameValid(data, true)
   359  	})
   360  }
   361  

View as plain text