// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "encoding/base64" "encoding/pem" "strings" "testing" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/trillian/ctfe/testonly" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509/pkix" "github.com/google/certificate-transparency-go/x509util" ) func wipeExtensions(cert *x509.Certificate) *x509.Certificate { cert.Extensions = cert.Extensions[:0] return cert } func makePoisonNonCritical(cert *x509.Certificate) *x509.Certificate { // Invalid as a pre-cert because poison extension needs to be marked as critical. cert.Extensions = []pkix.Extension{{Id: x509.OIDExtensionCTPoison, Critical: false, Value: asn1.NullBytes}} return cert } func makePoisonNonNull(cert *x509.Certificate) *x509.Certificate { // Invalid as a pre-cert because poison extension is not ASN.1 NULL value. cert.Extensions = []pkix.Extension{{Id: x509.OIDExtensionCTPoison, Critical: false, Value: []byte{0x42, 0x42, 0x42}}} return cert } func TestIsPrecertificate(t *testing.T) { var tests = []struct { desc string cert *x509.Certificate wantPrecert bool wantErr bool }{ { desc: "valid-precert", cert: pemToCert(t, testonly.PrecertPEMValid), wantPrecert: true, }, { desc: "valid-cert", cert: pemToCert(t, testonly.CACertPEM), wantPrecert: false, }, { desc: "remove-exts-from-precert", cert: wipeExtensions(pemToCert(t, testonly.PrecertPEMValid)), wantPrecert: false, }, { desc: "poison-non-critical", cert: makePoisonNonCritical(pemToCert(t, testonly.PrecertPEMValid)), wantPrecert: false, wantErr: true, }, { desc: "poison-non-null", cert: makePoisonNonNull(pemToCert(t, testonly.PrecertPEMValid)), wantPrecert: false, wantErr: true, }, } for _, test := range tests { gotPrecert, err := IsPrecertificate(test.cert) t.Run(test.desc, func(t *testing.T) { if err != nil { if !test.wantErr { t.Errorf("IsPrecertificate()=%v,%v; want %v,nil", gotPrecert, err, test.wantPrecert) } return } if test.wantErr { t.Errorf("IsPrecertificate()=%v,%v; want _,%v", gotPrecert, err, test.wantErr) } if gotPrecert != test.wantPrecert { t.Errorf("IsPrecertificate()=%v,%v; want %v,nil", gotPrecert, err, test.wantPrecert) } }) } } func TestValidateChain(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeRootCACertPEM)) { t.Fatal("failed to load fake root") } if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.CACertPEM)) { t.Fatal("failed to load CA root") } if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.RealPrecertIntermediatePEM)) { t.Fatal("failed to load real intermediate") } validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, } var tests = []struct { desc string chain [][]byte wantErr bool wantPathLen int modifyOpts func(v *CertValidationOpts) }{ { desc: "missing-intermediate-cert", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM}), wantErr: true, }, { desc: "wrong-cert-order", chain: pemsToDERChain(t, []string{testonly.FakeIntermediateCertPEM, testonly.LeafSignedByFakeIntermediateCertPEM}), wantErr: true, }, { desc: "unrelated-cert-in-chain", chain: pemsToDERChain(t, []string{testonly.FakeIntermediateCertPEM, testonly.TestCertPEM}), wantErr: true, }, { desc: "unrelated-cert-after-chain", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM, testonly.TestCertPEM}), wantErr: true, }, { desc: "valid-chain", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantPathLen: 3, }, { desc: "valid-chain-with-policyconstraints", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithPolicyConstraintsCertPEM}), wantPathLen: 3, }, { desc: "valid-chain-with-policyconstraints-inc-root", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithPolicyConstraintsCertPEM, testonly.FakeRootCACertPEM}), wantPathLen: 3, }, { desc: "valid-chain-with-nameconstraints", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithNameConstraintsCertPEM}), wantPathLen: 3, }, { desc: "chain-with-invalid-nameconstraints", chain: pemsToDERChain(t, []string{testonly.LeafCertPEM, testonly.FakeIntermediateWithInvalidNameConstraintsCertPEM}), wantPathLen: 3, }, { desc: "chain-of-len-4", chain: pemFileToDERChain(t, "../testdata/subleaf.chain"), wantPathLen: 4, }, { desc: "misordered-chain-of-len-4", chain: pemFileToDERChain(t, "../testdata/subleaf.misordered.chain"), wantErr: true, }, { desc: "reject-non-existent-ext-id", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{99, 99, 99, 99}} }, wantPathLen: 3, }, { desc: "reject-non-existent-ext-id-precert", chain: pemsToDERChain(t, []string{testonly.PrecertPEMValid}), modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{99, 99, 99, 99}} }, wantPathLen: 2, }, { desc: "reject-ext-id", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{2, 5, 29, 14}} }, }, { desc: "reject-ext-id-precert", chain: pemsToDERChain(t, []string{testonly.PrecertPEMValid}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject SubjectKeyIdentifier extension v.rejectExtIds = []asn1.ObjectIdentifier{[]int{2, 5, 29, 14}} }, }, { desc: "reject-eku-not-present-in-cert", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject cert without ExtKeyUsageEmailProtection v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection} }, }, { desc: "allow-eku-present-in-cert", chain: pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}), wantPathLen: 3, modifyOpts: func(v *CertValidationOpts) { v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} }, }, { desc: "reject-eku-not-present-in-precert", chain: pemsToDERChain(t, []string{testonly.RealPrecertWithEKUPEM}), wantErr: true, modifyOpts: func(v *CertValidationOpts) { // reject cert without ExtKeyUsageEmailProtection v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection} }, }, { desc: "allow-eku-present-in-precert", chain: pemsToDERChain(t, []string{testonly.RealPrecertWithEKUPEM}), wantPathLen: 2, modifyOpts: func(v *CertValidationOpts) { v.extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} }, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { validateOpts := validateOpts if test.modifyOpts != nil { test.modifyOpts(&validateOpts) } gotPath, err := ValidateChain(test.chain, validateOpts) if err != nil { if !test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err) } return } if test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err) return } if len(gotPath) != test.wantPathLen { t.Errorf("|ValidateChain()|=%d; want %d", len(gotPath), test.wantPathLen) for _, c := range gotPath { t.Logf("Subject: %s Issuer: %s", x509util.NameToString(c.Subject), x509util.NameToString(c.Issuer)) } } }) } } func TestCA(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, } chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}) leaf, err := x509.ParseCertificate(chain[0]) if x509.IsFatal(err) { t.Fatalf("Failed to parse golden certificate DER: %v", err) } t.Logf("Cert expiry date: %v", leaf.NotAfter) var tests = []struct { desc string chain [][]byte caOnly bool wantErr bool }{ { desc: "end-entity, allow non-CA", chain: chain, }, { desc: "end-entity, disallow non-CA", chain: chain, caOnly: true, wantErr: true, }, { desc: "intermediate, allow non-CA", chain: chain[1:], }, { desc: "intermediate, disallow non-CA", chain: chain[1:], caOnly: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { validateOpts.acceptOnlyCA = test.caOnly gotPath, err := ValidateChain(test.chain, validateOpts) if err != nil { if !test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err) } return } if test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err) } }) } } func TestNotAfterRange(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, rejectExpired: false, } chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}) var tests = []struct { desc string chain [][]byte notAfterStart time.Time notAfterLimit time.Time wantErr bool }{ { desc: "valid-chain, no range", chain: chain, }, { desc: "valid-chain, valid range", chain: chain, notAfterStart: time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), notAfterLimit: time.Date(2020, 7, 1, 0, 0, 0, 0, time.UTC), }, { desc: "before valid range", chain: chain, notAfterStart: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), wantErr: true, }, { desc: "after valid range", chain: chain, notAfterLimit: time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC), wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { if !test.notAfterStart.IsZero() { validateOpts.notAfterStart = &test.notAfterStart } if !test.notAfterLimit.IsZero() { validateOpts.notAfterLimit = &test.notAfterLimit } gotPath, err := ValidateChain(test.chain, validateOpts) if err != nil { if !test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,nil", gotPath, err) } return } if test.wantErr { t.Errorf("ValidateChain()=%v,%v; want _,non-nil", gotPath, err) } }) } } func TestRejectExpiredUnexpired(t *testing.T) { fakeCARoots := x509util.NewPEMCertPool() // Validity period: Jul 11, 2016 - Jul 11, 2017. if !fakeCARoots.AppendCertsFromPEM([]byte(testonly.FakeCACertPEM)) { t.Fatal("failed to load fake root") } // Validity period: May 13, 2016 - Jul 12, 2019. chain := pemsToDERChain(t, []string{testonly.LeafSignedByFakeIntermediateCertPEM, testonly.FakeIntermediateCertPEM}) validateOpts := CertValidationOpts{ trustedRoots: fakeCARoots, extKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, } beforeValidPeriod := time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC) currentValidPeriod := time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC) afterValidPeriod := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) for _, tc := range []struct { desc string rejectExpired bool rejectUnexpired bool now time.Time wantErr string }{ // No flags: accept anything. { desc: "no-reject-current", now: currentValidPeriod, }, { desc: "no-reject-after", now: afterValidPeriod, }, { desc: "no-reject-before", now: beforeValidPeriod, }, // Reject-Expired: only allow currently-valid and not yet valid { desc: "reject-expired-current", rejectExpired: true, now: currentValidPeriod, }, { desc: "reject-expired-after", rejectExpired: true, now: afterValidPeriod, wantErr: "rejecting expired certificate", }, { desc: "reject-expired-before", rejectExpired: true, now: beforeValidPeriod, }, // Reject-Unexpired: only allow expired { desc: "reject-non-expired-after", rejectUnexpired: true, now: afterValidPeriod, }, { desc: "reject-non-expired-before", rejectUnexpired: true, now: beforeValidPeriod, wantErr: "rejecting unexpired certificate", }, { desc: "reject-non-expired-current", rejectUnexpired: true, now: currentValidPeriod, wantErr: "rejecting unexpired certificate", }, // Reject-Expired AND Reject-Unexpired: nothing allowed { desc: "reject-all-after", rejectExpired: true, rejectUnexpired: true, now: afterValidPeriod, wantErr: "rejecting expired certificate", }, { desc: "reject-all-before", rejectExpired: true, rejectUnexpired: true, now: beforeValidPeriod, wantErr: "rejecting unexpired certificate", }, { desc: "reject-all-current", rejectExpired: true, rejectUnexpired: true, now: currentValidPeriod, wantErr: "rejecting unexpired certificate", }, } { t.Run(tc.desc, func(t *testing.T) { validateOpts.currentTime = tc.now validateOpts.rejectExpired = tc.rejectExpired validateOpts.rejectUnexpired = tc.rejectUnexpired _, err := ValidateChain(chain, validateOpts) if err != nil { if len(tc.wantErr) == 0 { t.Errorf("ValidateChain()=_,%v; want _,nil", err) } else if !strings.Contains(err.Error(), tc.wantErr) { t.Errorf("ValidateChain()=_,%v; want err containing %q", err, tc.wantErr) } } else if len(tc.wantErr) != 0 { t.Errorf("ValidateChain()=_,nil; want err containing %q", tc.wantErr) } }) } } // Builds a chain of DER-encoded certs. // Note: ordering is important func pemsToDERChain(t *testing.T, pemCerts []string) [][]byte { t.Helper() chain := make([][]byte, 0, len(pemCerts)) for _, pemCert := range pemCerts { cert := pemToCert(t, pemCert) chain = append(chain, cert.Raw) } return chain } func pemToCert(t *testing.T, pemData string) *x509.Certificate { t.Helper() bytes, rest := pem.Decode([]byte(pemData)) if len(rest) > 0 { t.Fatalf("Extra data after PEM: %v", rest) return nil } cert, err := x509.ParseCertificate(bytes.Bytes) if x509.IsFatal(err) { t.Fatal(err) } return cert } func pemFileToDERChain(t *testing.T, filename string) [][]byte { t.Helper() rawChain, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") if err != nil { t.Fatalf("failed to load testdata: %v", err) } return rawChain } // Validate a chain including a pre-issuer as produced by Google's Compliance Monitor. func TestCMPreIssuedCert(t *testing.T) { var b64Chain = []string{ "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", } rawChain := make([][]byte, len(b64Chain)) for i, b64Data := range b64Chain { var err error rawChain[i], err = base64.StdEncoding.DecodeString(b64Data) if err != nil { t.Fatalf("failed to base64.Decode(chain[%d]): %v", i, err) } } root, err := x509.ParseCertificate(rawChain[len(rawChain)-1]) if err != nil { t.Fatalf("failed to parse root cert: %v", err) } cmRoot := x509util.NewPEMCertPool() cmRoot.AddCert(root) for _, tc := range []struct { desc string eku []x509.ExtKeyUsage }{ { desc: "no EKU specified", }, { desc: "EKU ServerAuth", eku: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, }, } { t.Run(tc.desc, func(t *testing.T) { opts := CertValidationOpts{ trustedRoots: cmRoot, extKeyUsages: tc.eku, } chain, err := ValidateChain(rawChain, opts) if err != nil { t.Fatalf("failed to ValidateChain: %v", err) } for i, c := range chain { t.Logf("chain[%d] = \n%s", i, x509util.CertificateToString(c)) } }) } }