forked from google/certificate-transparency-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fixer.go
269 lines (247 loc) · 7.64 KB
/
fixer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// Copyright 2016 Google Inc. 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)
}
type chainSlice struct {
chains [][]*x509.Certificate
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// sort.Sort(data Interface) for chainSlice - uses data.Len, data.Less & data.Swap.
// Sort will sort the chains contained within the chainSlice. The chains will
// be sorted 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
func (c chainSlice) Len() int { return len(c.chains) }
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)
}
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
}