@@ -18,7 +18,6 @@ import (
1818 "io/ioutil"
1919 "math/big"
2020 "net/http"
21- "net/url"
2221 "time"
2322
2423 "golang.org/x/crypto/ocsp"
@@ -28,9 +27,6 @@ import (
2827var (
2928 tlsFeatureExtensionOID = asn1.ObjectIdentifier {1 , 3 , 6 , 1 , 5 , 5 , 7 , 1 , 24 }
3029 mustStapleFeatureValue = big .NewInt (5 )
31-
32- defaultRequestTimeout = 5 * time .Second
33- errGotOCSPResponse = errors .New ("done" )
3430)
3531
3632// Error represents an OCSP verification error
@@ -126,10 +122,7 @@ func getParsedResponse(ctx context.Context, cfg config, connState tls.Connection
126122 if cfg .disableEndpointChecking {
127123 return nil , nil
128124 }
129- externalResponse , err := contactResponders (ctx , cfg )
130- if err != nil {
131- return nil , err
132- }
125+ externalResponse := contactResponders (ctx , cfg )
133126 if externalResponse == nil {
134127 // None of the responders were available.
135128 return nil , nil
@@ -210,40 +203,33 @@ func isMustStapleCertificate(cert *x509.Certificate) (bool, error) {
210203 return false , nil
211204}
212205
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 {
217211 if len (cfg .serverCert .OCSPServer ) == 0 {
218- return nil , nil
212+ return nil
219213 }
220214
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 ()
238219
239- group , groupCtx := errgroup .WithContext (requestCtx )
220+ group , ctx := errgroup .WithContext (ctx )
240221 ocspResponses := make (chan * ocsp.Response , len (cfg .serverCert .OCSPServer ))
241222 defer close (ocspResponses )
242223
243224 for _ , endpoint := range cfg .serverCert .OCSPServer {
244225 // Re-assign endpoint so it gets re-scoped rather than using the iteration variable in the goroutine. See
245226 // https://golang.org/doc/faq#closures_and_goroutines.
246227 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.
247233 group .Go (func () error {
248234 // Use bytes.NewReader instead of bytes.NewBuffer because a bytes.Buffer is an owning representation and the
249235 // 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
252238 if err != nil {
253239 return nil
254240 }
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 respond.
241+ request = request .WithContext (ctx )
242+
265243 httpResponse , err := http .DefaultClient .Do (request )
266244 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
280246 }
281247 defer func () {
282248 _ = httpResponse .Body .Close ()
283249 }()
250+
284251 if httpResponse .StatusCode != 200 {
285252 return nil
286253 }
@@ -292,26 +259,27 @@ func contactResponders(ctx context.Context, cfg config) (*ResponseDetails, error
292259
293260 ocspResponse , err := ocsp .ParseResponseForCert (httpBytes , cfg .serverCert , cfg .issuer )
294261 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.
297264 return nil
298265 }
299266
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 .
302269 ocspResponses <- ocspResponse
303- return errGotOCSPResponse
270+ return errors . New ( "done" )
304271 })
305272 }
306273
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
313282 }
314- return extractResponseDetails (<- ocspResponses ), nil
315283}
316284
317285// verifyResponse checks that the provided OCSP response is valid.
0 commit comments