// 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 ( "bytes" "log" "net/http" "sort" "sync" "sync/atomic" "time" "github.com/google/certificate-transparency-go/x509" ) // Fixer contains methods to asynchronously fix certificate chains and // properties to store information about each attempt that is made to fix a // certificate chain. type Fixer struct { toFix chan *toFix chains chan<- []*x509.Certificate // Chains successfully fixed by the fixer errors chan<- *FixError active uint32 reconstructed uint32 notReconstructed uint32 fixed uint32 notFixed uint32 validChainsProduced uint32 validChainsOut uint32 wg sync.WaitGroup cache *urlCache } // QueueChain adds the given cert and chain to the queue to be fixed by the // fixer, with respect to the given roots. Note: chain is expected to be in the // order of cert --> root. func (f *Fixer) QueueChain(cert *x509.Certificate, chain []*x509.Certificate, roots *x509.CertPool) { f.toFix <- &toFix{ cert: cert, chain: newDedupedChain(chain), roots: roots, cache: f.cache, } } // Wait for all the fixer workers to finish. func (f *Fixer) Wait() { close(f.toFix) f.wg.Wait() } func (f *Fixer) updateCounters(chains [][]*x509.Certificate, ferrs []*FixError) { atomic.AddUint32(&f.validChainsProduced, uint32(len(chains))) var verifyFailed bool var fixFailed bool for _, ferr := range ferrs { switch ferr.Type { case VerifyFailed: verifyFailed = true case FixFailed: fixFailed = true } } // No errors --> reconstructed // VerifyFailed --> notReconstructed // VerifyFailed but no FixFailed --> fixed // VerifyFailed and FixFailed --> notFixed if verifyFailed { atomic.AddUint32(&f.notReconstructed, 1) // FixFailed error will only be present if a VerifyFailed error is, as // fixChain() is only called if constructChain() fails. if fixFailed { atomic.AddUint32(&f.notFixed, 1) return } atomic.AddUint32(&f.fixed, 1) return } atomic.AddUint32(&f.reconstructed, 1) } // chainSlice contains chains of certificates. Applying Sort will sort in // order of the first certificate in the chain, i.e. their leaf certificate. // If two chains have equal leaf certificates, they will be sorted by the // second certificate in the chain, and so on. By this logic, a chain that // is a subchain of another chain beginning at the leaf of the other chain, // will come before the other chain after sorting. // // Example: // // Before sorting: // A -> B -> C // D // A -> C // A -> B // // After sorting: // A -> B // A -> B -> C // A -> C // D type chainSlice struct { chains [][]*x509.Certificate } func min(a, b int) int { if a < b { return a } return b } // Len implements sort.Sort(data Interface) for chainSlice. func (c chainSlice) Len() int { return len(c.chains) } // Less implements sort.Sort interface for chainSlice. func (c chainSlice) Less(i, j int) bool { chi := c.chains[i] chj := c.chains[j] for k := 0; k < min(len(chi), len(chj)); k++ { if !chi[k].Equal(chj[k]) { return bytes.Compare(chi[k].Raw, chj[k].Raw) < 0 } } return len(chi) < len(chj) } // Swap implements sort.Sort interface for chainSlice. func (c chainSlice) Swap(i, j int) { t := c.chains[i] c.chains[i] = c.chains[j] c.chains[j] = t } // removeSuperChains will remove super chains from the list of chains passed to // it. A super chain is considered to be a chain whose first x certificates are // included in the list somewhere else as a whole chain. Put another way, if // there exists a chain A in the list, and another chain B that is A with some // additional certificates chained onto the end, B is a super chain of A // (and A is a subchain of B). // // Examples: // 1. A -> B -> C is a super chain of A -> B, and both are super chains of A. // 2. Z -> A -> B is not a super chain of A -> B, as A -> B is not at the // beginning of Z -> A -> B. // 3. Calling removeSuperChains on: // A -> B -> C // A -> C // A -> B // A -> C -> D // will return: // A -> B // A -> C // 4. Calling removeSuperChains on: // A -> B -> C // A -> C // A -> B // A -> C -> D // A // will return: // A func removeSuperChains(chains [][]*x509.Certificate) [][]*x509.Certificate { // Sort the list of chains using the sorting algorithm described above. // This will result in chains and their super chains being grouped together // in the list, with the shortest chain listed first in the group (i.e. a // chain, and then all its super chains - if any - listed directly after // that chain). c := chainSlice{chains: chains} sort.Sort(c) var retChains [][]*x509.Certificate NextChain: // Start at the beginning of the list. for i := 0; i < len(c.chains); { // Add the chain to the list of chains to be returned. retChains = append(retChains, c.chains[i]) // Step past any super chains of the chain just added to the return list, // without adding them to the return list. We do not want super chains // of other chains in our return list. Due to the initial sort of the // list, any super chains of a chain will come directly after said chain. for j := i + 1; j < len(c.chains); j++ { for k := range c.chains[i] { // When a chain that is not a super chain of the chain most // recently added to the return list is found, move to that // chain and start over. if !c.chains[i][k].Equal(c.chains[j][k]) { i = j continue NextChain } } } break } return retChains } func (f *Fixer) fixServer() { defer f.wg.Done() for fix := range f.toFix { atomic.AddUint32(&f.active, 1) chains, ferrs := fix.handleChain() f.updateCounters(chains, ferrs) for _, ferr := range ferrs { f.errors <- ferr } // If handleChain() outputs valid chains that are subchains of other // valid chains, (where the subchains start at the leaf) // e.g. A -> B -> C and A -> B -> C -> D, only forward on the shorter // of the chains. for _, chain := range removeSuperChains(chains) { f.chains <- chain atomic.AddUint32(&f.validChainsOut, 1) } atomic.AddUint32(&f.active, ^uint32(0)) } } func (f *Fixer) newFixServerPool(workerCount int) { for i := 0; i < workerCount; i++ { f.wg.Add(1) go f.fixServer() } } func (f *Fixer) logStats() { t := time.NewTicker(time.Second) go func() { for range t.C { log.Printf("fixers: %d active, %d reconstructed, "+ "%d not reconstructed, %d fixed, %d not fixed, "+ "%d valid chains produced, %d valid chains sent", f.active, f.reconstructed, f.notReconstructed, f.fixed, f.notFixed, f.validChainsProduced, f.validChainsOut) } }() } // NewFixer creates a new asynchronous fixer and starts up a pool of // workerCount workers. Errors are pushed to the errors channel, and fixed // chains are pushed to the chains channel. client is used to try to get any // missing certificates that are needed when attempting to fix chains. func NewFixer(workerCount int, chains chan<- []*x509.Certificate, errors chan<- *FixError, client *http.Client, logStats bool) *Fixer { f := &Fixer{ toFix: make(chan *toFix), chains: chains, errors: errors, cache: newURLCache(client, logStats), } f.newFixServerPool(workerCount) if logStats { f.logStats() } return f }