Skip to content

Commit

Permalink
crl-checker: a simple tool to validate public CRLs (letsencrypt#6381)
Browse files Browse the repository at this point in the history
Add crl-checker, a simple tool which downloads, parses, lints,
and validates signatures on a list of CRLs. It takes its input in
the form of a JSON Array of Sharded CRL URLs, the exact
same format as we will be disclosing in CCADB.

We can add additional checks -- such as ensuring that a set of
known-revoked serials are present, or checking that all of the
downloaded CRLs are "recent enough" -- over time.
  • Loading branch information
aarongable authored Sep 14, 2022
1 parent d53c90a commit aed6042
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 3 deletions.
1 change: 1 addition & 0 deletions cmd/boulder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
_ "github.com/letsencrypt/boulder/cmd/ceremony"
_ "github.com/letsencrypt/boulder/cmd/cert-checker"
_ "github.com/letsencrypt/boulder/cmd/contact-auditor"
_ "github.com/letsencrypt/boulder/cmd/crl-checker"
_ "github.com/letsencrypt/boulder/cmd/crl-storer"
_ "github.com/letsencrypt/boulder/cmd/crl-updater"
_ "github.com/letsencrypt/boulder/cmd/expiration-mailer"
Expand Down
81 changes: 81 additions & 0 deletions cmd/crl-checker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package notmain

import (
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"

"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/crl/crl_x509"
"github.com/letsencrypt/boulder/issuance"
"github.com/letsencrypt/boulder/linter"
crlint "github.com/letsencrypt/boulder/linter/lints/crl"
)

func validateShard(url string, issuer *issuance.Certificate) error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("downloading crl: %w", err)
}

crlBytes, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading CRL bytes: %w", err)
}

crl, err := crl_x509.ParseRevocationList(crlBytes)
if err != nil {
return fmt.Errorf("parsing CRL: %w", err)
}

err = linter.ProcessResultSet(crlint.LintCRL(crl))
if err != nil {
return fmt.Errorf("linting CRL: %w", err)
}

err = crl.CheckSignatureFrom(issuer.Certificate)
if err != nil {
return fmt.Errorf("checking CRL signature: %w", err)
}

return nil
}

func main() {
urlFile := flag.String("crls", "", "path to a file containing a JSON Array of CRL URLs")
issuerFile := flag.String("issuer", "", "path to an issuer certificate on disk")
flag.Parse()

logger := cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 6, SyslogLevel: -1})

urlFileContents, err := os.ReadFile(*urlFile)
cmd.FailOnError(err, "Reading CRL URLs file")

var urls []string
err = json.Unmarshal(urlFileContents, &urls)
cmd.FailOnError(err, "Parsing JSON Array of CRL URLs")

issuer, err := issuance.LoadCertificate(*issuerFile)
cmd.FailOnError(err, "Loading issuer certificate")

errCount := 0
for _, url := range urls {
err = validateShard(url, issuer)
if err != nil {
errCount += 1
logger.Errf("CRL %q failed: %s\n", url, err)
}
}

if errCount != 0 {
cmd.Fail(fmt.Sprintf("Encountered %d errors", errCount))
}
logger.AuditInfo("All CRLs validated")
}

func init() {
cmd.RegisterCommand("crl-checker", main)
}
6 changes: 3 additions & 3 deletions linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) err
return err
}
lintRes := zlint.LintCertificateEx(cert, l.registry)
return processResultSet(lintRes)
return ProcessResultSet(lintRes)
}

// CheckCRL signs the given RevocationList template using the Linter's fake
Expand All @@ -88,7 +88,7 @@ func (l Linter) CheckCRL(tbs *crl_x509.RevocationList) error {
return err
}
lintRes := crllints.LintCRL(crl)
return processResultSet(lintRes)
return ProcessResultSet(lintRes)
}

func makeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
Expand Down Expand Up @@ -190,7 +190,7 @@ func makeLintCert(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, issuer
return lintCert, nil
}

func processResultSet(lintRes *zlint.ResultSet) error {
func ProcessResultSet(lintRes *zlint.ResultSet) error {
if lintRes.NoticesPresent || lintRes.WarningsPresent || lintRes.ErrorsPresent || lintRes.FatalsPresent {
var failedLints []string
for lintName, result := range lintRes.Results {
Expand Down

0 comments on commit aed6042

Please sign in to comment.