-
-
Notifications
You must be signed in to change notification settings - Fork 293
/
ocsp.go
255 lines (225 loc) · 8.84 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
// Copyright 2015 Matthew Holt
//
// 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 certmagic
import (
"bytes"
"context"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"log"
"net/http"
"time"
"golang.org/x/crypto/ocsp"
)
// ErrNoOCSPServerSpecified indicates that OCSP information could not be
// stapled because the certificate does not support OCSP.
var ErrNoOCSPServerSpecified = errors.New("no OCSP server specified in certificate")
// stapleOCSP staples OCSP information to cert for hostname name.
// If you have it handy, you should pass in the PEM-encoded certificate
// bundle; otherwise the DER-encoded cert will have to be PEM-encoded.
// If you don't have the PEM blocks already, just pass in nil.
//
// If successful, the OCSP response will be set to cert's ocsp field,
// regardless of the OCSP status. It is only stapled, however, if the
// status is Good.
//
// Errors here are not necessarily fatal, it could just be that the
// certificate doesn't have an issuer URL.
func stapleOCSP(ctx context.Context, ocspConfig OCSPConfig, storage Storage, cert *Certificate, pemBundle []byte) error {
if ocspConfig.DisableStapling {
return nil
}
if pemBundle == nil {
// we need a PEM encoding only for some function calls below
bundle := new(bytes.Buffer)
for _, derBytes := range cert.Certificate.Certificate {
pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
}
pemBundle = bundle.Bytes()
}
var ocspBytes []byte
var ocspResp *ocsp.Response
var ocspErr error
var gotNewOCSP bool
// First try to load OCSP staple from storage and see if
// we can still use it.
ocspStapleKey := StorageKeys.OCSPStaple(cert, pemBundle)
cachedOCSP, err := storage.Load(ctx, ocspStapleKey)
if err == nil {
resp, err := ocsp.ParseResponse(cachedOCSP, nil)
if err == nil {
if freshOCSP(resp) {
// staple is still fresh; use it
ocspBytes = cachedOCSP
ocspResp = resp
}
} else {
// invalid contents; delete the file
// (we do this independently of the maintenance routine because
// in this case we know for sure this should be a staple file
// because we loaded it by name, whereas the maintenance routine
// just iterates the list of files, even if somehow a non-staple
// file gets in the folder. in this case we are sure it is corrupt.)
err := storage.Delete(ctx, ocspStapleKey)
if err != nil {
log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err)
}
}
}
// If we couldn't get a fresh staple by reading the cache,
// then we need to request it from the OCSP responder
if ocspResp == nil || len(ocspBytes) == 0 {
ocspBytes, ocspResp, ocspErr = getOCSPForCert(ocspConfig, pemBundle)
// An error here is not a problem because a certificate
// may simply not contain a link to an OCSP server.
if ocspErr != nil {
// For short-lived certificates, this is fine and we can ignore
// logging because OCSP doesn't make much sense for them anyway.
if cert.Lifetime() < 7*24*time.Hour {
return nil
}
// There's nothing else we can do to get OCSP for this certificate,
// so we can return here with the error to warn about it.
return fmt.Errorf("no OCSP stapling for %v: %w", cert.Names, ocspErr)
}
gotNewOCSP = true
}
if ocspResp.NextUpdate.After(expiresAt(cert.Leaf)) {
// uh oh, this OCSP response expires AFTER the certificate does, that's kinda bogus.
// it was the reason a lot of Symantec-validated sites (not Caddy) went down
// in October 2017. https://twitter.com/mattiasgeniar/status/919432824708648961
return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)",
cert.Names, expiresAt(cert.Leaf).Sub(ocspResp.NextUpdate))
}
// Attach the latest OCSP response to the certificate; this is NOT the same
// as stapling it, which we do below only if the status is Good, but it is
// useful to keep with the cert in order to act on it later (like if Revoked).
cert.ocsp = ocspResp
// If the response is good, staple it to the certificate. If the OCSP
// response was not loaded from storage, we persist it for next time.
if ocspResp.Status == ocsp.Good {
cert.Certificate.OCSPStaple = ocspBytes
if gotNewOCSP {
err := storage.Store(ctx, ocspStapleKey, ocspBytes)
if err != nil {
return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
}
}
}
return nil
}
// getOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response,
// the parsed response, and an error, if any. The returned []byte can be passed directly
// into the OCSPStaple property of a tls.Certificate. If the bundle only contains the
// issued certificate, this function will try to get the issuer certificate from the
// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
// values are nil, the OCSP status may be assumed OCSPUnknown.
//
// Borrowed from xenolf.
func getOCSPForCert(ocspConfig OCSPConfig, bundle []byte) ([]byte, *ocsp.Response, error) {
// TODO: Perhaps this should be synchronized too, with a Locker?
certificates, err := parseCertsFromPEMBundle(bundle)
if err != nil {
return nil, nil, err
}
// We expect the certificate slice to be ordered downwards the chain.
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
// which should always be the first two certificates. If there's no
// OCSP server listed in the leaf cert, there's nothing to do. And if
// we have only one certificate so far, we need to get the issuer cert.
issuedCert := certificates[0]
if len(issuedCert.OCSPServer) == 0 {
return nil, nil, ErrNoOCSPServerSpecified
}
// apply override for responder URL
respURL := issuedCert.OCSPServer[0]
if len(ocspConfig.ResponderOverrides) > 0 {
if override, ok := ocspConfig.ResponderOverrides[respURL]; ok {
respURL = override
}
}
if respURL == "" {
return nil, nil, fmt.Errorf("override disables querying OCSP responder: %v", issuedCert.OCSPServer[0])
}
// configure HTTP client if necessary
httpClient := http.DefaultClient
if ocspConfig.HTTPProxy != nil {
httpClient = &http.Client{
Transport: &http.Transport{
Proxy: ocspConfig.HTTPProxy,
},
Timeout: 30 * time.Second,
}
}
// get issuer certificate if needed
if len(certificates) == 1 {
if len(issuedCert.IssuingCertificateURL) == 0 {
return nil, nil, fmt.Errorf("no URL to issuing certificate")
}
resp, err := httpClient.Get(issuedCert.IssuingCertificateURL[0])
if err != nil {
return nil, nil, fmt.Errorf("getting issuer certificate: %v", err)
}
defer resp.Body.Close()
issuerBytes, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
if err != nil {
return nil, nil, fmt.Errorf("reading issuer certificate: %v", err)
}
issuerCert, err := x509.ParseCertificate(issuerBytes)
if err != nil {
return nil, nil, fmt.Errorf("parsing issuer certificate: %v", err)
}
// insert it into the slice on position 0;
// we want it ordered right SRV CRT -> CA
certificates = append(certificates, issuerCert)
}
issuerCert := certificates[1]
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
if err != nil {
return nil, nil, fmt.Errorf("creating OCSP request: %v", err)
}
reader := bytes.NewReader(ocspReq)
req, err := httpClient.Post(respURL, "application/ocsp-request", reader)
if err != nil {
return nil, nil, fmt.Errorf("making OCSP request: %v", err)
}
defer req.Body.Close()
ocspResBytes, err := io.ReadAll(io.LimitReader(req.Body, 1024*1024))
if err != nil {
return nil, nil, fmt.Errorf("reading OCSP response: %v", err)
}
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
if err != nil {
return nil, nil, fmt.Errorf("parsing OCSP response: %v", err)
}
return ocspResBytes, ocspRes, nil
}
// freshOCSP returns true if resp is still fresh,
// meaning that it is not expedient to get an
// updated response from the OCSP server.
func freshOCSP(resp *ocsp.Response) bool {
nextUpdate := resp.NextUpdate
// If there is an OCSP responder certificate, and it expires before the
// OCSP response, use its expiration date as the end of the OCSP
// response's validity period.
if resp.Certificate != nil && resp.Certificate.NotAfter.Before(nextUpdate) {
nextUpdate = resp.Certificate.NotAfter
}
// start checking OCSP staple about halfway through validity period for good measure
refreshTime := resp.ThisUpdate.Add(nextUpdate.Sub(resp.ThisUpdate) / 2)
return time.Now().Before(refreshTime)
}