Skip to content

Commit 48474d3

Browse files
committed
Add OCSP and CRL support to certificate verify
Add args and functionality to certificate verify to check a CRL and OCSP for a certificate based on the extensions. Users can pass flags to enable verification of each (CRL, OCSP). The command will try and get the CRL and OCSP server from the certifiacate and verify the certificate against each. Implements #845
1 parent 576d8ad commit 48474d3

File tree

2 files changed

+141
-1
lines changed

2 files changed

+141
-1
lines changed

command/certificate/verify.go

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package certificate
33
import (
44
"crypto/x509"
55
"encoding/pem"
6+
"net/http"
67
"os"
78

89
"github.com/pkg/errors"
910
"github.com/smallstep/cli/flags"
1011
"github.com/urfave/cli"
1112
"go.step.sm/cli-utils/errs"
1213
"go.step.sm/crypto/x509util"
14+
"golang.org/x/crypto/ocsp"
1315
)
1416

1517
func verifyCommand() cli.Command {
@@ -18,7 +20,8 @@ func verifyCommand() cli.Command {
1820
Action: cli.ActionFunc(verifyAction),
1921
Usage: `verify a certificate`,
2022
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**]`,
2225
Description: `**step certificate verify** executes the certificate path
2326
validation algorithm for x.509 certificates defined in RFC 5280. If the
2427
certificate is valid this command will return '0'. If validation fails, or if
@@ -88,6 +91,8 @@ authenticity of the remote server.
8891
: Relative or full path to a directory. Every PEM encoded certificate from each file in the directory will be used for path validation.`,
8992
},
9093
flags.ServerName,
94+
flags.VerifyOCSP,
95+
flags.VerifyCRL,
9196
},
9297
}
9398
}
@@ -102,6 +107,8 @@ func verifyAction(ctx *cli.Context) error {
102107
host = ctx.String("host")
103108
serverName = ctx.String("servername")
104109
roots = ctx.String("roots")
110+
verifyOCSP = ctx.Bool("verify-ocsp")
111+
verifyCRL = ctx.Bool("verify-crl")
105112
intermediatePool = x509.NewCertPool()
106113
rootPool *x509.CertPool
107114
cert *x509.Certificate
@@ -180,5 +187,128 @@ func verifyAction(ctx *cli.Context) error {
180187
return errors.Wrapf(err, "failed to verify certificate")
181188
}
182189

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+
183313
return nil
184314
}

flags/flags.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,16 @@ flag exists so it can be configured in $STEPPATH/config/defaults.json.`,
462462
Name: "attestation-uri",
463463
Usage: "The KMS <uri> used for attestation.",
464464
}
465+
466+
VerifyOCSP = cli.BoolFlag{
467+
Name: "verify-ocsp",
468+
Usage: "Verify the certificate against it's OCSP if the certificate has the OCSP server is defined in the AIA extension.",
469+
}
470+
471+
VerifyCRL = cli.BoolFlag{
472+
Name: "verify-crl",
473+
Usage: "Verify the certificate against it's CRL if the certificate has a CRL Distribution Point defined",
474+
}
465475
)
466476

467477
// FingerprintFormatFlag returns a flag for configuring the fingerprint format.

0 commit comments

Comments
 (0)