-
Notifications
You must be signed in to change notification settings - Fork 883
/
ocsp.go
353 lines (308 loc) · 11.9 KB
/
ocsp.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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
// Copyright (C) MongoDB, Inc. 2017-present.
//
// 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
package ocsp
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/url"
"time"
"golang.org/x/crypto/ocsp"
"golang.org/x/sync/errgroup"
)
var (
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
mustStapleFeatureValue = big.NewInt(5)
defaultRequestTimeout = 5 * time.Second
errGotOCSPResponse = errors.New("done")
)
// Error represents an OCSP verification error
type Error struct {
wrapped error
}
// Error implements the error interface
func (e *Error) Error() string {
return fmt.Sprintf("OCSP verification failed: %v", e.wrapped)
}
// Unwrap returns the underlying error.
func (e *Error) Unwrap() error {
return e.wrapped
}
func newOCSPError(wrapped error) error {
return &Error{wrapped: wrapped}
}
// ResponseDetails contains a subset of the details needed from an OCSP response after the original response has been
// validated.
type ResponseDetails struct {
Status int
NextUpdate time.Time
}
func extractResponseDetails(res *ocsp.Response) *ResponseDetails {
return &ResponseDetails{
Status: res.Status,
NextUpdate: res.NextUpdate,
}
}
// Verify performs OCSP verification for the provided ConnectionState instance.
func Verify(ctx context.Context, connState tls.ConnectionState, opts *VerifyOptions) error {
if opts.Cache == nil {
// There should always be an OCSP cache. Even if the user has specified the URI option to disable communication
// with OCSP responders, the driver will cache any stapled responses. Requiring that the cache is non-nil
// allows us to confirm that the cache is correctly being passed down from a higher level.
return newOCSPError(errors.New("no OCSP cache provided"))
}
if len(connState.VerifiedChains) == 0 {
return newOCSPError(errors.New("no verified certificate chains reported after TLS handshake"))
}
certChain := connState.VerifiedChains[0]
if numCerts := len(certChain); numCerts == 0 {
return newOCSPError(errors.New("verified chain contained no certificates"))
}
ocspCfg, err := newConfig(certChain, opts)
if err != nil {
return newOCSPError(err)
}
res, err := getParsedResponse(ctx, ocspCfg, connState)
if err != nil {
return err
}
if res == nil {
// If no response was parsed from the staple and responders, the status of the certificate is unknown, so don't
// error.
return nil
}
if res.Status == ocsp.Revoked {
return newOCSPError(errors.New("certificate is revoked"))
}
return nil
}
// getParsedResponse attempts to parse a response from the stapled OCSP data or by contacting OCSP responders if no
// staple is present.
func getParsedResponse(ctx context.Context, cfg config, connState tls.ConnectionState) (*ResponseDetails, error) {
stapledResponse, err := processStaple(cfg, connState.OCSPResponse)
if err != nil {
return nil, err
}
if stapledResponse != nil {
// If there is a staple, attempt to cache it. The cache.Update call will resolve conflicts with an existing
// cache enry if necessary.
return cfg.cache.Update(cfg.ocspRequest, stapledResponse), nil
}
if cachedResponse := cfg.cache.Get(cfg.ocspRequest); cachedResponse != nil {
return cachedResponse, nil
}
// If there is no stapled or cached response, fall back to querying the responders if that functionality has not
// been disabled.
if cfg.disableEndpointChecking {
return nil, nil
}
externalResponse, err := contactResponders(ctx, cfg)
if err != nil {
return nil, err
}
if externalResponse == nil {
// None of the responders were available.
return nil, nil
}
// Similar to the stapled response case above, unconditionally call Update and it will either cache the response
// or resolve conflicts if a different connection has cached a response since the previous call to Get.
return cfg.cache.Update(cfg.ocspRequest, externalResponse), nil
}
// processStaple returns the OCSP response from the provided staple. An error will be returned if any of the following
// are true:
//
// 1. cfg.serverCert has the Must-Staple extension but the staple is empty.
// 2. The staple is malformed.
// 3. The staple does not cover cfg.serverCert.
// 4. The OCSP response has an error status.
func processStaple(cfg config, staple []byte) (*ResponseDetails, error) {
mustStaple, err := isMustStapleCertificate(cfg.serverCert)
if err != nil {
return nil, err
}
// If the server has a Must-Staple certificate and the server does not present a stapled OCSP response, error.
if mustStaple && len(staple) == 0 {
return nil, errors.New("server provided a certificate with the Must-Staple extension but did not " +
"provde a stapled OCSP response")
}
if len(staple) == 0 {
return nil, nil
}
parsedResponse, err := ocsp.ParseResponseForCert(staple, cfg.serverCert, cfg.issuer)
if err != nil {
// If the stapled response could not be parsed correctly, error. This can happen if the response is malformed,
// the response does not cover the certificate presented by the server, or if the response contains an error
// status.
return nil, fmt.Errorf("error parsing stapled response: %v", err)
}
if err = verifyResponse(cfg, parsedResponse); err != nil {
return nil, fmt.Errorf("error validating stapled response: %v", err)
}
return extractResponseDetails(parsedResponse), nil
}
// isMustStapleCertificate determines whether or not an X509 certificate is a must-staple certificate.
func isMustStapleCertificate(cert *x509.Certificate) (bool, error) {
var featureExtension pkix.Extension
var foundExtension bool
for _, ext := range cert.Extensions {
if ext.Id.Equal(tlsFeatureExtensionOID) {
featureExtension = ext
foundExtension = true
break
}
}
if !foundExtension {
return false, nil
}
// The value for the TLS feature extension is a sequence of integers. Per the asn1.Unmarshal documentation, an
// integer can be unmarshalled into an int, int32, int64, or *big.Int and unmarshalling will error if the integer
// cannot be encoded into the target type.
//
// Use []*big.Int to ensure that all values in the sequence can be successfully unmarshalled.
var featureValues []*big.Int
if _, err := asn1.Unmarshal(featureExtension.Value, &featureValues); err != nil {
return false, fmt.Errorf("error unmarshalling TLS feature extension values: %v", err)
}
for _, value := range featureValues {
if value.Cmp(mustStapleFeatureValue) == 0 {
return true, nil
}
}
return false, nil
}
// contactResponders will send a request to the OCSP responders reported by cfg.serverCert. The first response that
// conclusively identifies cfg.serverCert as good or revoked will be returned. If all responders are unavailable or no
// responder returns a conclusive status, (nil, nil) will be returned.
func contactResponders(ctx context.Context, cfg config) (*ResponseDetails, error) {
if len(cfg.serverCert.OCSPServer) == 0 {
return nil, nil
}
requestCtx := ctx // Either ctx or a new context derived from ctx with a five second timeout.
userContextUsed := true
var cancelFn context.CancelFunc
// Use a context with defaultRequestTimeout if ctx does not have a deadline set or the current deadline is further
// out than defaultRequestTimeout. If the current deadline is less than less than defaultRequestTimeout out, respect
// it. Calling context.WithTimeout would do this for us, but we need to know which context we're using.
wantDeadline := time.Now().Add(defaultRequestTimeout)
if deadline, ok := ctx.Deadline(); !ok || deadline.After(wantDeadline) {
userContextUsed = false
requestCtx, cancelFn = context.WithDeadline(ctx, wantDeadline)
}
defer func() {
if cancelFn != nil {
cancelFn()
}
}()
group, groupCtx := errgroup.WithContext(requestCtx)
ocspResponses := make(chan *ocsp.Response, len(cfg.serverCert.OCSPServer))
defer close(ocspResponses)
for _, endpoint := range cfg.serverCert.OCSPServer {
// Re-assign endpoint so it gets re-scoped rather than using the iteration variable in the goroutine. See
// https://golang.org/doc/faq#closures_and_goroutines.
endpoint := endpoint
group.Go(func() error {
// Use bytes.NewReader instead of bytes.NewBuffer because a bytes.Buffer is an owning representation and the
// docs recommend not using the underlying []byte after creating the buffer, so a new copy of the request
// bytes would be needed for each request.
request, err := http.NewRequest("POST", endpoint, bytes.NewReader(cfg.ocspRequestBytes))
if err != nil {
return nil
}
request = request.WithContext(groupCtx)
// Execute the request and handle errors as follows:
//
// 1. If the original context expired or was cancelled, propagate the error up so the caller will abort the
// verification and return control to the user.
//
// 2. If any other errors occurred, including the defaultRequestTimeout expiring, or the response has a
// non-200 status code, suppress the error because we want to ignore this responder and wait for a different
// one to responsd.
httpResponse, err := http.DefaultClient.Do(request)
if err != nil {
urlErr, ok := err.(*url.Error)
if !ok {
return nil
}
timeout := urlErr.Timeout()
cancelled := urlErr.Err == context.Canceled // Timeout() does not return true for context.Cancelled.
if cancelled || (userContextUsed && timeout) {
// Handle the original context expiring or being cancelled. The url.Error type supports Unwrap, so
// users can use errors.Is to check for context errors.
return err
}
return nil // Ignore all other errors.
}
defer func() {
_ = httpResponse.Body.Close()
}()
if httpResponse.StatusCode != 200 {
return nil
}
httpBytes, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return nil
}
ocspResponse, err := ocsp.ParseResponseForCert(httpBytes, cfg.serverCert, cfg.issuer)
if err != nil || verifyResponse(cfg, ocspResponse) != nil || ocspResponse.Status == ocsp.Unknown {
// If there was an error parsing/validating the response or the response was inconclusive, suppress
// the error because we want to ignore this responder.
return nil
}
// Store the response and return a sentinel error so the error group will exit and any in-flight requests
// will be cancelled.
ocspResponses <- ocspResponse
return errGotOCSPResponse
})
}
if err := group.Wait(); err != nil && err != errGotOCSPResponse {
return nil, err
}
if len(ocspResponses) == 0 {
// None of the responders gave a conclusive response.
return nil, nil
}
return extractResponseDetails(<-ocspResponses), nil
}
// verifyResponse checks that the provided OCSP response is valid.
func verifyResponse(cfg config, res *ocsp.Response) error {
if err := verifyExtendedKeyUsage(cfg, res); err != nil {
return err
}
currTime := time.Now().UTC()
if res.ThisUpdate.After(currTime) {
return fmt.Errorf("reported thisUpdate time %s is after current time %s", res.ThisUpdate, currTime)
}
if !res.NextUpdate.IsZero() && res.NextUpdate.Before(currTime) {
return fmt.Errorf("reported nextUpdate time %s is before current time %s", res.NextUpdate, currTime)
}
return nil
}
func verifyExtendedKeyUsage(cfg config, res *ocsp.Response) error {
if res.Certificate == nil {
return nil
}
namesMatch := res.RawResponderName != nil && bytes.Equal(res.RawResponderName, cfg.issuer.RawSubject)
keyHashesMatch := res.ResponderKeyHash != nil && bytes.Equal(res.ResponderKeyHash, cfg.ocspRequest.IssuerKeyHash)
if namesMatch || keyHashesMatch {
// The responder certificate is the same as the issuer certificate.
return nil
}
// There is a delegate.
for _, extKeyUsage := range res.Certificate.ExtKeyUsage {
if extKeyUsage == x509.ExtKeyUsageOCSPSigning {
return nil
}
}
return errors.New("delegate responder certificate is missing the OCSP signing extended key usage")
}