@@ -18,7 +18,6 @@ import (
18
18
"io/ioutil"
19
19
"math/big"
20
20
"net/http"
21
- "net/url"
22
21
"time"
23
22
24
23
"golang.org/x/crypto/ocsp"
@@ -28,9 +27,6 @@ import (
28
27
var (
29
28
tlsFeatureExtensionOID = asn1.ObjectIdentifier {1 , 3 , 6 , 1 , 5 , 5 , 7 , 1 , 24 }
30
29
mustStapleFeatureValue = big .NewInt (5 )
31
-
32
- defaultRequestTimeout = 5 * time .Second
33
- errGotOCSPResponse = errors .New ("done" )
34
30
)
35
31
36
32
// Error represents an OCSP verification error
@@ -126,10 +122,7 @@ func getParsedResponse(ctx context.Context, cfg config, connState tls.Connection
126
122
if cfg .disableEndpointChecking {
127
123
return nil , nil
128
124
}
129
- externalResponse , err := contactResponders (ctx , cfg )
130
- if err != nil {
131
- return nil , err
132
- }
125
+ externalResponse := contactResponders (ctx , cfg )
133
126
if externalResponse == nil {
134
127
// None of the responders were available.
135
128
return nil , nil
@@ -210,40 +203,33 @@ func isMustStapleCertificate(cert *x509.Certificate) (bool, error) {
210
203
return false , nil
211
204
}
212
205
213
- // contactResponders will send a request to the OCSP responders reported by cfg.serverCert. The first response that
214
- // conclusively identifies cfg.serverCert as good or revoked will be returned. If all responders are unavailable or no
215
- // responder returns a conclusive status, (nil, nil) will be returned.
216
- func contactResponders (ctx context.Context , cfg config ) (* ResponseDetails , error ) {
206
+ // contactResponders will send a request to all OCSP responders reported by cfg.serverCert. The
207
+ // first response that conclusively identifies cfg.serverCert as good or revoked will be returned.
208
+ // If all responders are unavailable or no responder returns a conclusive status, it returns nil.
209
+ // contactResponders will wait for up to 5 seconds to get a certificate status response.
210
+ func contactResponders (ctx context.Context , cfg config ) * ResponseDetails {
217
211
if len (cfg .serverCert .OCSPServer ) == 0 {
218
- return nil , nil
212
+ return nil
219
213
}
220
214
221
- requestCtx := ctx // Either ctx or a new context derived from ctx with a five second timeout.
222
- userContextUsed := true
223
- var cancelFn context.CancelFunc
224
-
225
- // Use a context with defaultRequestTimeout if ctx does not have a deadline set or the current deadline is further
226
- // out than defaultRequestTimeout. If the current deadline is less than less than defaultRequestTimeout out, respect
227
- // it. Calling context.WithTimeout would do this for us, but we need to know which context we're using.
228
- wantDeadline := time .Now ().Add (defaultRequestTimeout )
229
- if deadline , ok := ctx .Deadline (); ! ok || deadline .After (wantDeadline ) {
230
- userContextUsed = false
231
- requestCtx , cancelFn = context .WithDeadline (ctx , wantDeadline )
232
- }
233
- defer func () {
234
- if cancelFn != nil {
235
- cancelFn ()
236
- }
237
- }()
215
+ // Limit all OCSP responder calls to a maximum of 5 seconds or when the passed-in context expires,
216
+ // whichever happens first.
217
+ ctx , cancel := context .WithTimeout (ctx , 5 * time .Second )
218
+ defer cancel ()
238
219
239
- group , groupCtx := errgroup .WithContext (requestCtx )
220
+ group , ctx := errgroup .WithContext (ctx )
240
221
ocspResponses := make (chan * ocsp.Response , len (cfg .serverCert .OCSPServer ))
241
222
defer close (ocspResponses )
242
223
243
224
for _ , endpoint := range cfg .serverCert .OCSPServer {
244
225
// Re-assign endpoint so it gets re-scoped rather than using the iteration variable in the goroutine. See
245
226
// https://golang.org/doc/faq#closures_and_goroutines.
246
227
endpoint := endpoint
228
+
229
+ // Start a group of goroutines that each attempt to request the certificate status from one
230
+ // of the OCSP endpoints listed in the server certificate. We want to "soft fail" on all
231
+ // errors, so this function never returns actual errors. Only a "done" error is returned
232
+ // when a response is received so the errgroup cancels any other in-progress requests.
247
233
group .Go (func () error {
248
234
// Use bytes.NewReader instead of bytes.NewBuffer because a bytes.Buffer is an owning representation and the
249
235
// docs recommend not using the underlying []byte after creating the buffer, so a new copy of the request
@@ -252,35 +238,16 @@ func contactResponders(ctx context.Context, cfg config) (*ResponseDetails, error
252
238
if err != nil {
253
239
return nil
254
240
}
255
- request = request .WithContext (groupCtx )
256
-
257
- // Execute the request and handle errors as follows:
258
- //
259
- // 1. If the original context expired or was cancelled, propagate the error up so the caller will abort the
260
- // verification and return control to the user.
261
- //
262
- // 2. If any other errors occurred, including the defaultRequestTimeout expiring, or the response has a
263
- // non-200 status code, suppress the error because we want to ignore this responder and wait for a different
264
- // one to responsd.
241
+ request = request .WithContext (ctx )
242
+
265
243
httpResponse , err := http .DefaultClient .Do (request )
266
244
if err != nil {
267
- urlErr , ok := err .(* url.Error )
268
- if ! ok {
269
- return nil
270
- }
271
-
272
- timeout := urlErr .Timeout ()
273
- cancelled := urlErr .Err == context .Canceled // Timeout() does not return true for context.Cancelled.
274
- if cancelled || (userContextUsed && timeout ) {
275
- // Handle the original context expiring or being cancelled. The url.Error type supports Unwrap, so
276
- // users can use errors.Is to check for context errors.
277
- return err
278
- }
279
- return nil // Ignore all other errors.
245
+ return nil
280
246
}
281
247
defer func () {
282
248
_ = httpResponse .Body .Close ()
283
249
}()
250
+
284
251
if httpResponse .StatusCode != 200 {
285
252
return nil
286
253
}
@@ -292,26 +259,27 @@ func contactResponders(ctx context.Context, cfg config) (*ResponseDetails, error
292
259
293
260
ocspResponse , err := ocsp .ParseResponseForCert (httpBytes , cfg .serverCert , cfg .issuer )
294
261
if err != nil || verifyResponse (cfg , ocspResponse ) != nil || ocspResponse .Status == ocsp .Unknown {
295
- // If there was an error parsing/validating the response or the response was inconclusive, suppress
296
- // the error because we want to ignore this responder.
262
+ // If there was an error parsing/validating the response or the response was
263
+ // inconclusive, suppress the error because we want to ignore this responder.
297
264
return nil
298
265
}
299
266
300
- // Store the response and return a sentinel error so the error group will exit and any in-flight requests
301
- // will be cancelled .
267
+ // Send the conclusive response on the response channel and return a "done" error that
268
+ // will cause the errgroup to cancel all other in-progress requests .
302
269
ocspResponses <- ocspResponse
303
- return errGotOCSPResponse
270
+ return errors . New ( "done" )
304
271
})
305
272
}
306
273
307
- if err := group .Wait (); err != nil && err != errGotOCSPResponse {
308
- return nil , err
309
- }
310
- if len (ocspResponses ) == 0 {
311
- // None of the responders gave a conclusive response.
312
- return nil , nil
274
+ _ = group .Wait ()
275
+ select {
276
+ case res := <- ocspResponses :
277
+ return extractResponseDetails (res )
278
+ default :
279
+ // If there is no OCSP response on the response channel, all OCSP calls either failed or
280
+ // were inconclusive. Return nil.
281
+ return nil
313
282
}
314
- return extractResponseDetails (<- ocspResponses ), nil
315
283
}
316
284
317
285
// verifyResponse checks that the provided OCSP response is valid.
0 commit comments