forked from cloudflare/cfssl
/
revoke.go
281 lines (238 loc) · 6.5 KB
/
revoke.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
// Package revoke provides functionality for checking the validity of
// a cert. Specifically, the temporal validity of the certificate is
// checked first, then any CRL in the cert is checked. OCSP is not
// supported at this time.
package revoke
import (
"bytes"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/pem"
"errors"
"io/ioutil"
"net/http"
neturl "net/url"
"time"
"golang.org/x/crypto/ocsp"
"github.com/bbandix/cfssl/helpers"
"github.com/bbandix/cfssl/log"
)
// HardFail determines whether the failure to check the revocation
// status of a certificate (i.e. due to network failure) causes
// verification to fail (a hard failure).
var HardFail = false
// TODO (kyle): figure out a good mechanism for OCSP; this requires
// presenting both the certificate and the issuer, and we don't have a
// good way at this time of getting the issuer.
// CRLSet associates a PKIX certificate list with the URL the CRL is
// fetched from.
var CRLSet = map[string]*pkix.CertificateList{}
// We can't handle LDAP certificates, so this checks to see if the
// URL string points to an LDAP resource so that we can ignore it.
func ldapURL(url string) bool {
u, err := neturl.Parse(url)
if err != nil {
log.Warningf("error parsing url %s: %v", url, err)
return false
}
if u.Scheme == "ldap" {
return true
}
return false
}
// revCheck should check the certificate for any revocations. It
// returns a pair of booleans: the first indicates whether the certificate
// is revoked, the second indicates whether the revocations were
// successfully checked.. This leads to the following combinations:
//
// false, false: an error was encountered while checking revocations.
//
// false, true: the certificate was checked successfully and
// it is not revoked.
//
// true, true: the certificate was checked successfully and
// it is revoked.
//
// true, false: failure to check revocation status causes
// verification to fail
func revCheck(cert *x509.Certificate) (revoked, ok bool) {
for _, url := range cert.CRLDistributionPoints {
if ldapURL(url) {
log.Infof("skipping LDAP CRL: %s", url)
continue
}
if revoked, ok := certIsRevokedCRL(cert, url); !ok {
log.Warning("error checking revocation via CRL")
if HardFail {
return true, false
}
return false, false
} else if revoked {
log.Info("certificate is revoked via CRL")
return true, true
}
if revoked, ok := certIsRevokedOCSP(cert, HardFail); !ok {
log.Warning("error checking revocation via OCSP")
if HardFail {
return true, false
}
return false, false
} else if revoked {
log.Info("certificate is revoked via OCSP")
return true, true
}
}
return false, true
}
// fetchCRL fetches and parses a CRL.
func fetchCRL(url string) (*pkix.CertificateList, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
} else if resp.StatusCode >= 300 {
return nil, errors.New("failed to retrieve CRL")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
resp.Body.Close()
return x509.ParseCRL(body)
}
// check a cert against a specific CRL. Returns the same bool pair
// as revCheck.
func certIsRevokedCRL(cert *x509.Certificate, url string) (revoked, ok bool) {
crl, ok := CRLSet[url]
if ok && crl == nil {
ok = false
delete(CRLSet, url)
}
var shouldFetchCRL = true
if ok {
if !crl.HasExpired(time.Now()) {
shouldFetchCRL = false
}
}
if shouldFetchCRL {
var err error
crl, err = fetchCRL(url)
if err != nil {
log.Warningf("failed to fetch CRL: %v", err)
return false, false
}
CRLSet[url] = crl
}
for _, revoked := range crl.TBSCertList.RevokedCertificates {
if cert.SerialNumber.Cmp(revoked.SerialNumber) == 0 {
log.Info("Serial number match: intermediate is revoked.")
return true, true
}
}
return false, true
}
// VerifyCertificate ensures that the certificate passed in hasn't
// expired and checks the CRL for the server.
func VerifyCertificate(cert *x509.Certificate) (revoked, ok bool) {
if !time.Now().Before(cert.NotAfter) {
log.Infof("Certificate expired %s\n", cert.NotAfter)
return true, true
} else if !time.Now().After(cert.NotBefore) {
log.Infof("Certificate isn't valid until %s\n", cert.NotBefore)
return true, true
}
return revCheck(cert)
}
func fetchRemote(url string) (*x509.Certificate, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
in, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
resp.Body.Close()
p, _ := pem.Decode(in)
if p != nil {
return helpers.ParseCertificatePEM(in)
}
return x509.ParseCertificate(in)
}
var ocspOpts = ocsp.RequestOptions{
Hash: crypto.SHA1,
}
func certIsRevokedOCSP(leaf *x509.Certificate, strict bool) (revoked, ok bool) {
var err error
ocspURLs := leaf.OCSPServer
if len(ocspURLs) == 0 {
// OCSP not enabled for this certificate.
return false, true
}
var issuer *x509.Certificate
for _, issuingCert := range leaf.IssuingCertificateURL {
issuer, err = fetchRemote(issuingCert)
if err != nil {
continue
}
break
}
if issuer == nil {
return
}
ocspRequest, err := ocsp.CreateRequest(leaf, issuer, &ocspOpts)
if err != nil {
return
}
for _, server := range ocspURLs {
resp, err := sendOCSPRequest(server, ocspRequest, issuer)
if err != nil {
if strict {
return
}
continue
}
// There wasn't an error fetching the OCSP status.
ok = true
if resp.Status != ocsp.Good {
// The certificate was revoked.
revoked = true
}
return
}
return
}
var ocspUnauthorised = []byte{0x30, 0x03, 0x0a, 0x01, 0x06}
var ocspMalformed = []byte{0x30, 0x03, 0x0a, 0x01, 0x01}
// sendOCSPRequest attempts to request an OCSP response from the
// server. The error only indicates a failure to *fetch* the
// certificate, and *does not* mean the certificate is valid.
func sendOCSPRequest(server string, req []byte, issuer *x509.Certificate) (ocspResponse *ocsp.Response, err error) {
var resp *http.Response
if len(req) > 256 {
buf := bytes.NewBuffer(req)
resp, err = http.Post(server, "application/ocsp-request", buf)
} else {
reqURL := server + "/" + base64.StdEncoding.EncodeToString(req)
resp, err = http.Get(reqURL)
}
if err != nil {
return
}
if resp.StatusCode != http.StatusOK {
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
resp.Body.Close()
if bytes.Equal(body, ocspUnauthorised) {
return
}
if bytes.Equal(body, ocspMalformed) {
return
}
return ocsp.ParseResponse(body, issuer)
}