// 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 fixchain import ( "context" "net/http" "strings" "sync" "testing" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/certificate-transparency-go/x509" ) var newFixAndLogTests = []fixAndLogTest{ // Tests that add chains to the FixAndLog one at a time using QueueChain() { // Full chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { // Chain without the root successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate}, function: "QueueChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, }, { // Chain to wrong root results in error. url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, comodoIntermediate, comodoRoot}, function: "QueueChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // Chain without correct root containing loop results in error. url: "https://ct.googleapis.com/pilot", chain: []string{testC, testB, testA}, function: "QueueChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // Incomplete chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf}, function: "QueueChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, expectedErrs: []errorType{VerifyFailed}, }, { url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf}, function: "QueueChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, expectedErrs: []errorType{VerifyFailed}, }, { // Garbled chain (with a leaf that has no chain to our roots) results in an error. url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueChain", expectedErrs: []errorType{VerifyFailed, FixFailed}, }, { // Garbled chain (with a leaf that has a chain to our roots) successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf, megaLeaf, googleLeaf, thawteIntermediate, comodoRoot}, function: "QueueChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, expectedErrs: []errorType{VerifyFailed}, }, // Tests that add chains to the FixAndLog using QueueAllCertsInChain() { // Full chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, {"VeriSign"}, }, }, { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, }, }, { // Chain to wrong root results errors. url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, comodoIntermediate, comodoRoot}, function: "QueueAllCertsInChain", expectedErrs: []errorType{ VerifyFailed, FixFailed, VerifyFailed, FixFailed, VerifyFailed, FixFailed, }, }, { // Chain without correct root containing loop results in error. url: "https://ct.googleapis.com/pilot", chain: []string{testC, testB, testA}, function: "QueueAllCertsInChain", expectedErrs: []errorType{ VerifyFailed, FixFailed, VerifyFailed, FixFailed, VerifyFailed, FixFailed, }, }, { // Incomplete chain successfully logged. url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, }, expectedErrs: []errorType{VerifyFailed}, }, { url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, }, expectedErrs: []errorType{VerifyFailed}, }, { // Garbled chain (with a leaf that has no chain to our roots) url: "https://ct.googleapis.com/pilot", chain: []string{megaLeaf, googleLeaf, thawteIntermediate, verisignRoot}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, {"VeriSign"}, }, expectedErrs: []errorType{ VerifyFailed, FixFailed, }, }, { // Garbled chain (with a leaf that has a chain to our roots) url: "https://ct.googleapis.com/pilot", chain: []string{testLeaf, megaLeaf, googleLeaf, thawteIntermediate, comodoRoot}, function: "QueueAllCertsInChain", expLoggedChains: [][]string{ {"Leaf", "Intermediate2", "Intermediate1", "CA"}, {"Google", "Thawte", "VeriSign"}, {"Thawte", "VeriSign"}, }, expectedErrs: []errorType{ VerifyFailed, VerifyFailed, FixFailed, VerifyFailed, FixFailed, }, }, } func TestNewFixAndLog(t *testing.T) { // Test that expected chains are logged when adding a chain using QueueChain() ctx := context.Background() for i, test := range newFixAndLogTests { seen := make([]bool, len(test.expLoggedChains)) errors := make(chan *FixError) c := &http.Client{Transport: &testRoundTripper{t: t, test: &test, testIndex: i, seen: seen}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } fl := NewFixAndLog(ctx, 1, 1, errors, c, logClient, newNilLimiter(), false) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() testErrors(t, i, test.expectedErrs, errors) }() switch test.function { case "QueueChain": fl.QueueChain(extractTestChain(t, i, test.chain)) case "QueueAllCertsInChain": fl.QueueAllCertsInChain(extractTestChain(t, i, test.chain)) } fl.Wait() close(errors) wg.Wait() // Check that no chains that were expected to be logged were not. for j, val := range seen { if !val { t.Errorf("#%d: Expected chain was not logged: %s", i, strings.Join(test.expLoggedChains[j], " -> ")) } } } } var fixAndLogQueueTests = []fixAndLogTest{ { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, thawteIntermediate, verisignRoot}, expectedCert: "Google", expectedChain: []string{"Google", "Thawte", "VeriSign"}, expectedRoots: []string{verisignRoot, testRoot}, }, { url: "https://ct.googleapis.com/pilot", chain: []string{googleLeaf, googleLeaf, thawteIntermediate, verisignRoot}, expectedCert: "Google", expectedChain: []string{"Google", "Thawte", "VeriSign"}, expectedRoots: []string{verisignRoot, testRoot}, }, { // Test passing a nil chain to FixAndLog.QueueChain() url: "https://ct.googleapis.com/pilot", }, } func testQueueAllCertsInChain(t *testing.T, i int, test *fixAndLogTest, fl *FixAndLog) { defer fl.wg.Done() seen := make([]bool, len(test.expectedChain)) NextToFix: for fix := range fl.fixer.toFix { // Check fix.chain is the chain that's expected. matchTestChain(t, i, test.expectedChain, fix.chain.certs) //Check fix.roots are the roots that are expected for the given url. matchTestRoots(t, i, test.expectedRoots, fix.roots) for j, expCert := range test.expectedChain { if seen[j] { continue } if strings.Contains(nameToKey(&fix.cert.Subject), expCert) { seen[j] = true continue NextToFix } } t.Errorf("#%d: Queued certificate %s was not expected", i, nameToKey(&fix.cert.Subject)) } for j, val := range seen { if !val { t.Errorf("#%d: Expected certificate %s was not queued", i, test.expectedChain[j]) } } } func TestQueueAllCertsInChain(t *testing.T) { ctx := context.Background() for i, test := range fixAndLogQueueTests { f := &Fixer{toFix: make(chan *toFix)} c := &http.Client{Transport: &testRoundTripper{}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := &Logger{ ctx: ctx, client: logClient, postCertCache: newLockedMap(), } fl := &FixAndLog{fixer: f, chains: make(chan []*x509.Certificate), logger: l, done: newLockedMap()} fl.wg.Add(1) go testQueueAllCertsInChain(t, i, &test, fl) fl.QueueAllCertsInChain(extractTestChain(t, i, test.chain)) fl.Wait() } } func testFixAndLogQueueChain(t *testing.T, i int, test *fixAndLogTest, fl *FixAndLog) { defer fl.wg.Done() fix, ok := <-fl.fixer.toFix if ok { // Check fix.cert is the cert that's expected. if !strings.Contains(nameToKey(&fix.cert.Subject), test.expectedCert) { t.Errorf("#%d: Expected cert does not match queued cert", i) } // Check fix.chain is the chain that's expected. matchTestChain(t, i, test.expectedChain, fix.chain.certs) //Check fix.roots are the roots that are expected for the given url. matchTestRoots(t, i, test.expectedRoots, fix.roots) } } func TestFixAndLogQueueChain(t *testing.T) { ctx := context.Background() for i, test := range fixAndLogQueueTests { f := &Fixer{toFix: make(chan *toFix)} c := &http.Client{Transport: &testRoundTripper{}} logClient, err := client.New(test.url, c, jsonclient.Options{}) if err != nil { t.Fatalf("failed to create LogClient: %v", err) } l := &Logger{ ctx: ctx, client: logClient, postCertCache: newLockedMap(), } fl := &FixAndLog{fixer: f, chains: make(chan []*x509.Certificate), logger: l, done: newLockedMap()} fl.wg.Add(1) go testFixAndLogQueueChain(t, i, &test, fl) fl.QueueChain(extractTestChain(t, i, test.chain)) fl.Wait() } }