forked from google/certificate-transparency-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fix.go
254 lines (227 loc) · 7.9 KB
/
fix.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
// 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 holds code to help fix the validation chains for certificates.
package fixchain
import (
"encoding/pem"
"net/http"
"github.com/google/certificate-transparency-go/x509"
)
// Fix attempts to fix the certificate chain for the certificate that is passed
// to it, with respect to the given roots. Fix returns a list of successfully
// constructed chains, and a list of errors it encountered along the way. The
// presence of FixErrors does not mean the fix was unsuccessful. Callers should
// check for returned chains to determine success.
func Fix(cert *x509.Certificate, chain []*x509.Certificate, roots *x509.CertPool, client *http.Client) ([][]*x509.Certificate, []*FixError) {
fix := &toFix{
cert: cert,
chain: newDedupedChain(chain),
roots: roots,
cache: newURLCache(client, false),
}
return fix.handleChain()
}
const maxChainLength = 20
type toFix struct {
cert *x509.Certificate
chain *dedupedChain
roots *x509.CertPool
opts *x509.VerifyOptions
cache *urlCache
}
func (fix *toFix) handleChain() ([][]*x509.Certificate, []*FixError) {
intermediates := x509.NewCertPool()
for _, c := range fix.chain.certs {
intermediates.AddCert(c)
}
fix.opts = &x509.VerifyOptions{
Intermediates: intermediates,
Roots: fix.roots,
DisableTimeChecks: true,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}
var retferrs []*FixError
chains, ferrs := fix.constructChain()
if ferrs != nil {
retferrs = append(retferrs, ferrs...)
chains, ferrs = fix.fixChain()
if ferrs != nil {
retferrs = append(retferrs, ferrs...)
}
}
return chains, retferrs
}
func (fix *toFix) constructChain() ([][]*x509.Certificate, []*FixError) {
chains, err := fix.cert.Verify(*fix.opts)
if err != nil {
return chains, []*FixError{
{
Type: VerifyFailed,
Cert: fix.cert,
Chain: fix.chain.certs,
Error: err,
},
}
}
return chains, nil
}
// toFix.fixChain() tries to fix the certificate chain in the toFix struct for
// the cert in the toFix struct wrt the roots in the toFix struct.
// toFix.fixChain() uses the opts provided in the toFix struct to verify the
// chain, and uses the cache in the toFix struct to go and get any potentially
// missing intermediate certs.
// toFix.fixChain() returns a slice of valid and verified chains for this cert
// to the roots in the toFix struct, and a slice of the errors encountered
// during the fixing process.
func (fix *toFix) fixChain() ([][]*x509.Certificate, []*FixError) {
var retferrs []*FixError
// Ensure the leaf certificate is included as part of the certificate chain.
dchain := *fix.chain
dchain.addCertToFront(fix.cert)
explored := make([]bool, len(dchain.certs))
lookup := make(map[[hashSize]byte]int)
for i, cert := range dchain.certs {
lookup[hash(cert)] = i
}
// For each certificate in the given certificate chain...
for i, cert := range dchain.certs {
// If the chains from this certificate have already been built and
// added to the pool of intermediates, skip.
if explored[i] {
continue
}
seen := make(map[[hashSize]byte]bool)
// Build all the chains possible that begin from this certificate,
// and add each certificate found along the way to the pool of
// intermediates against which to verify fix.cert. If the addition of
// these intermediates causes chains for fix.cert to be verified,
// fix.augmentIntermediates() will return those chains.
chains, ferrs := fix.augmentIntermediates(cert, 1, seen)
if ferrs != nil {
retferrs = append(retferrs, ferrs...)
}
// If adding certs from the chains steming from this cert resulted in
// successful verification of chains for fix.cert to fix.root, return
// the chains.
if chains != nil {
return chains, retferrs
}
// Mark any seen certs that match certs in the original chain as already
// explored.
for certHash := range seen {
index, ok := lookup[certHash]
if ok {
explored[index] = true
}
}
}
return nil, append(retferrs, &FixError{
Type: FixFailed,
Cert: fix.cert,
Chain: fix.chain.certs,
})
}
// TODO(katjoyce): Extend fixing algorithm to build all of the chains for
// toFix.cert and log all of the resulting intermediates.
// toFix.augmentIntermediates() builds all possible chains that stem from the
// given cert, and adds every certificate it finds in these chains to the pool
// of intermediate certs in toFix.opts. Every time a new certificate is added
// to this pool, it tries to re-verify toFix.cert wrt toFix.roots.
// If this verification is ever successful, toFix.augmentIntermediates() returns
// the verified chains for toFix.cert wrt toFix.roots. Also returned are any
// errors that were encountered along the way.
//
// toFix.augmentIntermediates() builds all possible chains from cert by using a
// recursive algorithm on the urls in the AIA information of each certificate
// discovered. length represents the position of the current given cert in the
// larger chain, and is used to impose a max length to which chains can be
// explored. seen is a slice in which all certs that are encountered during the
// search are noted down.
func (fix *toFix) augmentIntermediates(cert *x509.Certificate, length int, seen map[[hashSize]byte]bool) ([][]*x509.Certificate, []*FixError) {
// If this cert takes the chain past maxChainLength, or if this cert has
// already been explored, return.
if length > maxChainLength || seen[hash(cert)] {
return nil, nil
}
// Mark this cert as already explored.
seen[hash(cert)] = true
// Add this cert to the pool of intermediates. If this results in successful
// verification of one or more chains for fix.cert, return the chains.
fix.opts.Intermediates.AddCert(cert)
chains, err := fix.cert.Verify(*fix.opts)
if err == nil {
return chains, nil
}
// For each url in the AIA information of cert, get the corresponding
// certificates and recursively build the chains from those certificates,
// adding every cert to the pool of intermdiates, running the verifier at
// every cert addition, and returning verified chains of fix.cert as soon
// as thay are found.
var retferrs []*FixError
for _, url := range cert.IssuingCertificateURL {
icerts, ferr := fix.getIntermediates(url)
if ferr != nil {
retferrs = append(retferrs, ferr)
}
for _, icert := range icerts {
chains, ferrs := fix.augmentIntermediates(icert, length+1, seen)
if ferrs != nil {
retferrs = append(retferrs, ferrs...)
}
if chains != nil {
return chains, retferrs
}
}
}
return nil, retferrs
}
// Get the certs that correspond to the given url.
func (fix *toFix) getIntermediates(url string) ([]*x509.Certificate, *FixError) {
var icerts []*x509.Certificate
// PKCS#7 additions as (at time of writing) there is no standard Go PKCS#7
// implementation
r := urlReplacement(url)
if r != nil {
return r, nil
}
body, err := fix.cache.getURL(url)
if err != nil {
return nil, &FixError{
Type: CannotFetchURL,
Cert: fix.cert,
Chain: fix.chain.certs,
URL: url,
Error: err,
}
}
icert, err := x509.ParseCertificate(body)
if err != nil {
s, _ := pem.Decode(body)
if s != nil {
icert, err = x509.ParseCertificate(s.Bytes)
}
}
if err != nil {
return nil, &FixError{
Type: ParseFailure,
Cert: fix.cert,
Chain: fix.chain.certs,
URL: url,
Bad: body,
Error: err,
}
}
icerts = append(icerts, icert)
return icerts, nil
}