1// Copyright 2016 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package ctfe
16
17import (
18	"encoding/base64"
19	"encoding/pem"
20	"testing"
21	"time"
22
23	"github.com/google/certificate-transparency-go/trillian/ctfe/testonly"
24	"github.com/google/certificate-transparency-go/x509"
25	"github.com/google/certificate-transparency-go/x509/pkix"
26	"github.com/google/certificate-transparency-go/x509util"
27)
28
29func wipeExtensions(cert *x509.Certificate) *x509.Certificate {
30	cert.Extensions = cert.Extensions[:0]
31	return cert
32}
33
34func makePoisonNonCritical(cert *x509.Certificate) *x509.Certificate {
35	// Invalid as a pre-cert because poison extension needs to be marked as critical.
36	cert.Extensions = []pkix.Extension{{Id: ctPoisonExtensionOID, Critical: false, Value: asn1NullBytes}}
37	return cert
38}
39
40func makePoisonNonNull(cert *x509.Certificate) *x509.Certificate {
41	// Invalid as a pre-cert because poison extension is not ASN.1 NULL value.
42	cert.Extensions = []pkix.Extension{{Id: ctPoisonExtensionOID, Critical: false, Value: []byte{0x42, 0x42, 0x42}}}
43	return cert
44}
45
46func TestIsPrecertificate(t *testing.T) {
47	var tests = []struct {
48		desc        string
49		cert        *x509.Certificate
50		wantPrecert bool
51		wantErr     bool
52	}{
53		{
54			desc:        "valid-precert",
55			cert:        pemToCert(t, testonly.PrecertPEMValid),
56			wantPrecert: true,
57		},
58		{
59			desc:        "valid-cert",
60			cert:        pemToCert(t, testonly.CACertPEM),
61			wantPrecert: false,
62		},
63		{
64			desc:        "remove-exts-from-precert",
65			cert:        wipeExtensions(pemToCert(t, testonly.PrecertPEMValid)),
66			wantPrecert: false,
67		},
68		{
69			desc:        "poison-non-critical",
70			cert:        makePoisonNonCritical(pemToCert(t, testonly.PrecertPEMValid)),
71			wantPrecert: false,
72			wantErr:     true,
73		},
74		{
75			desc:        "poison-non-null",
76			cert:        makePoisonNonNull(pemToCert(t, testonly.PrecertPEMValid)),
77			wantPrecert: false,
78			wantErr:     true,
79		},
80	}
81
82	for _, test := range tests {
83		gotPrecert, err := IsPrecertificate(test.cert)
84		t.Run(test.desc, func(t *testing.T) {
85			if err != nil {
86				if !test.wantErr {
87					t.Errorf("IsPrecertificate()=%v,%v; want %v,nil", gotPrecert, err, test.wantPrecert)
88				}
89				return
90			}
91			if test.wantErr {
92				t.Errorf("IsPrecertificate()=%v,%v; want _,%v", gotPrecert, err, test.wantErr)
93			}
94			if gotPrecert != test.wantPrecert {
95				t.Errorf("IsPrecertificate()=%v,%v; want %v,nil", gotPrecert, err, test.wantPrecert)
96			}
97		})
98	}
99}
100
101func TestValidateChain(t *testing.T) {
102	fakeCARoots := NewPEMCertPool()
103	if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) {
104		t.Fatal("failed to load fake root")
105	}
106	if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeRootCACertPEM)) {
107		t.Fatal("failed to load fake root")
108	}
109	validateOpts := CertValidationOpts{
110		trustedRoots: fakeCARoots,
111		extKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
112	}
113
114	var tests = []struct {
115		desc        string
116		chain       [][]byte
117		wantErr     bool
118		wantPathLen int
119	}{
120		{
121			desc:    "missing-intermediate-cert",
122			chain:   pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM}),
123			wantErr: true,
124		},
125		{
126			desc:    "wrong-cert-order",
127			chain:   pemsToDERChain(t, []string{testonly.FakeIntermediateCertPEM, testonly.LeafSignedByFakeIntermediateCertPEM}),
128			wantErr: true,
129		},
130		{
131			desc:    "unrelated-cert-in-chain",
132			chain:   pemsToDERChain(t, []string{testonly.FakeIntermediateCertPEM, testonly.TestCertPEM}),
133			wantErr: true,
134		},
135		{
136			desc:    "unrelated-cert-after-chain",
137			chain:   pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM, testonly.TestCertPEM}),
138			wantErr: true,
139		},
140		{
141			desc:        "valid-chain",
142			chain:       pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}),
143			wantPathLen: 3,
144		},
145		{
146			desc:        "valid-chain-with-policyconstraints",
147			chain:       pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithPolicyConstraintsCertPEM}),
148			wantPathLen: 3,
149		},
150		{
151			desc:        "valid-chain-with-policyconstraints-inc-root",
152			chain:       pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithPolicyConstraintsCertPEM, testonly.FakeRootCACertPEM}),
153			wantPathLen: 3,
154		},
155		{
156			desc:        "valid-chain-with-nameconstraints",
157			chain:       pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithNameConstraintsCertPEM}),
158			wantPathLen: 3,
159		},
160		{
161			desc:        "chain-with-invalid-nameconstraints",
162			chain:       pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithInvalidNameConstraintsCertPEM}),
163			wantPathLen: 3,
164		},
165		{
166			desc:        "chain-of-len-4",
167			chain:       pemFileToDERChain(t, "../testdata/subleaf.chain"),
168			wantPathLen: 4,
169		},
170		{
171			desc:    "misordered-chain-of-len-4",
172			chain:   pemFileToDERChain(t, "../testdata/subleaf.misordered.chain"),
173			wantErr: true,
174		},
175	}
176	for _, test := range tests {
177		t.Run(test.desc, func(t *testing.T) {
178			gotPath, err := ValidateChain(test.chain, validateOpts)
179			if err != nil {
180				if !test.wantErr {
181					t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err)
182				}
183				return
184			}
185			if test.wantErr {
186				t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err)
187				return
188			}
189			if len(gotPath) != test.wantPathLen {
190				t.Errorf("|ValidateChain()|=%d; want %d", len(gotPath), test.wantPathLen)
191				for _, c := range gotPath {
192					t.Logf("Subject: %s Issuer: %s", x509util.NameToString(c.Subject), x509util.NameToString(c.Issuer))
193				}
194			}
195		})
196	}
197}
198
199func TestCA(t *testing.T) {
200	fakeCARoots := NewPEMCertPool()
201	if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) {
202		t.Fatal("failed to load fake root")
203	}
204	validateOpts := CertValidationOpts{
205		trustedRoots: fakeCARoots,
206		extKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
207	}
208	chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM})
209	leaf, err := x509.ParseCertificate(chain[0])
210	if x509.IsFatal(err) {
211		t.Fatalf("Failed to parse golden certificate DER: %v", err)
212	}
213	t.Logf("Cert expiry date: %v", leaf.NotAfter)
214
215	var tests = []struct {
216		desc    string
217		chain   [][]byte
218		caOnly  bool
219		wantErr bool
220	}{
221		{
222			desc:  "end-entity, allow non-CA",
223			chain: chain,
224		},
225		{
226			desc:    "end-entity, disallow non-CA",
227			chain:   chain,
228			caOnly:  true,
229			wantErr: true,
230		},
231		{
232			desc:  "intermediate, allow non-CA",
233			chain: chain[1:],
234		},
235		{
236			desc:   "intermediate, disallow non-CA",
237			chain:  chain[1:],
238			caOnly: true,
239		},
240	}
241	for _, test := range tests {
242		t.Run(test.desc, func(t *testing.T) {
243			validateOpts.acceptOnlyCA = test.caOnly
244			gotPath, err := ValidateChain(test.chain, validateOpts)
245			if err != nil {
246				if !test.wantErr {
247					t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err)
248				}
249				return
250			}
251			if test.wantErr {
252				t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err)
253			}
254		})
255	}
256}
257
258func TestNotAfterRange(t *testing.T) {
259	fakeCARoots := NewPEMCertPool()
260	if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) {
261		t.Fatal("failed to load fake root")
262	}
263	validateOpts := CertValidationOpts{
264		trustedRoots:  fakeCARoots,
265		rejectExpired: false,
266		extKeyUsages:  []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
267	}
268
269	chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM})
270
271	var tests = []struct {
272		desc          string
273		chain         [][]byte
274		notAfterStart time.Time
275		notAfterLimit time.Time
276		wantErr       bool
277	}{
278		{
279			desc:  "valid-chain, no range",
280			chain: chain,
281		},
282		{
283			desc:          "valid-chain, valid range",
284			chain:         chain,
285			notAfterStart: time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC),
286			notAfterLimit: time.Date(2020, 7, 1, 0, 0, 0, 0, time.UTC),
287		},
288		{
289			desc:          "before valid range",
290			chain:         chain,
291			notAfterStart: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
292			wantErr:       true,
293		},
294		{
295			desc:          "after valid range",
296			chain:         chain,
297			notAfterLimit: time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC),
298			wantErr:       true,
299		},
300	}
301	for _, test := range tests {
302		t.Run(test.desc, func(t *testing.T) {
303			if !test.notAfterStart.IsZero() {
304				validateOpts.notAfterStart = &test.notAfterStart
305			}
306			if !test.notAfterLimit.IsZero() {
307				validateOpts.notAfterLimit = &test.notAfterLimit
308			}
309			gotPath, err := ValidateChain(test.chain, validateOpts)
310			if err != nil {
311				if !test.wantErr {
312					t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err)
313				}
314				return
315			}
316			if test.wantErr {
317				t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err)
318			}
319		})
320	}
321}
322
323// Builds a chain of DER-encoded certs.
324// Note: ordering is important
325func pemsToDERChain(t *testing.T, pemCerts []string) [][]byte {
326	t.Helper()
327	chain := make([][]byte, 0, len(pemCerts))
328	for _, pemCert := range pemCerts {
329		cert := pemToCert(t, pemCert)
330		chain = append(chain, cert.Raw)
331	}
332	return chain
333}
334
335func pemToCert(t *testing.T, pemData string) *x509.Certificate {
336	t.Helper()
337	bytes, rest := pem.Decode([]byte(pemData))
338	if len(rest) > 0 {
339		t.Fatalf("Extra data after PEM: %v", rest)
340		return nil
341	}
342
343	cert, err := x509.ParseCertificate(bytes.Bytes)
344	if x509.IsFatal(err) {
345		t.Fatal(err)
346	}
347
348	return cert
349}
350
351func pemFileToDERChain(t *testing.T, filename string) [][]byte {
352	t.Helper()
353	rawChain, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
354	if err != nil {
355		t.Fatalf("failed to load testdata: %v", err)
356	}
357	return rawChain
358}
359
360// Validate a chain including a pre-issuer as produced by Google's Compliance Monitor.
361func TestCMPreIssuedCert(t *testing.T) {
362	var b64Chain = []string{
363		"MIID+jCCAuKgAwIBAgIHBWW7shJizTANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJHQjEPMA0GA1UEBwwGTG9uZG9uMTowOAYDVQQKDDFHb29nbGUgQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IChQcmVjZXJ0IFNpZ25pbmcpMRkwFwYDVQQFExAxNTE5MjMxNzA0MTczNDg3MB4XDTE4MDIyMTE2NDgyNFoXDTE4MTIwMTIwMzMyN1owYzELMAkGA1UEBhMCR0IxDzANBgNVBAcMBkxvbmRvbjEoMCYGA1UECgwfR29vZ2xlIENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEZMBcGA1UEBRMQMTUxOTIzMTcwNDM5MjM5NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKnKP9TP6hkEuD+d1rPeA8mxo5xFYffhCcEitP8PtTl7G2RqFrndPeAkzgvOxPB3Jrhx7LtMtg0IvS8y7Sy1qDqDou1/OrJgwCeWMc1/KSneuGP8GTX0Rqy4z8+LsiBN/tMDbt94RuiyCeltIAaHGmsNeYXV34ayD3vSIAQbtLUOD39KqrJWO0tQ//nshBuFlebiUrDP7rirPusYYW0stJKiCKeORhHvL3/I8mCYGNO0XIWMpASH2S9LGMwg+AQM13whC1KL65EGuVs4Ta0rO+Tl8Yi0is0RwdUmgdSGtl0evPTzyUXbA1n1BpkLcSQ5E3RxY3O6Ge9Whvtmg9vAJiMCAwEAAaOBoDCBnTATBgNVHSUEDDAKBggrBgEFBQcDATAjBgNVHREEHDAaghhmbG93ZXJzLXRvLXRoZS13b3JsZC5jb20wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRKCM/Ajh0Fu6FFjJ9F4gVWK2oj/jAdBgNVHQ4EFgQUVjYl6wDey3DxvmTG2HL4vdiUt+MwEwYKKwYBBAHWeQIEAwEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAAvyEFDIAWr0URsZzrJLZEL8p6FMTzVxY/MOvGP8QMXA6xNVElxYnDPF32JERAl+poR7syByhVFcEjrw7f2FTlMc04+hT/hsYzi8cMAmfX9KA36xUBVjyqvqwofxTwoWYdf+eGZW0EG8Yp1pM7iUy9bdlh3sgdOpmT9Z5XGCRwvdW1+mctv0JMKDdWzxBqYyNMnNjvjHBmkiuHeDDGFsV2zq+wV64RwJa2eVrnkMDMV1mscL6KzNRLPP2ZpNz/8H7SPock+fk4cZrdqj+0IzFt+6ixSoKyltyD+nkbWjRGY4iyboo/nPgTQ1IQCS2OPVHWw3NijFD8hqgAnYvz0Dn+k=",
364		"MIIE4jCCAsqgAwIBAgIHBWW7sg8LrzANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMRcwFQYDVQQKDA5Hb29nbGUgVUsgTHRkLjEhMB8GA1UECwwYQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MSMwIQYDVQQDDBpNZXJnZSBEZWxheSBJbnRlcm1lZGlhdGUgMTAeFw0xODAyMjExNjQ4MjRaFw0xODEyMDEyMDM0MjdaMHUxCzAJBgNVBAYTAkdCMQ8wDQYDVQQHDAZMb25kb24xOjA4BgNVBAoMMUdvb2dsZSBDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgKFByZWNlcnQgU2lnbmluZykxGTAXBgNVBAUTEDE1MTkyMzE3MDQxNzM0ODcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCKWlc3A43kJ9IzmkCPXcsGwTxlIvtl9sNYBWlx9qqHa1i6tU6rZuH9uXAb3wsn39fqY22HzF/yrx9pd05doFfRq6dvvm4eHNFfFm4cJur1kmPe8vLKpSI/P2DPx4/mRzrHnPAI8Jo9QgKcj91AyYeB689ZFzH30ay32beo6PxQvtoJkzl+dzf9Hs1ezavS7nDCuqDnu1V1Og7J5xTHZeNyTKgD5Kx28ukmIp2wGOvg3omuInABg/ew0VxnG/txKV+69zfV9dhclU3m16L81e3RkJ8Kg4RLb0mh9X3EMn90SpJ9yw0j8FF0Esk6wxuYeUGLShUji8BPnnbactY9B6ORAgMBAAGjbTBrMBgGA1UdJQEB/wQOMAwGCisGAQQB1nkCBAQwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTpPAThgC/ChBMtJnCe8v0az6r+xjAdBgNVHQ4EFgQUSgjPwI4dBbuhRYyfReIFVitqI/4wDQYJKoZIhvcNAQEFBQADggIBAEep2uWAFsdq1nLtLWLGh7DfVPc/K+1lcqNx64ucjpVZbDnMnYKFagf2Z9rHEWqR7kwuLac5xW8woSlLa/NHmJmdg18HGUhlS+x8iMPv7dK6hfNsRFdjLZkZOFneuf9j1b0dV+rXoRvyY+Oq+lomC98bEr+g9zq+M7wJ4wS/KeaNHpPw1pBeTtCdw+1c4ZgRTOEa2OUUpkpueJ+9psD/hbp6HLF+WYijWQ0/iYSxJ4TbjTC+omKRsGhvxSLbP8cSMt3X1pJgrFK1BvH4lqqEXGDNEiVNoPCHraEa8JtMZIo47/Af13lDfp6sBdZ0lvLAVDduWgg/2RkWCbHefAe81h+cYdDS775TF2TCMTwsR6GsM9sVCbfPvHXI/pUzamRn0i0CrhyccBBdPrUhj+cXuc9kqSkLegun9D8EBDMM9va5wb1HM0ruSno+YuLtfhCdBRHr/RG2BKJi7uUDjJ8goHov/EUJmHjAIARKz74IPWRkxMrnOvGhnNa2Hz+da3hpusz0Mj4rsqv1EKTC2wbCs6Rk2MRPSxdRbywdWLSmGn249SMfXK4An+dqoRk1fwKqdXc4swoUvxnGUi5ajBaRtc6631zBTmvmSFQnvGmS42aF7q2PjfvWPIuO+d//m8KgN6o2YyjrdPDDslI2RZUE5ngOR+JynvhjYrrB7Bat1EY7",
365		"MIIFyDCCA7CgAwIBAgICEAEwDQYJKoZIhvcNAQEFBQAwfTELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEhMB8GA1UEAwwYTWVyZ2UgRGVsYXkgTW9uaXRvciBSb290MB4XDTE0MDcxNzEyMjYzMFoXDTE5MDcxNjEyMjYzMFowfzELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEjMCEGA1UEAwwaTWVyZ2UgRGVsYXkgSW50ZXJtZWRpYXRlIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDB6HT+/5ru8wO7+mNFOIH6r43BwiwJZB2vQwOB8zvBV79sTIqNV7Grx5KFnSDyGRUJxZfEN7FGc96lr0vqFDlt1DbcYgVV15U+Dt4B9/+0Tz/3zeZO0kVjTg3wqvzpw6xetj2N4dlpysiFQZVAOp+dHUw9zu3xNR7dlFdDvFSrdFsgT7Uln+Pt9pXCz5C4hsSP9oC3RP7CaRtDRSQrMcNvMRi3J8XeXCXsGqMKTCRhxRGe9ruQ2Bbm5ExbmVW/ou00Fr9uSlPJL6+sDR8Li/PTW+DU9hygXSj8Zi36WI+6PuA4BHDAEt7Z5Ru/Hnol76dFeExJ0F6vjc7gUnNh7JExJgBelyz0uGORT4NhWC7SRWP/ngPFLoqcoyZMVsGGtOxSt+aVzkKuF+x64CVxMeHb9I8t3iQubpHqMEmIE1oVSCsF/AkTVTKLOeWG6N06SjoUy5fu9o+faXKMKR8hldLM5z1K6QhFsb/F+uBAuU/DWaKVEZgbmWautW06fF5I+OyoFeW+hrPTbmon4OLE3ubjDxKnyTa4yYytWSisojjfw5z58sUkbLu7KAy2+Z60m/0deAiVOQcsFkxwgzcXRt7bxN7By5Q5Bzrz8uYPjFBfBnlhqMU5RU/FNBFY7Mx4Uy8+OcMYfJQ5/A/4julXEx1HjfBj3VCyrT/noHDpBeOGiwIDAQABo1AwTjAdBgNVHQ4EFgQU6TwE4YAvwoQTLSZwnvL9Gs+q/sYwHwYDVR0jBBgwFoAU8197dUnjeEE5aiC2fGtMXMk9WEEwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAgEACFjL1UXy6S4JkGrDnz1VwTYHplFDY4bG6Q8Sh3Og6z9HJdivNft/iAQ2tIHyz0eAGCXeVPE/j1kgvz2RbnUxQd5eWdLeu/w/wiZyHxWhbTt6RhjqBVFjnx0st7n6rRt+Bw8jpugZfD11SbumVT/V20Gc45lHf2oEgbkPUcnTB9gssFz5Z4KKGs5lIHz4a20WeSJF3PJLTBefkRhHNufi/LhjpLXImwrC82g5ChBZS5XIVuJZx3VkMWiYz4emgX0YWF/JdtaB2dUQ7yrTforQ5J9b1JnJ7H/o9DsX3/ubfQ39gwDBxTicnqC+Q3Dcv3i9PvwjCNJQuGa7ygMcDEn/d6elQg2qHxtqRE02ZlOXTC0XnDAJhx7myJFA/Knv3yO9S4jG6665KG9Y88/CHkh08YLR7NYFiRmwOxjbe3lb6csl/FFmqUXvjhEzzWAxKjI09GSd9hZkB8u17Mg46eEYwF3ufIlqmYdlWufjSc2BZuaNNN6jtK6JKp8jhQUycehgtUK+NlBQOXTzu28miDdasoSH2mdR0PLDo1547+MLGdV4COvqLERTmQrYHrliicD5nFCA+CCSvGEjo0DGOmF/O8StwSmNiKJ4ppPvk2iGEdO07e0LbQI+2fbC6og2SDGXUlsbG85wqQw0A7CU1fQSqhFBuZZauDFMUvdy3v/BAIw=",
366		"MIIFzTCCA7WgAwIBAgIJAJ7TzLHRLKJyMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoMDkdvb2dsZSBVSyBMdGQuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxITAfBgNVBAMMGE1lcmdlIERlbGF5IE1vbml0b3IgUm9vdDAeFw0xNDA3MTcxMjA1NDNaFw00MTEyMDIxMjA1NDNaMH0xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoMDkdvb2dsZSBVSyBMdGQuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxITAfBgNVBAMMGE1lcmdlIERlbGF5IE1vbml0b3IgUm9vdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKoWHPIgXtgaxWVIPNpCaj2y5Yj9t1ixe5PqjWhJXVNKAbpPbNHA/AoSivecBm3FTD9DfgW6J17mHb+cvbKSgYNzgTk5e2GJrnOP7yubYJpt2OCw0OILJD25NsApzcIiCvLA4aXkqkGgBq9FiVfisReNJxVu8MtxfhbVQCXZf0PpkW+yQPuF99V5Ri+grHbHYlaEN1C/HM3+t2yMR4hkd2RNXsMjViit9qCchIi/pQNt5xeQgVGmtYXyc92ftTMrmvduj7+pHq9DEYFt3ifFxE8v0GzCIE1xR/d7prFqKl/KRwAjYUcpU4vuazywcmRxODKuwWFVDrUBkGgCIVIjrMJWStH5i7WTSSTrVtOD/HWYvkXInZlSgcDvsNIG0pptJaEKSP4jUzI3nFymnoNZn6pnfdIII/XISpYSVeyl1IcdVMod8HdKoRew9CzW6f2n6KSKU5I8X5QEM1NUTmRLWmVi5c75/CvS/PzOMyMzXPf+fE2Dwbf4OcR5AZLTupqp8yCTqo7ny+cIBZ1TjcZjzKG4JTMaqDZ1Sg0T3mO/ZbbiBE3N8EHxoMWpw8OP50z1dtRRwj6qUZ2zLvngOb2EihlMO15BpVZC3Cg929c9Hdl65pUd4YrYnQBQB/rn6IvHo8zot8zElgOg22fHbViijUt3qnRggB40N30MXkYGwuJbAgMBAAGjUDBOMB0GA1UdDgQWBBTzX3t1SeN4QTlqILZ8a0xcyT1YQTAfBgNVHSMEGDAWgBTzX3t1SeN4QTlqILZ8a0xcyT1YQTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQB3HP6jRXmpdSDYwkI9aOzQeJH4x/HDi/PNMOqdNje/xdNzUy7HZWVYvvSVBkZ1DG/ghcUtn/wJ5m6/orBn3ncnyzgdKyXbWLnCGX/V61PgIPQpuGo7HzegenYaZqWz7NeXxGaVo3/y1HxUEmvmvSiioQM1cifGtz9/aJsJtIkn5umlImenKKEV1Ly7R3Uz3Cjz/Ffac1o+xU+8NpkLF/67fkazJCCMH6dCWgy6SL3AOB6oKFIVJhw8SD8vptHaDbpJSRBxifMtcop/85XUNDCvO4zkvlB1vPZ9ZmYZQdyL43NA+PkoKy0qrdaQZZMq1Jdp+Lx/yeX255/zkkILp43jFyd44rZ+TfGEQN1WHlp4RMjvoGwOX1uGlfoGkRSgBRj7TBn514VYMbXu687RS4WY2v+kny3PUFv/ZBfYSyjoNZnU4Dce9kstgv+gaKMQRPcyL+4vZU7DV8nBIfNFilCXKMN/VnNBKtDV52qmtOsVghgai+QE09w15x7dg+44gIfWFHxNhvHKys+s4BBN8fSxAMLOsb5NGFHE8x58RAkmIYWHjyPM6zB5AUPw1b2A0sDtQmCqoxJZfZUKrzyLz8gS2aVujRYN13KklHQ3EKfkeKBG2KXVBe5rjMN/7Anf1MtXxsTY6O8qIuHZ5QlXhSYzE41yIlPlG6d7AGnTiBIgeg==",
367	}
368	rawChain := make([][]byte, len(b64Chain))
369	for i, b64Data := range b64Chain {
370		var err error
371		rawChain[i], err = base64.StdEncoding.DecodeString(b64Data)
372		if err != nil {
373			t.Fatalf("failed to base64.Decode(chain[%d]): %v", i, err)
374		}
375	}
376
377	root, err := x509.ParseCertificate(rawChain[len(rawChain)-1])
378	if err != nil {
379		t.Fatalf("failed to parse root cert: %v", err)
380	}
381	cmRoot := NewPEMCertPool()
382	cmRoot.AddCert(root)
383	opts := CertValidationOpts{trustedRoots: cmRoot}
384	chain, err := ValidateChain(rawChain, opts)
385	if err != nil {
386		t.Fatalf("failed to ValidateChain: %v", err)
387	}
388	for i, c := range chain {
389		t.Logf("chain[%d] = \n%s", i, x509util.CertificateToString(c))
390	}
391}
392