@@ -3,13 +3,15 @@ package certificate
3
3
import (
4
4
"crypto/x509"
5
5
"encoding/pem"
6
+ "net/http"
6
7
"os"
7
8
8
9
"github.com/pkg/errors"
9
10
"github.com/smallstep/cli/flags"
10
11
"github.com/urfave/cli"
11
12
"go.step.sm/cli-utils/errs"
12
13
"go.step.sm/crypto/x509util"
14
+ "golang.org/x/crypto/ocsp"
13
15
)
14
16
15
17
func verifyCommand () cli.Command {
@@ -18,7 +20,8 @@ func verifyCommand() cli.Command {
18
20
Action : cli .ActionFunc (verifyAction ),
19
21
Usage : `verify a certificate` ,
20
22
UsageText : `**step certificate verify** <crt-file> [**--host**=<host>]
21
- [**--roots**=<root-bundle>] [**--servername**=<servername>]` ,
23
+ [**--roots**=<root-bundle>] [**--servername**=<servername>]
24
+ [**--verify-ocsp**] [**-verify-crl**]` ,
22
25
Description : `**step certificate verify** executes the certificate path
23
26
validation algorithm for x.509 certificates defined in RFC 5280. If the
24
27
certificate is valid this command will return '0'. If validation fails, or if
@@ -88,6 +91,8 @@ authenticity of the remote server.
88
91
: Relative or full path to a directory. Every PEM encoded certificate from each file in the directory will be used for path validation.` ,
89
92
},
90
93
flags .ServerName ,
94
+ flags .VerifyOCSP ,
95
+ flags .VerifyCRL ,
91
96
},
92
97
}
93
98
}
@@ -102,6 +107,8 @@ func verifyAction(ctx *cli.Context) error {
102
107
host = ctx .String ("host" )
103
108
serverName = ctx .String ("servername" )
104
109
roots = ctx .String ("roots" )
110
+ verifyOCSP = ctx .Bool ("verify-ocsp" )
111
+ verifyCRL = ctx .Bool ("verify-crl" )
105
112
intermediatePool = x509 .NewCertPool ()
106
113
rootPool * x509.CertPool
107
114
cert * x509.Certificate
@@ -180,5 +187,128 @@ func verifyAction(ctx *cli.Context) error {
180
187
return errors .Wrapf (err , "failed to verify certificate" )
181
188
}
182
189
190
+ if verifyCRL {
191
+ crlIsVerified := false
192
+ // loop through CRL endpoints
193
+ // if any respond and the cert is not revoked == ok
194
+ // else not ok
195
+ if len (cert .CRLDistributionPoints ) == 0 {
196
+ return errors .Errorf ("CRL distribution endpoint not found in certificate" )
197
+ }
198
+ for _ , endpoint := range cert .CRLDistributionPoints {
199
+ // get CRL
200
+ resp , err := http .Get (endpoint )
201
+ if err != nil {
202
+ continue
203
+ }
204
+ defer resp .Body .Close ()
205
+ crlPEM , err := ioutil .ReadAll (resp .Body )
206
+ if err != nil {
207
+ continue
208
+ }
209
+
210
+ // decode CRL
211
+ block , _ = pem .Decode (crlPEM )
212
+ if block == nil {
213
+ continue
214
+ }
215
+
216
+ // parse CRL
217
+ crl , err := x509 .ParseCRL (block .Bytes )
218
+ if err != nil {
219
+ continue
220
+ }
221
+
222
+ // check if certificate is revoked
223
+ for _ , revoked := range crl .TBSCertList .RevokedCertificates {
224
+ if cert .SerialNumber .Cmp (revoked .SerialNumber ) == 0 {
225
+ return errors .Errorf ("certificate marked as revoked in CRL '%s'" , endpoint )
226
+ }
227
+ }
228
+ crlIsVerified = true
229
+ break
230
+ }
231
+
232
+ if ! crlIsVerified {
233
+ return errors .Errorf ("could not verify certificate against CRL" )
234
+ }
235
+ }
236
+
237
+ var issuer * x509.Certificate
238
+ if verifyOCSP {
239
+ ocspIsVerified := false
240
+ if len (cert .OCSPServer ) == 0 {
241
+ return errors .Errorf ("no OCSP AIA extension found" )
242
+ }
243
+ // TODO
244
+ // use the CAs fed in by users if available (issuer) else get it from the cert extension
245
+ if ! issuer {
246
+ if len (cert .IssuingCertificateURL ) == 0 {
247
+ return errors .Errorf ("no Issuing Certificate URL extension found" )
248
+ }
249
+ for _ , endpoint := range cert .IssuingCertificateURL {
250
+ resp , err := http .Get (endpoint )
251
+ if err != nil {
252
+ continue
253
+ }
254
+ defer resp .Body .Close ()
255
+ issuerPEM , err := ioutil .ReadAll (resp .Body )
256
+ if err != nil {
257
+ continue
258
+ }
259
+
260
+ block , _ = pem .Decode (issuerPEM )
261
+ if block == nil {
262
+ continue
263
+ }
264
+
265
+ issuer , err := x509 .ParseCertificate (block .Bytes )
266
+ if err != nil {
267
+ continue
268
+ }
269
+ }
270
+ }
271
+ if ! issuer {
272
+ return errors .Errorf ("could not get issuer CA certificate" )
273
+ }
274
+ // create OCSP request
275
+ req , err := ocsp .CreateRequest (cert , issuer , nil )
276
+ if err != nil {
277
+ return errors .Errorf ("error creating OCSP request" )
278
+ }
279
+ var resp ocsp.ParseResponse
280
+ // loop through OCSP servers
281
+ for _ , endpoint := range cert .OCSPServer {
282
+ httpReq , err := http .NewRequest (http .MethodPost , endpoint , bytes .NewReader (req ))
283
+ if err != nil {
284
+ continue
285
+ }
286
+ httpReq .Header .Add ("Content-Type" , "application/ocsp-request" )
287
+ httpResp , err := http .DefaultClient .Do (httpReq )
288
+ if err != nil {
289
+ continue
290
+ }
291
+ defer httpResp .Body .Close ()
292
+ respBytes , err := ioutil .ReadAll (httpResp .Body )
293
+ if err != nil {
294
+ continue
295
+ }
296
+
297
+ resp , err := ocsp .ParseResponse (respBytes , issuer )
298
+ if err != nil {
299
+ continue
300
+ }
301
+ }
302
+
303
+ // Check OCSP response
304
+ switch res .Status {
305
+ case ocsp .Revoked :
306
+ return errors .Errorf ("certificate has been revoked according to OCSP" )
307
+ case ocsp .Good :
308
+ default :
309
+ return errors .Errorf ("certificate status is unknown" )
310
+ }
311
+ }
312
+
183
313
return nil
184
314
}
0 commit comments