| // Copyright 2021 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 ( | |
| "bytes" | |
| "crypto/ecdsa" | |
| "crypto/elliptic" | |
| "crypto/rand" | |
| "crypto/x509/pkix" | |
| "encoding/asn1" | |
| "encoding/pem" | |
| "os" | |
| "reflect" | |
| "strings" | |
| "testing" | |
| "time" | |
| cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" | |
| ) | |
| func TestParseASN1String(t *testing.T) { | |
| tests := []struct { | |
| name string | |
| tag cryptobyte_asn1.Tag | |
| value []byte | |
| expected string | |
| expectedErr string | |
| }{ | |
| { | |
| name: "T61String", | |
| tag: cryptobyte_asn1.T61String, | |
| value: []byte{0xbf, 0x61, 0x3f}, | |
| expected: string("¿a?"), | |
| }, | |
| { | |
| name: "PrintableString", | |
| tag: cryptobyte_asn1.PrintableString, | |
| value: []byte{80, 81, 82}, | |
| expected: string("PQR"), | |
| }, | |
| { | |
| name: "PrintableString (invalid)", | |
| tag: cryptobyte_asn1.PrintableString, | |
| value: []byte{1, 2, 3}, | |
| expectedErr: "invalid PrintableString", | |
| }, | |
| { | |
| name: "UTF8String", | |
| tag: cryptobyte_asn1.UTF8String, | |
| value: []byte{80, 81, 82}, | |
| expected: string("PQR"), | |
| }, | |
| { | |
| name: "UTF8String (invalid)", | |
| tag: cryptobyte_asn1.UTF8String, | |
| value: []byte{255}, | |
| expectedErr: "invalid UTF-8 string", | |
| }, | |
| { | |
| name: "BMPString", | |
| tag: cryptobyte_asn1.Tag(asn1.TagBMPString), | |
| value: []byte{80, 81}, | |
| expected: string("偑"), | |
| }, | |
| { | |
| name: "BMPString (invalid length)", | |
| tag: cryptobyte_asn1.Tag(asn1.TagBMPString), | |
| value: []byte{255}, | |
| expectedErr: "invalid BMPString", | |
| }, | |
| { | |
| name: "BMPString (invalid surrogate)", | |
| tag: cryptobyte_asn1.Tag(asn1.TagBMPString), | |
| value: []byte{80, 81, 216, 1}, | |
| expectedErr: "invalid BMPString", | |
| }, | |
| { | |
| name: "BMPString (invalid noncharacter 0xfdd1)", | |
| tag: cryptobyte_asn1.Tag(asn1.TagBMPString), | |
| value: []byte{80, 81, 253, 209}, | |
| expectedErr: "invalid BMPString", | |
| }, | |
| { | |
| name: "BMPString (invalid noncharacter 0xffff)", | |
| tag: cryptobyte_asn1.Tag(asn1.TagBMPString), | |
| value: []byte{80, 81, 255, 255}, | |
| expectedErr: "invalid BMPString", | |
| }, | |
| { | |
| name: "BMPString (invalid noncharacter 0xfffe)", | |
| tag: cryptobyte_asn1.Tag(asn1.TagBMPString), | |
| value: []byte{80, 81, 255, 254}, | |
| expectedErr: "invalid BMPString", | |
| }, | |
| { | |
| name: "IA5String", | |
| tag: cryptobyte_asn1.IA5String, | |
| value: []byte{80, 81}, | |
| expected: string("PQ"), | |
| }, | |
| { | |
| name: "IA5String (invalid)", | |
| tag: cryptobyte_asn1.IA5String, | |
| value: []byte{255}, | |
| expectedErr: "invalid IA5String", | |
| }, | |
| { | |
| name: "NumericString", | |
| tag: cryptobyte_asn1.Tag(asn1.TagNumericString), | |
| value: []byte{49, 50}, | |
| expected: string("12"), | |
| }, | |
| { | |
| name: "NumericString (invalid)", | |
| tag: cryptobyte_asn1.Tag(asn1.TagNumericString), | |
| value: []byte{80}, | |
| expectedErr: "invalid NumericString", | |
| }, | |
| } | |
| for _, tc := range tests { | |
| t.Run(tc.name, func(t *testing.T) { | |
| out, err := parseASN1String(tc.tag, tc.value) | |
| if err != nil && err.Error() != tc.expectedErr { | |
| t.Fatalf("parseASN1String returned unexpected error: got %q, want %q", err, tc.expectedErr) | |
| } else if err == nil && tc.expectedErr != "" { | |
| t.Fatalf("parseASN1String didn't fail, expected: %s", tc.expectedErr) | |
| } | |
| if out != tc.expected { | |
| t.Fatalf("parseASN1String returned unexpected value: got %q, want %q", out, tc.expected) | |
| } | |
| }) | |
| } | |
| } | |
| const policyPEM = `-----BEGIN CERTIFICATE----- | |
| MIIGeDCCBWCgAwIBAgIUED9KQBi0ScBDoufB2mgAJ63G5uIwDQYJKoZIhvcNAQEL | |
| BQAwVTELMAkGA1UEBhMCVVMxGDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDENMAsG | |
| A1UECxMERlBLSTEdMBsGA1UEAxMURmVkZXJhbCBCcmlkZ2UgQ0EgRzQwHhcNMjAx | |
| MDIyMTcwNDE5WhcNMjMxMDIyMTcwNDE5WjCBgTELMAkGA1UEBhMCVVMxHTAbBgNV | |
| BAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVz | |
| dCBOZXR3b3JrMTIwMAYDVQQDEylTeW1hbnRlYyBDbGFzcyAzIFNTUCBJbnRlcm1l | |
| ZGlhdGUgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2p | |
| 75cMpx86sS2aH4r+0o8r+m/KTrPrknWP0RA9Kp6sewAzkNa7BVwg0jOhyamiv1iP | |
| Cns10usoH93nxYbXLWF54vOLRdYU/53KEPNmgkj2ipMaTLuaReBghNibikWSnAmy | |
| S8RItaDMs8tdF2goKPI4xWiamNwqe92VC+pic2tq0Nva3Y4kvMDJjtyje3uduTtL | |
| oyoaaHkrX7i7gE67psnMKj1THUtre1JV1ohl9+oOuyot4p3eSxVlrMWiiwb11bnk | |
| CakecOz/mP2DHMGg6pZ/BeJ+ThaLUylAXECARIqHc9UwRPKC9BfLaCX4edIoeYiB | |
| loRs4KdqLdg/I9eTwKkCAwEAAaOCAxEwggMNMB0GA1UdDgQWBBQ1Jn1QleGhwb0F | |
| 1cOdd0LHDBOWjDAfBgNVHSMEGDAWgBR58ABJ6393wl1BAmU0ipAjmx4HbzAOBgNV | |
| HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zCBiAYDVR0gBIGAMH4wDAYKYIZI | |
| AWUDAgEDAzAMBgpghkgBZQMCAQMMMAwGCmCGSAFlAwIBAw4wDAYKYIZIAWUDAgED | |
| DzAMBgpghkgBZQMCAQMSMAwGCmCGSAFlAwIBAxMwDAYKYIZIAWUDAgEDFDAMBgpg | |
| hkgBZQMCAQMlMAwGCmCGSAFlAwIBAyYwggESBgNVHSEEggEJMIIBBTAbBgpghkgB | |
| ZQMCAQMDBg1ghkgBhvhFAQcXAwEGMBsGCmCGSAFlAwIBAwwGDWCGSAGG+EUBBxcD | |
| AQcwGwYKYIZIAWUDAgEDDgYNYIZIAYb4RQEHFwMBDjAbBgpghkgBZQMCAQMPBg1g | |
| hkgBhvhFAQcXAwEPMBsGCmCGSAFlAwIBAxIGDWCGSAGG+EUBBxcDARIwGwYKYIZI | |
| AWUDAgEDEwYNYIZIAYb4RQEHFwMBETAbBgpghkgBZQMCAQMUBg1ghkgBhvhFAQcX | |
| AwEUMBsGCmCGSAFlAwIBAyUGDWCGSAGG+EUBBxcDAQgwGwYKYIZIAWUDAgEDJgYN | |
| YIZIAYb4RQEHFwMBJDBgBggrBgEFBQcBCwRUMFIwUAYIKwYBBQUHMAWGRGh0dHA6 | |
| Ly9zc3Atc2lhLnN5bWF1dGguY29tL1NUTlNTUC9DZXJ0c19Jc3N1ZWRfYnlfQ2xh | |
| c3MzU1NQQ0EtRzMucDdjMA8GA1UdJAQIMAaAAQCBAQAwCgYDVR02BAMCAQAwUQYI | |
| KwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVodHRwOi8vcmVwby5mcGtpLmdvdi9i | |
| cmlkZ2UvY2FDZXJ0c0lzc3VlZFRvZmJjYWc0LnA3YzA3BgNVHR8EMDAuMCygKqAo | |
| hiZodHRwOi8vcmVwby5mcGtpLmdvdi9icmlkZ2UvZmJjYWc0LmNybDANBgkqhkiG | |
| 9w0BAQsFAAOCAQEAA751TycC1f/WTkHmedF9ZWxP58Jstmwvkyo8bKueJ0eF7LTG | |
| BgQlzE2B9vke4sFhd4V+BdgOPGE1dsGzllYKCWg0BhkCBs5kIJ7F6Ay6G1TBuGU1 | |
| Ie8247GL+P9pcC5TVvXHC/62R2w3DuD/vAPLbYEbSQjobXlsqt8Kmtd6yK/jVuDV | |
| BTZMdZmvoNtjemqmgcBXHsf0ctVm0m6tH5uYqyVxu8tfyUis6Cf303PHj+spWP1k | |
| gc5PYnVF0ot7qAmNFENIpbKg3BdusBkF9rGxLaDSUBvSc7+s9iQz9d/iRuAebrYu | |
| +eqUlJ2lsjS1U8qyPmlH+spfPNbAEQEsuP32Aw== | |
| -----END CERTIFICATE----- | |
| ` | |
| func TestPolicyParse(t *testing.T) { | |
| b, _ := pem.Decode([]byte(policyPEM)) | |
| c, err := ParseCertificate(b.Bytes) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if len(c.Policies) != 9 { | |
| t.Errorf("unexpected number of policies: got %d, want %d", len(c.Policies), 9) | |
| } | |
| if len(c.PolicyMappings) != 9 { | |
| t.Errorf("unexpected number of policy mappings: got %d, want %d", len(c.PolicyMappings), 9) | |
| } | |
| if !c.RequireExplicitPolicyZero { | |
| t.Error("expected RequireExplicitPolicyZero to be set") | |
| } | |
| if !c.InhibitPolicyMappingZero { | |
| t.Error("expected InhibitPolicyMappingZero to be set") | |
| } | |
| if !c.InhibitAnyPolicyZero { | |
| t.Error("expected InhibitAnyPolicyZero to be set") | |
| } | |
| } | |
| func TestParsePolicies(t *testing.T) { | |
| for _, tc := range []string{ | |
| "testdata/policy_leaf_duplicate.pem", | |
| "testdata/policy_leaf_invalid.pem", | |
| } { | |
| t.Run(tc, func(t *testing.T) { | |
| b, err := os.ReadFile(tc) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| p, _ := pem.Decode(b) | |
| _, err = ParseCertificate(p.Bytes) | |
| if err == nil { | |
| t.Error("parsing should've failed") | |
| } | |
| }) | |
| } | |
| } | |
| func TestParseCertificateNegativeMaxPathLength(t *testing.T) { | |
| certs := []string{ | |
| // Certificate with MaxPathLen set to -1. | |
| ` | |
| -----BEGIN CERTIFICATE----- | |
| MIIByTCCATKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRURVNU | |
| MB4XDTcwMDEwMTAwMTY0MFoXDTcwMDEwMjAzNDY0MFowDzENMAsGA1UEAxMEVEVT | |
| VDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsaHglFuSicTT8TKfipgsSi3N | |
| Wb/TcvuAhanFF1VGB+vS95kO7yFqyfRgX3GgOwT0KlJVsVjPjghEGR9RGTSLqkTD | |
| UFbiBgm8+VEPMOrUtIHIHXhl+ye44AkOEStxfz7gjN/EAS2h8ffPKhvDTHOlShKw | |
| Y3LQlxR0LdeJXq3eSqUCAwEAAaM1MDMwEgYDVR0TAQH/BAgwBgEB/wIB/zAdBgNV | |
| HQ4EFgQUrbrk0tqQAEsce8uYifP0BIVhuFAwDQYJKoZIhvcNAQELBQADgYEAIkhV | |
| ZBj1ThT+eyh50XsoU570NUysTg3Nj/3lbkEolzdcE+wu0CPXvgxLRM6Y62u1ey82 | |
| 8d5VQHstzF4dXgc3W+O9UySa+CKdcHx/q7o7seOGXdysT0IJtAY3w66mFkuF7PIn | |
| y9b7M5t6pmWjb7N0QqGuWeNqi4ZvS8gLKmVEgGY= | |
| -----END CERTIFICATE----- | |
| `, | |
| // Certificate with MaxPathLen set to -2. | |
| ` | |
| -----BEGIN CERTIFICATE----- | |
| MIIByTCCATKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRURVNU | |
| MB4XDTcwMDEwMTAwMTY0MFoXDTcwMDEwMjAzNDY0MFowDzENMAsGA1UEAxMEVEVT | |
| VDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsaHglFuSicTT8TKfipgsSi3N | |
| Wb/TcvuAhanFF1VGB+vS95kO7yFqyfRgX3GgOwT0KlJVsVjPjghEGR9RGTSLqkTD | |
| UFbiBgm8+VEPMOrUtIHIHXhl+ye44AkOEStxfz7gjN/EAS2h8ffPKhvDTHOlShKw | |
| Y3LQlxR0LdeJXq3eSqUCAwEAAaM1MDMwEgYDVR0TAQH/BAgwBgEB/wIB/jAdBgNV | |
| HQ4EFgQUrbrk0tqQAEsce8uYifP0BIVhuFAwDQYJKoZIhvcNAQELBQADgYEAGjIr | |
| YGQc7Ods+BuKck7p+vpAMONM8SLEuUtKorCP3ecsO51MoA4/niLbgMHaOGNHwzMp | |
| ajg0zLbY0Dj6Ml0VZ+lS3rjgTEhYXc626eZkoQqgUzL1jhe3S0ZbSxxmHMBKjJFl | |
| d5l1tRhScKu2NBgm74nYmJxJYgvuTA38wGhRrGU= | |
| -----END CERTIFICATE----- | |
| `, | |
| } | |
| for _, cert := range certs { | |
| b, _ := pem.Decode([]byte(cert)) | |
| _, err := ParseCertificate(b.Bytes) | |
| if err == nil || err.Error() != "x509: invalid basic constraints" { | |
| t.Errorf(`ParseCertificate() = %v; want = "x509: invalid basic constraints"`, err) | |
| } | |
| } | |
| } | |
| func TestDomainNameValid(t *testing.T) { | |
| for _, tc := range []struct { | |
| name string | |
| dnsName string | |
| constraint bool | |
| valid bool | |
| }{ | |
| // TODO(#75835): these tests are for stricter name validation, which we | |
| // had to disable. Once we reenable these strict checks, behind a | |
| // GODEBUG, we should add them back in. | |
| // {"empty name, name", "", false, false}, | |
| // {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false}, | |
| // {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false}, | |
| // {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false}, | |
| // {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false}, | |
| // {"64 char single label, name", strings.Repeat("a", 64), false, false}, | |
| // {"64 char single label, constraint", strings.Repeat("a", 64), true, false}, | |
| // {"64 char label, name", "a." + strings.Repeat("a", 64), false, false}, | |
| // {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false}, | |
| // TODO(#75835): these are the inverse of the tests above, they should be removed | |
| // once the strict checking is enabled. | |
| {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, true}, | |
| {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, true}, | |
| {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, true}, | |
| {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, true}, | |
| {"64 char single label, name", strings.Repeat("a", 64), false, true}, | |
| {"64 char single label, constraint", strings.Repeat("a", 64), true, true}, | |
| {"64 char label, name", "a." + strings.Repeat("a", 64), false, true}, | |
| {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, true}, | |
| // Check we properly enforce properties of domain names. | |
| {"empty name, constraint", "", true, true}, | |
| {"empty label, name", "a..a", false, false}, | |
| {"empty label, constraint", "a..a", true, false}, | |
| {"period, name", ".", false, false}, | |
| {"period, constraint", ".", true, false}, // TODO(roland): not entirely clear if this is a valid constraint (require at least one label?) | |
| {"valid, name", "a.b.c", false, true}, | |
| {"valid, constraint", "a.b.c", true, true}, | |
| {"leading period, name", ".a.b.c", false, false}, | |
| {"leading period, constraint", ".a.b.c", true, true}, | |
| {"trailing period, name", "a.", false, false}, | |
| {"trailing period, constraint", "a.", true, false}, | |
| {"bare label, name", "a", false, true}, | |
| {"bare label, constraint", "a", true, true}, | |
| {"63 char single label, name", strings.Repeat("a", 63), false, true}, | |
| {"63 char single label, constraint", strings.Repeat("a", 63), true, true}, | |
| {"63 char label, name", "a." + strings.Repeat("a", 63), false, true}, | |
| {"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true}, | |
| } { | |
| t.Run(tc.name, func(t *testing.T) { | |
| valid := domainNameValid(tc.dnsName, tc.constraint) | |
| if tc.valid != valid { | |
| t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid) | |
| } | |
| // Also check that we enforce the same properties as domainToReverseLabels | |
| trimmedName := tc.dnsName | |
| if tc.constraint && len(trimmedName) > 1 && trimmedName[0] == '.' { | |
| trimmedName = trimmedName[1:] | |
| } | |
| _, revValid := domainToReverseLabels(trimmedName) | |
| if valid != revValid { | |
| t.Errorf("domainNameValid(%q, %t) = %t != domainToReverseLabels(%q) = %t", tc.dnsName, tc.constraint, valid, trimmedName, revValid) | |
| } | |
| }) | |
| } | |
| } | |
| func TestRoundtripWeirdSANs(t *testing.T) { | |
| // TODO(#75835): check that certificates we create with CreateCertificate that have malformed SAN values | |
| // can be parsed by ParseCertificate. We should eventually restrict this, but for now we have to maintain | |
| // this property as people have been relying on it. | |
| k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| badNames := []string{ | |
| "baredomain", | |
| "baredomain.", | |
| strings.Repeat("a", 255), | |
| strings.Repeat("a", 65) + ".com", | |
| } | |
| tmpl := &Certificate{ | |
| EmailAddresses: badNames, | |
| DNSNames: badNames, | |
| } | |
| b, err := CreateCertificate(rand.Reader, tmpl, tmpl, &k.PublicKey, k) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| _, err = ParseCertificate(b) | |
| if err != nil { | |
| t.Fatalf("Couldn't roundtrip certificate: %v", err) | |
| } | |
| } | |
| func FuzzDomainNameValid(f *testing.F) { | |
| f.Fuzz(func(t *testing.T, data string) { | |
| domainNameValid(data, false) | |
| domainNameValid(data, true) | |
| }) | |
| } | |
| func TestParseNameTypes(t *testing.T) { | |
| block, _ := pem.Decode([]byte(` | |
| -----BEGIN CERTIFICATE----- | |
| MIICGzCCAcGgAwIBAgIJAIZft7jy3RcpMAoGCCqGSM49BAMCMA8xDTALBgNVBAMM | |
| BFRlc3QwHhcNMjUwOTAyMTg0MzE3WhcNMjUxMDAyMTg0MzE3WjCCARUxIjAgBg0q | |
| hkiG9xIEAYS3CQIBDA91dGY4LXN0cmluZ/CfpooxHzAdBg0qhkiG9xIEAYS3CQIC | |
| BAxvY3RldC1zdHJpbmcxGDAWBg0qhkiG9xIEAYS3CQIDDQUBAgMEBTETMBEGDSqG | |
| SIb3EgQBhLcJAgQFADEVMBMGDSqGSIb3EgQBhLcJAgUwAgECMRQwEgYNKoZIhvcS | |
| BAGEtwkCBgIBKjEgMB4GDSqGSIb3EgQBhLcJAgcGDSqGSIb3EgQBhLcJAgcxGDAW | |
| Bg0qhkiG9xIEAYS3CQIIAwUAqrvM3TEgMB4GDSqGSIb3EgQBhLcJAgkXDTI1MDkw | |
| MjE4NDMxN1oxFDASBg0qhkiG9xIEAYS3CQIKAQH/MFkwEwYHKoZIzj0CAQYIKoZI | |
| zj0DAQcDQgAE7M4zoqQtXbvGsudKaM5gd8emxyk68AFTjIYU4PO1AtiYX3wyL89k | |
| wbHjxgvmh/9aBg1LOj6kfsJIxULUmUpdzTAKBggqhkjOPQQDAgNIADBFAiBvKz3o | |
| ALhCqKrRFLUbax6+tI1s1B14IPVk2ZHbBEou5gIhAOpvJRNj5qluPXKLXmZvIK8u | |
| OjUhiZoowYvborSS1EBK | |
| -----END CERTIFICATE----- | |
| `)) | |
| expected := map[string]any{ | |
| "1.2.840.113554.4.1.72585.2.1": "utf8-string🦊", | |
| "1.2.840.113554.4.1.72585.2.2": []byte("octet-string"), | |
| "1.2.840.113554.4.1.72585.2.3": asn1.RawValue{Tag: 13, | |
| Bytes: []byte{1, 2, 3, 4, 5}, FullBytes: []byte{13, 5, 1, 2, 3, 4, 5}}, | |
| "1.2.840.113554.4.1.72585.2.4": nil, | |
| "1.2.840.113554.4.1.72585.2.5": asn1.RawValue{Tag: asn1.TagSequence, IsCompound: true, | |
| Bytes: []byte{1, 2}, FullBytes: []byte{0x30, 2, 1, 2}}, | |
| "1.2.840.113554.4.1.72585.2.6": int64(42), | |
| "1.2.840.113554.4.1.72585.2.7": asn1.ObjectIdentifier{1, 2, 840, 113554, 4, 1, 72585, 2, 7}, | |
| "1.2.840.113554.4.1.72585.2.8": asn1.BitString{BitLength: 32, | |
| Bytes: []byte{0xaa, 0xbb, 0xcc, 0xdd}}, | |
| "1.2.840.113554.4.1.72585.2.9": time.Date(2025, 9, 2, 18, 43, 17, 0, time.UTC), | |
| "1.2.840.113554.4.1.72585.2.10": true, | |
| } | |
| cert, err := ParseCertificate(block.Bytes) | |
| if err != nil { | |
| t.Fatalf("ParseCertificate failed: %v", err) | |
| } | |
| for _, attr := range cert.Subject.Names { | |
| if val, ok := expected[attr.Type.String()]; ok { | |
| if !reflect.DeepEqual(attr.Value, val) { | |
| t.Errorf("unexpected value for %s: got %v (%T), want %v (%T)", attr.Type, attr.Value, attr.Value, val, val) | |
| } | |
| } else { | |
| t.Errorf("unexpected attribute type: %s", attr.Type) | |
| } | |
| } | |
| extra := pkix.Name{ExtraNames: cert.Subject.Names} | |
| // asn1.Marshal does not encode NULL. | |
| for i := range extra.ExtraNames { | |
| if extra.ExtraNames[i].Value == nil { | |
| extra.ExtraNames[i].Value = asn1.NullRawValue | |
| } | |
| } | |
| got, err := asn1.Marshal(extra.ToRDNSequence()) | |
| if err != nil { | |
| t.Fatalf("asn1.Marshal failed: %v", err) | |
| } | |
| if !bytes.Equal(got, cert.RawSubject) { | |
| t.Errorf("unexpected marshaled RDNSequence: got %x, want %x", got, cert.RawSubject) | |
| } | |
| } |