boulder/crl/checker/checker.go

117 lines
3.2 KiB
Go

package checker
import (
"bytes"
"crypto/x509"
"fmt"
"math/big"
"sort"
"time"
zlint_x509 "github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v3"
"github.com/letsencrypt/boulder/linter"
)
// Validate runs the given CRL through our set of lints, ensures its signature
// validates (if supplied with a non-nil issuer), and checks that the CRL is
// less than ageLimit old. It returns an error if any of these conditions are
// not met.
func Validate(crl *x509.RevocationList, issuer *x509.Certificate, ageLimit time.Duration) error {
zcrl, err := zlint_x509.ParseRevocationList(crl.Raw)
if err != nil {
return fmt.Errorf("parsing CRL: %w", err)
}
err = linter.ProcessResultSet(zlint.LintRevocationList(zcrl))
if err != nil {
return fmt.Errorf("linting CRL: %w", err)
}
if issuer != nil {
err = crl.CheckSignatureFrom(issuer)
if err != nil {
return fmt.Errorf("checking CRL signature: %w", err)
}
}
if time.Since(crl.ThisUpdate) >= ageLimit {
return fmt.Errorf("thisUpdate more than %s in the past: %v", ageLimit, crl.ThisUpdate)
}
return nil
}
type diffResult struct {
Added []*big.Int
Removed []*big.Int
// TODO: consider adding a "changed" field, for entries whose revocation time
// or revocation reason changes.
}
// Diff returns the sets of serials that were added and removed between two
// CRLs. In order to be comparable, the CRLs must come from the same issuer, and
// be given in the correct order (the "old" CRL's Number and ThisUpdate must
// both precede the "new" CRL's).
func Diff(old, new *x509.RevocationList) (*diffResult, error) {
if !bytes.Equal(old.AuthorityKeyId, new.AuthorityKeyId) {
return nil, fmt.Errorf("CRLs were not issued by same issuer")
}
if old.Number.Cmp(new.Number) >= 0 {
return nil, fmt.Errorf("old CRL does not precede new CRL")
}
if new.ThisUpdate.Before(old.ThisUpdate) {
return nil, fmt.Errorf("old CRL does not precede new CRL")
}
// Sort both sets of serials so we can march through them in order.
oldSerials := make([]*big.Int, len(old.RevokedCertificateEntries))
for i, rc := range old.RevokedCertificateEntries {
oldSerials[i] = rc.SerialNumber
}
sort.Slice(oldSerials, func(i, j int) bool {
return oldSerials[i].Cmp(oldSerials[j]) < 0
})
newSerials := make([]*big.Int, len(new.RevokedCertificateEntries))
for j, rc := range new.RevokedCertificateEntries {
newSerials[j] = rc.SerialNumber
}
sort.Slice(newSerials, func(i, j int) bool {
return newSerials[i].Cmp(newSerials[j]) < 0
})
// Work our way through both lists of sorted serials. If the old list skips
// past a serial seen in the new list, then that serial was added. If the new
// list skips past a serial seen in the old list, then it was removed.
i, j := 0, 0
added := make([]*big.Int, 0)
removed := make([]*big.Int, 0)
for {
if i >= len(oldSerials) {
added = append(added, newSerials[j:]...)
break
}
if j >= len(newSerials) {
removed = append(removed, oldSerials[i:]...)
break
}
cmp := oldSerials[i].Cmp(newSerials[j])
if cmp < 0 {
removed = append(removed, oldSerials[i])
i++
} else if cmp > 0 {
added = append(added, newSerials[j])
j++
} else {
i++
j++
}
}
return &diffResult{added, removed}, nil
}