1
1
package certificate
2
2
3
3
import (
4
+ "bytes"
4
5
"crypto/x509"
5
6
"encoding/pem"
7
+ "fmt"
8
+ "io"
9
+ "io/ioutil"
10
+ "net/http"
6
11
"os"
7
12
8
13
"github.com/pkg/errors"
9
14
"github.com/smallstep/cli/flags"
15
+ "github.com/smallstep/cli/internal/crlutil"
10
16
"github.com/urfave/cli"
11
17
"go.step.sm/cli-utils/errs"
12
18
"go.step.sm/crypto/x509util"
19
+ "golang.org/x/crypto/ocsp"
13
20
)
14
21
15
22
func verifyCommand () cli.Command {
@@ -18,7 +25,10 @@ func verifyCommand() cli.Command {
18
25
Action : cli .ActionFunc (verifyAction ),
19
26
Usage : `verify a certificate` ,
20
27
UsageText : `**step certificate verify** <crt-file> [**--host**=<host>]
21
- [**--roots**=<root-bundle>] [**--servername**=<servername>]` ,
28
+ [**--roots**=<root-bundle>] [**--servername**=<servername>]
29
+ [**--ca**]=file [**--verify-verbose**]
30
+ [**--verify-ocsp**]] [**--ocsp-endpoint**]=url
31
+ [**--verify-crl**] [**--crl-endpoint**]=url` ,
22
32
Description : `**step certificate verify** executes the certificate path
23
33
validation algorithm for x.509 certificates defined in RFC 5280. If the
24
34
certificate is valid this command will return '0'. If validation fails, or if
@@ -65,7 +75,24 @@ Verify a certificate using a custom directory of root certificates for path vali
65
75
'''
66
76
$ step certificate verify ./certificate.crt --roots ./root-certificates/
67
77
'''
68
- ` ,
78
+
79
+ Verify a certificate including OCSP and CRL using CRL and OCSP defined in the certificate
80
+
81
+ '''
82
+ $ step certificate verify ./certificate.crt --ca ./issuing_ca.pem --verify-crl --verify-ocsp
83
+ '''
84
+
85
+ Verify a certificate including OCSP and specificing an OCSP server
86
+
87
+ '''
88
+ $ step certificate verify ./certificate.crt --ca ./issuing_ca.pem --verify-ocsp --ocsp-endpoint http://acme.com/ocsp
89
+ '''
90
+
91
+ Verify a certificate including CRL and specificing a CRL server
92
+
93
+ '''
94
+ $ step certificate verify ./certificate.crt --ca ./issuing_ca.pem --verify-crl --crl-endpoint http://acme.com/crl
95
+ '''` ,
69
96
Flags : []cli.Flag {
70
97
cli.StringFlag {
71
98
Name : "host" ,
@@ -87,7 +114,32 @@ authenticity of the remote server.
87
114
**directory**
88
115
: Relative or full path to a directory. Every PEM encoded certificate from each file in the directory will be used for path validation.` ,
89
116
},
117
+ cli.StringFlag {
118
+ Name : "ca" ,
119
+ Usage : `The certificate issuer CA <file> needed to communicate with OCSP and verify a CRL.` ,
120
+ },
121
+ cli.BoolFlag {
122
+ Name : "verify-ocsp" ,
123
+ Usage : "Verify the certificate against it's OCSP." ,
124
+ },
125
+ cli.StringFlag {
126
+ Name : "ocsp-endpoint" ,
127
+ Usage : `The OCSP endpoint to use. If not provided step will attempt to check it against the certificate's OCSPServer AIA extension endpoints.` ,
128
+ },
129
+ cli.BoolFlag {
130
+ Name : "verify-crl" ,
131
+ Usage : "Verify the certificate against it's CRL." ,
132
+ },
133
+ cli.StringFlag {
134
+ Name : "crl-endpoint" ,
135
+ Usage : "The CRL endpoint to use. If not provided step will attempt to check it against the certificate's CRLDistributionPoints extension endpoints." ,
136
+ },
137
+ cli.BoolFlag {
138
+ Name : "verify-verbose" ,
139
+ Usage : "Print result of certificate verification to stdout on success" ,
140
+ },
90
141
flags .ServerName ,
142
+ flags .Insecure ,
91
143
},
92
144
}
93
145
}
@@ -102,11 +154,23 @@ func verifyAction(ctx *cli.Context) error {
102
154
host = ctx .String ("host" )
103
155
serverName = ctx .String ("servername" )
104
156
roots = ctx .String ("roots" )
157
+ verifyOCSP = ctx .Bool ("verify-ocsp" )
158
+ ocspEndpoint = ctx .String ("ocsp-endpoint" )
159
+ verifyCRL = ctx .Bool ("verify-crl" )
160
+ crlEndpoint = ctx .String ("crl-endpoint" )
161
+ verbose = ctx .Bool ("verify-verbose" )
162
+ issuerFile = ctx .String ("ca" )
163
+ insecure = ctx .Bool ("insecure" )
105
164
intermediatePool = x509 .NewCertPool ()
106
165
rootPool * x509.CertPool
107
166
cert * x509.Certificate
167
+ issuer * x509.Certificate
108
168
)
109
169
170
+ if issuerFile == "" && verifyOCSP {
171
+ return errors .Errorf ("You must provide the issuinge CA certificate if you want to verify the certificate with OCSP" )
172
+ }
173
+
110
174
switch addr , isURL , err := trimURL (crtFile ); {
111
175
case err != nil :
112
176
return err
@@ -180,5 +244,127 @@ func verifyAction(ctx *cli.Context) error {
180
244
return errors .Wrapf (err , "failed to verify certificate" )
181
245
}
182
246
247
+ verboseMSG := "certificate validated against roots\n "
248
+ if host != "" {
249
+ verboseMSG = verboseMSG + "certificate host name validated\n "
250
+ }
251
+
252
+ if issuerFile != "" {
253
+ issuerCertPEM , err := ioutil .ReadFile (issuerFile )
254
+ if err != nil {
255
+ return errors .Errorf ("unable to load the issuing CA certificate file" )
256
+ }
257
+
258
+ issuerBlock , _ := pem .Decode (issuerCertPEM )
259
+ if issuerBlock == nil || issuerBlock .Type != "CERTIFICATE" {
260
+ return errors .Errorf ("failed to decode the issuing CA certificate" )
261
+ }
262
+
263
+ issuer , err = x509 .ParseCertificate (issuerBlock .Bytes )
264
+ if err != nil {
265
+ return errors .Errorf ("failed to parse the issuing CA certificate" )
266
+ }
267
+ }
268
+
269
+ if verifyCRL {
270
+ var endpoint string
271
+ if crlEndpoint != "" && issuer .Equal (& x509.Certificate {}) {
272
+ return errors .Errorf ("you must provider the issuer CA if you are going to specificy the CRL endpoint" )
273
+ } else if crlEndpoint != "" {
274
+ endpoint = crlEndpoint
275
+ } else if len (cert .CRLDistributionPoints ) == 0 {
276
+ return errors .Errorf ("CRL distribution endpoint not found in certificate" )
277
+ } else {
278
+ endpoint = cert .CRLDistributionPoints [0 ]
279
+ }
280
+
281
+ httpClient := http.Client {}
282
+
283
+ resp , err := httpClient .Get (endpoint )
284
+ if err != nil {
285
+ return errors .Wrap (err , "error downloading crl" )
286
+ }
287
+ defer resp .Body .Close ()
288
+ if resp .StatusCode >= 400 {
289
+ return errors .Errorf ("error downloading crl: status code %d" , resp .StatusCode )
290
+ }
291
+ b , err := io .ReadAll (resp .Body )
292
+ if err != nil {
293
+ return errors .Wrap (err , "error downloading crl" )
294
+ }
295
+
296
+ crl , err := x509 .ParseRevocationList (b )
297
+ if err != nil {
298
+ return errors .Wrap (err , "error parsing crl" )
299
+ }
300
+
301
+ crlJson , err := crlutil .ParseCRL (b )
302
+ if err != nil {
303
+ return errors .Wrap (err , "error parsing crl into json" )
304
+ }
305
+
306
+ if ! issuer .Equal (& x509.Certificate {}) && ! insecure {
307
+ err = crl .CheckSignatureFrom (issuer )
308
+ if err != nil {
309
+ return errors .Wrap (err , "error validating the CRL against the CA issuer" )
310
+ }
311
+ }
312
+
313
+ for _ , revoked := range crlJson .RevokedCertificates {
314
+ if cert .SerialNumber .String () == revoked .SerialNumber {
315
+ return errors .Errorf ("certificate marked as revoked in CRL %s" , endpoint )
316
+ }
317
+ }
318
+ verboseMSG = verboseMSG + fmt .Sprintf ("certificate not revoked in CRL %s\n " , endpoint )
319
+ }
320
+
321
+ if verifyOCSP {
322
+ var endpoint string
323
+ if ocspEndpoint != "" {
324
+ endpoint = ocspEndpoint
325
+ } else if len (cert .OCSPServer ) == 0 {
326
+ return errors .Errorf ("no OCSP AIA extension found" )
327
+ } else {
328
+ endpoint = cert .OCSPServer [0 ]
329
+ }
330
+
331
+ req , err := ocsp .CreateRequest (cert , issuer , nil )
332
+ if err != nil {
333
+ return errors .Errorf ("error creating OCSP request" )
334
+ }
335
+
336
+ httpReq , err := http .NewRequest (http .MethodPost , endpoint , bytes .NewReader (req ))
337
+ if err != nil {
338
+ return errors .Errorf ("error contacting OCSP server: %s" , endpoint )
339
+ }
340
+ httpReq .Header .Add ("Content-Type" , "application/ocsp-request" )
341
+ httpResp , err := http .DefaultClient .Do (httpReq )
342
+ if err != nil {
343
+ return errors .Errorf ("error contacting OCSP server: %s" , endpoint )
344
+ }
345
+ defer httpResp .Body .Close ()
346
+ respBytes , err := ioutil .ReadAll (httpResp .Body )
347
+ if err != nil {
348
+ return errors .Errorf ("error reading response from OCSP server: %s" , endpoint )
349
+ }
350
+
351
+ resp , err := ocsp .ParseResponse (respBytes , issuer )
352
+ if err != nil {
353
+ return errors .Errorf ("error parsing repsonse from OCSP server: %s" , endpoint )
354
+ }
355
+
356
+ switch resp .Status {
357
+ case ocsp .Revoked :
358
+ return errors .Errorf ("certificate has been revoked according to OCSP %s" , endpoint )
359
+ case ocsp .Good :
360
+ default :
361
+ return errors .Errorf ("certificate status is unknown according to OCSP %s" , endpoint )
362
+ }
363
+ verboseMSG = verboseMSG + fmt .Sprintf ("certificate status shows good in OCSP\n " , endpoint )
364
+ }
365
+
366
+ if verbose {
367
+ fmt .Println (verboseMSG + "certficiate is valid" )
368
+ }
183
369
return nil
184
370
}
0 commit comments