Add CRL linting framework and first few lints (#6205)

Add a collection of lints (structured similarly, but not identically,
to zlint's certificate lints) which check a variety of requirements
based on RFC 5280, the Baseline Requirements, and the Mozilla
Root Store Policy.

Add a method to lint CRLs to the existing linter package which
uses its fake issuer to sign the CRL, calls all of the above lints,
and returns all of their findings. Call this new method from within
the CA's new GenerateCRL method immediately before signing
the real CRL using the real issuer.

Fixes #6188
This commit is contained in:
Aaron Gable 2022-07-08 12:22:44 -07:00 committed by GitHub
parent 436061fb35
commit c7014dfd29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1060 additions and 29 deletions

View File

@ -127,6 +127,12 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error
}
template.RevokedCertificates = rcs
err := issuer.Linter.CheckCRL(template)
if err != nil {
return err
}
crlBytes, err := crl_x509.CreateRevocationList(
rand.Reader,
template,

View File

@ -30,6 +30,10 @@ var (
// a CRL.
// NOTE: This type does not exist in upstream.
type RevokedCertificate struct {
// Raw contains the raw bytes of the revokedCertificates entry. It is set when
// parsing a CRL; it is ignored when generating a CRL.
Raw []byte
// SerialNumber represents the serial number of a revoked certificate. It is
// both used when creating a CRL and populated when parsing a CRL. It MUST NOT
// be nil.
@ -45,11 +49,15 @@ type RevokedCertificate struct {
// value of 0 represents a reasonCode extension containing enum value 0 (this
// SHOULD NOT happen, but can and does).
ReasonCode *int
// When creating a CRL, ExtraExtensions should contain all extra extensions to
// add to the CRL entry. If ExtraExtensions contains a reasonCode extension,
// it will be ignored in favor of the ReasonCode field above. When parsing a
// CRL, ExtraExtensions contains all raw extensions parsed from the CRL entry,
// except for reasonCode which is represented by the ReasonCode field above.
// Extensions contains raw X.509 extensions. When creating a CRL, the
// Extensions field is ignored, see ExtraExtensions.
Extensions []pkix.Extension
// ExtraExtensions contains any additional extensions to add directly to the
// revokedCertificate entry. It is up to the caller to ensure that this field
// does not contain any extensions which duplicate extensions created by this
// package (currently, the reasonCode extension). The ExtraExtensions field is
// not populated when parsing a CRL, see Extensions.
ExtraExtensions []pkix.Extension
}
@ -102,13 +110,14 @@ type RevocationList struct {
// the issuer information instead.
AuthorityKeyId []byte
// Extensions contains raw X.509 extensions. When creating a CRL,
// the Extensions field is ignored, see ExtraExtensions.
// Extensions contains raw X.509 extensions. When creating a CRL, the
// Extensions field is ignored, see ExtraExtensions.
Extensions []pkix.Extension
// ExtraExtensions contains any additional extensions to add directly to the
// CRL. The ExtraExtensions field is not populated when parsing a CRL, see
// Extensions.
// CRL. It is up to the caller to ensure that this field does not contain any
// extensions which duplicate extensions created by this package (currently,
// the number and authorityKeyIdentifier extensions). The ExtraExtensions
// field is not populated when parsing a CRL, see Extensions.
ExtraExtensions []pkix.Extension
}
@ -122,22 +131,22 @@ func ParseRevocationList(der []byte) (*RevocationList, error) {
// we can populate RevocationList.Raw, before unwrapping the
// SEQUENCE so it can be operated on
if !input.ReadASN1Element(&input, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed certificate")
return nil, errors.New("x509: malformed crl")
}
rl.Raw = input
if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed certificate")
return nil, errors.New("x509: malformed crl")
}
var tbs cryptobyte.String
// do the same trick again as above to extract the raw
// bytes for Certificate.RawTBSCertificate
if !input.ReadASN1Element(&tbs, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed tbs certificate")
return nil, errors.New("x509: malformed tbs crl")
}
rl.RawTBSRevocationList = tbs
if !tbs.ReadASN1(&tbs, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed tbs certificate")
return nil, errors.New("x509: malformed tbs crl")
}
var version int
@ -200,16 +209,20 @@ func ParseRevocationList(der []byte) (*RevocationList, error) {
}
if tbs.PeekASN1Tag(cryptobyte_asn1.SEQUENCE) {
rcs := make([]RevokedCertificate, 0)
var revokedSeq cryptobyte.String
if !tbs.ReadASN1(&revokedSeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
for !revokedSeq.Empty() {
var certSeq cryptobyte.String
if !revokedSeq.ReadASN1(&certSeq, cryptobyte_asn1.SEQUENCE) {
if !revokedSeq.ReadASN1Element(&certSeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
rc := RevokedCertificate{Raw: certSeq}
if !certSeq.ReadASN1(&certSeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
rc := RevokedCertificate{}
rc.SerialNumber = new(big.Int)
if !certSeq.ReadASN1Integer(rc.SerialNumber) {
return nil, errors.New("x509: malformed serial number")
@ -240,14 +253,14 @@ func ParseRevocationList(der []byte) (*RevocationList, error) {
if !val.ReadASN1Enum(rc.ReasonCode) {
return nil, fmt.Errorf("x509: malformed reasonCode extension")
}
continue
}
rc.ExtraExtensions = append(rc.ExtraExtensions, ext)
rc.Extensions = append(rc.Extensions, ext)
}
}
rl.RevokedCertificates = append(rl.RevokedCertificates, rc)
rcs = append(rcs, rc)
}
rl.RevokedCertificates = rcs
}
var extensions cryptobyte.String
@ -272,12 +285,11 @@ func ParseRevocationList(der []byte) (*RevocationList, error) {
if ext.Id.Equal(oidExtensionAuthorityKeyId) {
rl.AuthorityKeyId = ext.Value
} else if ext.Id.Equal(oidExtensionCRLNumber) {
number := new(big.Int)
value := cryptobyte.String(ext.Value)
if !value.ReadASN1Integer(number) {
rl.Number = new(big.Int)
if !value.ReadASN1Integer(rl.Number) {
return nil, errors.New("x509: malformed crl number")
}
rl.Number = number
}
rl.Extensions = append(rl.Extensions, ext)
}
@ -357,7 +369,9 @@ func CreateRevocationList(rand io.Reader, template *RevocationList, issuer *x509
})
}
prc.Extensions = exts
if len(exts) > 0 {
prc.Extensions = exts
}
revokedCerts[i] = prc
}

View File

@ -352,9 +352,41 @@ func TestCreateRevocationList(t *testing.T) {
tc.template.SignatureAlgorithm)
}
if !reflect.DeepEqual(parsedCRL.RevokedCertificates, tc.template.RevokedCertificates) {
t.Fatalf("RevokedCertificates mismatch: got %v; want %v.",
parsedCRL.RevokedCertificates, tc.template.RevokedCertificates)
if len(parsedCRL.RevokedCertificates) != len(tc.template.RevokedCertificates) {
t.Fatalf("RevokedCertificates length mismatch: got %d; want %d.",
len(parsedCRL.RevokedCertificates), len(tc.template.RevokedCertificates))
}
for i, rc := range parsedCRL.RevokedCertificates {
erc := tc.template.RevokedCertificates[i]
if rc.SerialNumber.Cmp(erc.SerialNumber) != 0 {
t.Errorf("RevokedCertificates entry %d serial mismatch: got %s; want %s.",
i, rc.SerialNumber.String(), erc.SerialNumber.String())
}
if rc.RevocationTime != erc.RevocationTime {
t.Errorf("RevokedCertificates entry %d date mismatch: got %v; want %v.",
i, rc.RevocationTime, erc.RevocationTime)
}
numExtra := 0
if erc.ReasonCode != nil {
if rc.ReasonCode == nil {
t.Errorf("RevokedCertificates entry %d reason mismatch: got nil; want %v.",
i, *erc.ReasonCode)
}
if *rc.ReasonCode != *erc.ReasonCode {
t.Errorf("RevokedCertificates entry %d reason mismatch: got %v; want %v.",
i, *rc.ReasonCode, *erc.ReasonCode)
}
numExtra = 1
} else {
if rc.ReasonCode != nil {
t.Errorf("RevokedCertificates entry %d reason mismatch: got %v; want nil.",
i, *rc.ReasonCode)
}
}
if len(rc.Extensions) != numExtra+len(erc.ExtraExtensions) {
t.Errorf("RevokedCertificates entry %d has wrong number of extensions: got %d; want %d",
i, len(rc.Extensions), numExtra+len(erc.ExtraExtensions))
}
}
if len(parsedCRL.Extensions) != 2+len(tc.template.ExtraExtensions) {

View File

@ -13,6 +13,9 @@ import (
"github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/lint"
"github.com/letsencrypt/boulder/crl/crl_x509"
crllints "github.com/letsencrypt/boulder/linter/lints/crl"
_ "github.com/letsencrypt/boulder/linter/lints/all"
_ "github.com/letsencrypt/boulder/linter/lints/intermediate"
_ "github.com/letsencrypt/boulder/linter/lints/root"
@ -72,7 +75,20 @@ func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) err
if err != nil {
return err
}
return check(cert, l.registry)
lintRes := zlint.LintCertificateEx(cert, l.registry)
return processResultSet(lintRes)
}
// CheckCRL signs the given RevocationList template using the Linter's fake
// issuer cert and private key, then runs the resulting CRL through our suite
// of CRL checks. It returns an error if any check fails.
func (l Linter) CheckCRL(tbs *crl_x509.RevocationList) error {
crl, err := makeLintCRL(tbs, l.issuer, l.signer)
if err != nil {
return err
}
lintRes := crllints.LintCRL(crl)
return processResultSet(lintRes)
}
func makeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
@ -174,8 +190,7 @@ func makeLintCert(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, issuer
return lintCert, nil
}
func check(lintCert *zlintx509.Certificate, lints lint.Registry) error {
lintRes := zlint.LintCertificateEx(lintCert, lints)
func processResultSet(lintRes *zlint.ResultSet) error {
if lintRes.NoticesPresent || lintRes.WarningsPresent || lintRes.ErrorsPresent || lintRes.FatalsPresent {
var failedLints []string
for lintName, result := range lintRes.Results {
@ -187,3 +202,15 @@ func check(lintCert *zlintx509.Certificate, lints lint.Registry) error {
}
return nil
}
func makeLintCRL(tbs *crl_x509.RevocationList, issuer *x509.Certificate, signer crypto.Signer) (*crl_x509.RevocationList, error) {
lintCRLBytes, err := crl_x509.CreateRevocationList(rand.Reader, tbs, issuer, signer)
if err != nil {
return nil, err
}
lintCRL, err := crl_x509.ParseRevocationList(lintCRLBytes)
if err != nil {
return nil, err
}
return lintCRL, nil
}

371
linter/lints/crl/lints.go Normal file
View File

@ -0,0 +1,371 @@
package crl
import (
"crypto/x509/pkix"
"encoding/asn1"
"time"
"github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/lint"
"golang.org/x/crypto/cryptobyte"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
"github.com/letsencrypt/boulder/crl/crl_x509"
)
type crlLint func(*crl_x509.RevocationList) *lint.LintResult
// registry is the collection of all known CRL lints. It is populated by this
// file's init(), and should not be touched by anything else on pain of races.
var registry map[string]crlLint
func init() {
// NOTE TO DEVS: you MUST add your new lint function to this list or it
// WILL NOT be run.
registry = map[string]crlLint{
"hasIssuerName": hasIssuerName,
"hasNextUpdate": hasNextUpdate,
"noEmptyRevokedCertificatesList": noEmptyRevokedCertificatesList,
"hasAKI": hasAKI,
"hasNumber": hasNumber,
"isNotDelta": isNotDelta,
"hasNoIDP": hasNoIDP,
"hasNoFreshest": hasNoFreshest,
"hasNoAIA": hasNoAIA,
"noZeroReasonCodes": noZeroReasonCodes,
"hasNoCertIssuers": hasNoCertIssuers,
"hasAcceptableValidity": hasAcceptableValidity,
"noCriticalReasons": noCriticalReasons,
"noCertificateHolds": noCertificateHolds,
"hasMozReasonCodes": hasMozReasonCodes,
}
}
// getExtWithOID is a helper for several lints in this file. It returns the
// extension with the given OID if it exists, or nil otherwise.
func getExtWithOID(exts []pkix.Extension, oid asn1.ObjectIdentifier) *pkix.Extension {
for _, ext := range exts {
if ext.Id.Equal(oid) {
return &ext
}
}
return nil
}
// LintCRL examines the given lint CRL, runs it through all of our checks, and
// returns a list of all failures
func LintCRL(lintCRL *crl_x509.RevocationList) *zlint.ResultSet {
rset := zlint.ResultSet{
Version: 0,
Timestamp: time.Now().UnixNano(),
Results: make(map[string]*lint.LintResult),
}
type namedResult struct {
Name string
Result *lint.LintResult
}
resChan := make(chan namedResult, len(registry))
for name, callable := range registry {
go func(name string, callable crlLint) {
resChan <- namedResult{name, callable(lintCRL)}
}(name, callable)
}
for i := 0; i < len(registry); i++ {
res := <-resChan
switch res.Result.Status {
case lint.Notice:
rset.NoticesPresent = true
case lint.Warn:
rset.WarningsPresent = true
case lint.Error:
rset.ErrorsPresent = true
case lint.Fatal:
rset.FatalsPresent = true
}
rset.Results[res.Name] = res.Result
}
return &rset
}
// hasIssuerName checks RFC 5280, Section 5.1.2.3:
// The issuer field MUST contain a non-empty X.500 distinguished name (DN).
// This lint does not enforce that the issuer field complies with the rest of
// the encoding rules of a certificate issuer name, because it (perhaps wrongly)
// assumes that those were checked when the issuer was itself issued, and on all
// certificates issued by this CRL issuer. Also because there are just a lot of
// things to check there, and zlint doesn't expose a public helper for it.
func hasIssuerName(crl *crl_x509.RevocationList) *lint.LintResult {
if len(crl.Issuer.Names) == 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "CRLs MUST have a non-empty issuer field",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// TODO(#6222): Write a lint which checks RFC 5280, Section 5.1.2.4 and 5.1.2.5:
// CRL issuers conforming to this profile MUST encode thisUpdate and nextUpdate
// as UTCTime for dates through the year 2049. UTCTime and GeneralizedTime
// values MUST be expressed in Greenwich Mean Time (Zulu) and MUST include
// seconds, even where the number of seconds is zero.
// hasNextUpdate checks RFC 5280, Section 5.1.2.5:
// Conforming CRL issuers MUST include the nextUpdate field in all CRLs.
func hasNextUpdate(crl *crl_x509.RevocationList) *lint.LintResult {
if crl.NextUpdate.IsZero() {
return &lint.LintResult{
Status: lint.Error,
Details: "Conforming CRL issuers MUST include the nextUpdate field in all CRLs",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// noEmptyRevokedCertificatesList checks RFC 5280, Section 5.1.2.6:
// When there are no revoked certificates, the revoked certificates list MUST be
// absent.
func noEmptyRevokedCertificatesList(crl *crl_x509.RevocationList) *lint.LintResult {
if crl.RevokedCertificates != nil && len(crl.RevokedCertificates) == 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "If the revokedCertificates list is empty, it must not be present",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasAKI checks RFC 5280, Section 5.2.1:
// Conforming CRL issuers MUST use the key identifier method, and MUST include
// this extension in all CRLs issued.
func hasAKI(crl *crl_x509.RevocationList) *lint.LintResult {
if len(crl.AuthorityKeyId) == 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "CRLs MUST include the authority key identifier extension",
}
}
aki := cryptobyte.String(crl.AuthorityKeyId)
var akiBody cryptobyte.String
if !aki.ReadASN1(&akiBody, cryptobyte_asn1.SEQUENCE) {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL has a malformed authority key identifier extension",
}
}
if !akiBody.PeekASN1Tag(cryptobyte_asn1.Tag(0).ContextSpecific()) {
return &lint.LintResult{
Status: lint.Error,
Details: "CRLs MUST use the key identifier method in the authority key identifier extension",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasNumber checks RFC 5280, Section 5.2.3:
// CRL issuers conforming to this profile MUST include this extension in all
// CRLs and MUST mark this extension as non-critical. Conforming CRL issuers
// MUST NOT use CRLNumber values longer than 20 octets.
func hasNumber(crl *crl_x509.RevocationList) *lint.LintResult {
if crl.Number == nil {
return &lint.LintResult{
Status: lint.Error,
Details: "CRLs MUST include the CRL number extension",
}
}
crlNumberOID := asn1.ObjectIdentifier{2, 5, 29, 20} // id-ce-cRLNumber
ext := getExtWithOID(crl.Extensions, crlNumberOID)
if ext != nil && ext.Critical {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL Number MUST NOT be marked critical",
}
}
numBytes := crl.Number.Bytes()
if len(numBytes) > 20 || (len(numBytes) == 20 && numBytes[0]&0x80 != 0) {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL Number MUST NOT be longer than 20 octets",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// isNotDelta checks that the CRL is not a Delta CRL. (RFC 5280, Section 5.2.4).
// There's no requirement against this, but Delta CRLs come with extra
// requirements we don't want to deal with.
func isNotDelta(crl *crl_x509.RevocationList) *lint.LintResult {
deltaCRLIndicatorOID := asn1.ObjectIdentifier{2, 5, 29, 27} // id-ce-deltaCRLIndicator
if getExtWithOID(crl.Extensions, deltaCRLIndicatorOID) != nil {
return &lint.LintResult{
Status: lint.Notice,
Details: "CRL is a Delta CRL",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasNoIDP checks that the CRL does not have an Issuing Distribution Point
// extension (RFC 5280, Section 5.2.5). There's no requirement against this, but
// IDPs come with extra requirements we don't want to deal with.
func hasNoIDP(crl *crl_x509.RevocationList) *lint.LintResult {
idpOID := asn1.ObjectIdentifier{2, 5, 29, 28} // id-ce-issuingDistributionPoint
if getExtWithOID(crl.Extensions, idpOID) != nil {
return &lint.LintResult{
Status: lint.Notice,
Details: "CRL has an Issuing Distribution Point url",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasNoFreshest checks that the CRL is does not have a Freshest CRL extension
// (RFC 5280, Section 5.2.6). There's no requirement against this, but Freshest
// CRL extensions (and the Delta CRLs they imply) come with extra requirements
// we don't want to deal with.
func hasNoFreshest(crl *crl_x509.RevocationList) *lint.LintResult {
freshestOID := asn1.ObjectIdentifier{2, 5, 29, 46} // id-ce-freshestCRL
if getExtWithOID(crl.Extensions, freshestOID) != nil {
return &lint.LintResult{
Status: lint.Notice,
Details: "CRL has a Freshest CRL url",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasNoAIA checks that the CRL is does not have an Authority Information Access
// extension (RFC 5280, Section 5.2.7). There's no requirement against this, but
// AIAs come with extra requirements we don't want to deal with.
func hasNoAIA(crl *crl_x509.RevocationList) *lint.LintResult {
aiaOID := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 1} // id-pe-authorityInfoAccess
if getExtWithOID(crl.Extensions, aiaOID) != nil {
return &lint.LintResult{
Status: lint.Notice,
Details: "CRL has an Authority Information Access url",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasNoCertIssuers checks that the CRL does not have any entries with the
// Certificate Issuer extension (RFC 5280, Section 5.3.3). There is no
// requirement against this, but the presence of this extension would mean that
// the CRL includes certificates issued by an issuer other than the one signing
// the CRL itself, which we don't want to do.
func hasNoCertIssuers(crl *crl_x509.RevocationList) *lint.LintResult {
certIssuerOID := asn1.ObjectIdentifier{2, 5, 29, 29} // id-ce-certificateIssuer
for _, entry := range crl.RevokedCertificates {
if getExtWithOID(entry.Extensions, certIssuerOID) != nil {
return &lint.LintResult{
Status: lint.Notice,
Details: "CRL has an entry with a Certificate Issuer extension",
}
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasAcceptableValidity checks Baseline Requirements, Section 4.9.7:
// The value of the nextUpdate field MUST NOT be more than ten days beyond the
// value of the thisUpdate field.
func hasAcceptableValidity(crl *crl_x509.RevocationList) *lint.LintResult {
validity := crl.NextUpdate.Sub(crl.ThisUpdate)
if validity <= 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL has NextUpdate at or before ThisUpdate",
}
} else if validity > 10*24*time.Hour {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL has validity period greater than ten days",
}
}
return &lint.LintResult{Status: lint.Pass}
}
// noZeroReasonCodes checks Baseline Requirements, Section 7.2.2.1:
// The CRLReason indicated MUST NOT be unspecified (0). If the reason for
// revocation is unspecified, CAs MUST omit reasonCode entry extension, if
// allowed by the previous requirements.
// By extension, it therefore also checks RFC 5280, Section 5.3.1:
// The reason code CRL entry extension SHOULD be absent instead of using the
// unspecified (0) reasonCode value.
func noZeroReasonCodes(crl *crl_x509.RevocationList) *lint.LintResult {
for _, entry := range crl.RevokedCertificates {
if entry.ReasonCode != nil && *entry.ReasonCode == 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL entries MUST NOT contain the unspecified (0) reason code",
}
}
}
return &lint.LintResult{Status: lint.Pass}
}
// noCrticialReasons checks Baseline Requirements, Section 7.2.2.1:
// If present, [the reasonCode] extension MUST NOT be marked critical.
func noCriticalReasons(crl *crl_x509.RevocationList) *lint.LintResult {
reasonCodeOID := asn1.ObjectIdentifier{2, 5, 29, 21} // id-ce-reasonCode
for _, rc := range crl.RevokedCertificates {
for _, ext := range rc.Extensions {
if ext.Id.Equal(reasonCodeOID) && ext.Critical {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL entry reasonCodes MUST NOT be critical",
}
}
}
}
return &lint.LintResult{Status: lint.Pass}
}
// noCertificateHolds checks Baseline Requirements, Section 7.2.2.1:
// The CRLReason MUST NOT be certificateHold (6).
func noCertificateHolds(crl *crl_x509.RevocationList) *lint.LintResult {
for _, entry := range crl.RevokedCertificates {
if entry.ReasonCode != nil && *entry.ReasonCode == 6 {
return &lint.LintResult{
Status: lint.Error,
Details: "CRL entries MUST NOT use the certificateHold (6) reason code",
}
}
}
return &lint.LintResult{Status: lint.Pass}
}
// hasMozReasonCodes checks MRSP v2.8 Section 6.1.1:
// When the CRLReason code is not one of the following, then the reasonCode extension MUST NOT be provided:
// - keyCompromise (RFC 5280 CRLReason #1);
// - privilegeWithdrawn (RFC 5280 CRLReason #9);
// - cessationOfOperation (RFC 5280 CRLReason #5);
// - affiliationChanged (RFC 5280 CRLReason #3); or
// - superseded (RFC 5280 CRLReason #4).
func hasMozReasonCodes(crl *crl_x509.RevocationList) *lint.LintResult {
for _, rc := range crl.RevokedCertificates {
if rc.ReasonCode == nil {
continue
}
switch *rc.ReasonCode {
case 1: // keyCompromise
case 3: // affiliationChanged
case 4: // superseded
case 5: // cessationOfOperation
case 9: // privilegeWithdrawn
continue
default:
return &lint.LintResult{
Status: lint.Error,
Details: "CRLs MUST NOT include reasonCodes other than 1, 3, 4, 5, and 9",
}
}
}
return &lint.LintResult{Status: lint.Pass}
}

View File

@ -0,0 +1,253 @@
package crl
import (
"encoding/pem"
"os"
"testing"
"github.com/letsencrypt/boulder/crl/crl_x509"
"github.com/letsencrypt/boulder/test"
"github.com/zmap/zlint/v3/lint"
)
func loadPEMCRL(t *testing.T, filename string) *crl_x509.RevocationList {
t.Helper()
file, err := os.ReadFile(filename)
test.AssertNotError(t, err, "reading CRL file")
block, rest := pem.Decode(file)
test.AssertEquals(t, block.Type, "X509 CRL")
test.AssertEquals(t, len(rest), 0)
crl, err := crl_x509.ParseRevocationList(block.Bytes)
test.AssertNotError(t, err, "parsing CRL bytes")
return crl
}
func TestHasIssuerName(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasIssuerName(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/no_issuer_name.pem")
res = hasIssuerName(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST have a non-empty issuer")
}
func TestHasNextUpdate(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasNextUpdate(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/no_next_update.pem")
res = hasNextUpdate(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST include the nextUpdate")
}
func TestNoEmptyRevokedCertificatesList(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := noEmptyRevokedCertificatesList(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/none_revoked.pem")
res = noEmptyRevokedCertificatesList(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/empty_revoked.pem")
res = noEmptyRevokedCertificatesList(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "must not be present")
}
func TestHasAKI(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasAKI(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/no_aki.pem")
res = hasAKI(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST include the authority key identifier")
crl = loadPEMCRL(t, "testdata/aki_name_and_serial.pem")
res = hasAKI(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST use the key identifier method")
}
func TestHashNumber(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasNumber(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/no_number.pem")
res = hasNumber(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST include the CRL number")
crl = loadPEMCRL(t, "testdata/critical_number.pem")
res = hasNumber(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT be marked critical")
crl = loadPEMCRL(t, "testdata/long_number.pem")
res = hasNumber(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT be longer than 20 octets")
}
func TestIsNotDelta(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := isNotDelta(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/delta.pem")
res = isNotDelta(crl)
test.AssertEquals(t, res.Status, lint.Notice)
test.AssertContains(t, res.Details, "Delta")
}
func TestHasNoIDP(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasNoIDP(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/idp.pem")
res = hasNoIDP(crl)
test.AssertEquals(t, res.Status, lint.Notice)
test.AssertContains(t, res.Details, "Issuing Distribution Point")
}
func TestHasNoFreshest(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasNoFreshest(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/freshest.pem")
res = hasNoFreshest(crl)
test.AssertEquals(t, res.Status, lint.Notice)
test.AssertContains(t, res.Details, "Freshest")
}
func TestHasNoAIA(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasNoAIA(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/aia.pem")
res = hasNoAIA(crl)
test.AssertEquals(t, res.Status, lint.Notice)
test.AssertContains(t, res.Details, "Authority Information Access")
}
func TestHasNoCertIssuers(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasNoCertIssuers(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/cert_issuer.pem")
res = hasNoCertIssuers(crl)
test.AssertEquals(t, res.Status, lint.Notice)
test.AssertContains(t, res.Details, "Certificate Issuer")
}
func TestHasAcceptableValidity(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasAcceptableValidity(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/negative_validity.pem")
res = hasAcceptableValidity(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "at or before")
crl = loadPEMCRL(t, "testdata/long_validity.pem")
res = hasAcceptableValidity(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "greater than ten days")
}
func TestNoZeroReasonCodes(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := noZeroReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_0.pem")
res = noZeroReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT contain the unspecified")
}
func TestNoCriticalReasons(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := noCriticalReasons(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/critical_reason.pem")
res = noCriticalReasons(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "reasonCodes MUST NOT be critical")
}
func TestNoCertificateHolds(t *testing.T) {
crl := loadPEMCRL(t, "testdata/good.pem")
res := noCertificateHolds(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_6.pem")
res = noCertificateHolds(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT use the certificateHold")
}
func TestHasMozReasonCodes(t *testing.T) {
// good.pem contains a revocation entry with no reason code extension.
crl := loadPEMCRL(t, "testdata/good.pem")
res := hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_0.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT include reasonCodes other than")
crl = loadPEMCRL(t, "testdata/reason_1.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_2.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT include reasonCodes other than")
crl = loadPEMCRL(t, "testdata/reason_3.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_4.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_5.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_6.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT include reasonCodes other than")
crl = loadPEMCRL(t, "testdata/reason_8.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT include reasonCodes other than")
crl = loadPEMCRL(t, "testdata/reason_9.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Pass)
crl = loadPEMCRL(t, "testdata/reason_10.pem")
res = hasMozReasonCodes(crl)
test.AssertEquals(t, res.Status, lint.Error)
test.AssertContains(t, res.Details, "MUST NOT include reasonCodes other than")
}

35
linter/lints/crl/testdata/README.md vendored Normal file
View File

@ -0,0 +1,35 @@
# Test Lint CRLs
The contents of this directory are a variety of PEM-encoded CRLs uses to test
the CRL linting functions in the parent directory.
To create a new test CRL to exercise a new lint:
1. Install the `der2text` and `text2der` tools:
```sh
$ go install github.com/syncsynchalt/der2text/cmds/text2der@latest
$ go install github.com/syncsynchalt/der2text/cmds/der2text@latest
```
2. Use `der2text` to create an editable version of CRL you want to start with, usually `good.pem`:
```sh
$ der2text good.pem > my_new_crl.txt
```
3. Edit the text file. See [the der2text readme](https://github.com/syncsynchalt/der2text) for details about the file format.
4. Write the new PEM file and run the tests to see if it works! Repeat steps 3 and 4 as necessary until you get the correct result.
```sh
$ text2der my_new_crl.txt >| my_new_crl.pem
$ go test ..
```
5. Remove the text file and commit your new CRL.
```sh
$ rm my_new_crl.txt
$ git add .
```

11
linter/lints/crl/testdata/aia.pem vendored Normal file
View File

@ -0,0 +1,11 @@
-----BEGIN X509 CRL-----
MIIBgDCCAQcCAQEwCgYIKoZIzj0EAwMwSTELMAkGA1UEBhMCWFgxFTATBgNVBAoT
DEJvdWxkZXIgVGVzdDEjMCEGA1UEAxMaKFRFU1QpIEVsZWdhbnQgRWxlcGhhbnQg
RTEXDTIyMDcwNjE2NDMzOFoXDTIyMDcxNTE2NDMzOFowKTAnAggDrlHbURVaPBcN
MjIwNzA2MTU0MzM4WjAMMAoGA1UdFQQDCgEBoGIwYDAfBgNVHSMEGDAWgBQB2rt6
yyUgjl551vmWQi8CQSkHvjARBgNVHRQECgIIFv9LJt+yGA8wKgYIKwYBBQUHAQEE
HjAcMBoGCCsGAQUFBzABgg5lMS5vLmxlbmNyLm9yZzAKBggqhkjOPQQDAwNnADBk
AjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQsHqk
qEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E4c5Z
HFHTQg==
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBazCB8wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgTjBMMDcGA1UdIwQwMC6BFzAVghNp
bnQtZTEuYm91bGRlci50ZXN0ghMCEQChCjEx4ZnD1S6gsNFjWXmlMBEGA1UdFAQK
AggW/0sm37IYDzAKBggqhkjOPQQDAwNnADBkAjBWshNFi60aMWl82G3TwIuwBBK+
dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQsHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4
hMicW23oENHmLNZQx9ddruZeFJDqKD2E4c5ZHFHTQg==
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBczCB+wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjBJMEcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMCwwCgYDVR0VBAMKAQEwHgYDVR0dBBcwFYITaW50LWUxLmJv
dWxkZXIudGVzdKA2MDQwHwYDVR0jBBgwFoAUAdq7esslII5eedb5lkIvAkEpB74w
EQYDVR0UBAoCCBb/SybfshgPMAoGCCqGSM49BAMDA2cAMGQCMFayE0WLrRoxaXzY
bdPAi7AEEr53OIulDND4vPlN0/A0RyJiIrgfXEPqsVCweqSoQQIwW7hgsE6Ke7wn
xjuxc+jdK7iEyJxbbegQ0eYs1lDH112u5l4UkOooPYThzlkcUdNC
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBVjCB3gIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgOTA3MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBQGA1UdFAEB/wQKAggW/0sm37IYDzAKBggqhkjOPQQD
AwNnADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD
6rFQsHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDq
KD2E4c5ZHFHTQg==
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBVjCB3gIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjAsMCoCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMA8wDQYDVR0VAQH/BAMKAQGgNjA0MB8GA1UdIwQYMBaAFAHa
u3rLJSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQD
AwNnADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD
6rFQsHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDq
KD2E4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/delta.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBZjCB7gIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgSTBHMB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzARBgNVHRsECgIIFv9L
Jt+yGA4wCgYIKoZIzj0EAwMDZwAwZAIwVrITRYutGjFpfNht08CLsAQSvnc4i6UM
0Pi8+U3T8DRHImIiuB9cQ+qxULB6pKhBAjBbuGCwTop7vCfGO7Fz6N0ruITInFtt
6BDR5izWUMfXXa7mXhSQ6ig9hOHOWRxR00I=
-----END X509 CRL-----

View File

@ -0,0 +1,9 @@
-----BEGIN X509 CRL-----
MIIBKjCBsgIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjAAoDYwNDAfBgNVHSMEGDAW
gBQB2rt6yyUgjl551vmWQi8CQSkHvjARBgNVHRQECgIIFv9LJt+yGA8wCgYIKoZI
zj0EAwMDZwAwZAIwVrITRYutGjFpfNht08CLsAQSvnc4i6UM0Pi8+U3T8DRHImIi
uB9cQ+qxULB6pKhBAjBbuGCwTop7vCfGO7Fz6N0ruITInFtt6BDR5izWUMfXXa7m
XhSQ6ig9hOHOWRxR00I=
-----END X509 CRL-----

10
linter/lints/crl/testdata/freshest.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBdjCB/gIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgWTBXMB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAhBgNVHS4EGjAYMBaA
FIASMBCCDmUxLmMubGVuY3Iub3JnMAoGCCqGSM49BAMDA2cAMGQCMFayE0WLrRox
aXzYbdPAi7AEEr53OIulDND4vPlN0/A0RyJiIrgfXEPqsVCweqSoQQIwW7hgsE6K
e7wnxjuxc+jdK7iEyJxbbegQ0eYs1lDH112u5l4UkOooPYThzlkcUdNC
-----END X509 CRL-----

9
linter/lints/crl/testdata/good.pem vendored Normal file
View File

@ -0,0 +1,9 @@
-----BEGIN X509 CRL-----
MIIBRTCBzQIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjAbMBkCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaoDYwNDAfBgNVHSMEGDAWgBQB2rt6yyUgjl551vmWQi8CQSkH
vjARBgNVHRQECgIIFv9LJt+yGA8wCgYIKoZIzj0EAwMDZwAwZAIwVrITRYutGjFp
fNht08CLsAQSvnc4i6UM0Pi8+U3T8DRHImIiuB9cQ+qxULB6pKhBAjBbuGCwTop7
vCfGO7Fz6N0ruITInFtt6BDR5izWUMfXXa7mXhSQ6ig9hOHOWRxR00I=
-----END X509 CRL-----

10
linter/lints/crl/testdata/good_old.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/idp.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBYTCB6QIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgRDBCMB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAMBgNVHRwEBTADgQH/
MAoGCCqGSM49BAMDA2cAMGQCMFayE0WLrRoxaXzYbdPAi7AEEr53OIulDND4vPlN
0/A0RyJiIrgfXEPqsVCweqSoQQIwW7hgsE6Ke7wnxjuxc+jdK7iEyJxbbegQ0eYs
1lDH112u5l4UkOooPYThzlkcUdNC
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBYDCB6AIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgQzBBMB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MB4GA1UdFAQXAhUW/0sm37IYDxb/SybfshgPFv9LJt8w
CgYIKoZIzj0EAwMDZwAwZAIwVrITRYutGjFpfNht08CLsAQSvnc4i6UM0Pi8+U3T
8DRHImIiuB9cQ+qxULB6pKhBAjBbuGCwTop7vCfGO7Fz6N0ruITInFtt6BDR5izW
UMfXXa7mXhSQ6ig9hOHOWRxR00I=
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE2MTY0MzM5WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzA2MTY0MzM3WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

9
linter/lints/crl/testdata/no_aki.pem vendored Normal file
View File

@ -0,0 +1,9 @@
-----BEGIN X509 CRL-----
MIIBMjCBugIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgFTATMBEGA1UdFAQKAggW/0sm37IY
DzAKBggqhkjOPQQDAwNnADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5
TdPwNEciYiK4H1xD6rFQsHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHm
LNZQx9ddruZeFJDqKD2E4c5ZHFHTQg==
-----END X509 CRL-----

View File

@ -0,0 +1,8 @@
-----BEGIN X509 CRL-----
MIIBCjCBkgIBATAKBggqhkjOPQQDAzAAFw0yMjA3MDYxNjQzMzhaFw0yMjA3MTUx
NjQzMzhaMCkwJwIIA65R21EVWjwXDTIyMDcwNjE1NDMzOFowDDAKBgNVHRUEAwoB
AaA2MDQwHwYDVR0jBBgwFoAUAdq7esslII5eedb5lkIvAkEpB74wEQYDVR0UBAoC
CBb/SybfshgPMAoGCCqGSM49BAMDA2cAMGQCMFayE0WLrRoxaXzYbdPAi7AEEr53
OIulDND4vPlN0/A0RyJiIrgfXEPqsVCweqSoQQIwW7hgsE6Ke7wnxjuxc+jdK7iE
yJxbbegQ0eYs1lDH112u5l4UkOooPYThzlkcUdNC
-----END X509 CRL-----

View File

@ -0,0 +1,9 @@
-----BEGIN X509 CRL-----
MIIBRDCBzAIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0yMjA3MDYxNTQzMzhaMAww
CgYDVR0VBAMKAQGgNjA0MB8GA1UdIwQYMBaAFAHau3rLJSCOXnnW+ZZCLwJBKQe+
MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNnADBkAjBWshNFi60aMWl8
2G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQsHqkqEECMFu4YLBOinu8
J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E4c5ZHFHTQg==
-----END X509 CRL-----

View File

@ -0,0 +1,9 @@
-----BEGIN X509 CRL-----
MIIBQDCByAIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgIzAhMB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MAoGCCqGSM49BAMDA2cAMGQCMFayE0WLrRoxaXzYbdPA
i7AEEr53OIulDND4vPlN0/A0RyJiIrgfXEPqsVCweqSoQQIwW7hgsE6Ke7wnxjux
c+jdK7iEyJxbbegQ0eYs1lDH112u5l4UkOooPYThzlkcUdNC
-----END X509 CRL-----

View File

@ -0,0 +1,9 @@
-----BEGIN X509 CRL-----
MIIBKDCBsAIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WqA2MDQwHwYDVR0jBBgwFoAU
Adq7esslII5eedb5lkIvAkEpB74wEQYDVR0UBAoCCBb/SybfshgPMAoGCCqGSM49
BAMDA2cAMGQCMFayE0WLrRoxaXzYbdPAi7AEEr53OIulDND4vPlN0/A0RyJiIrgf
XEPqsVCweqSoQQIwW7hgsE6Ke7wnxjuxc+jdK7iEyJxbbegQ0eYs1lDH112u5l4U
kOooPYThzlkcUdNC
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_0.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQCgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_1.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_10.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQqgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_2.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQKgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_3.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQOgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_4.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQSgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_5.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQWgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_6.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQagNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_8.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQigNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

10
linter/lints/crl/testdata/reason_9.pem vendored Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQmgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----

View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBUzCB2wIBATAKBggqhkjOPQQDAzBJMQswCQYDVQQGEwJYWDEVMBMGA1UEChMM
Qm91bGRlciBUZXN0MSMwIQYDVQQDExooVEVTVCkgRWxlZ2FudCBFbGVwaGFudCBF
MRcNMjIwNzA2MTY0MzM4WhcNMjIwNzE1MTY0MzM4WjApMCcCCAOuUdtRFVo8Fw0y
MjA3MDYxNTQzMzhaMAwwCgYDVR0VBAMKAQGgNjA0MB8GA1UdIwQYMBaAFAHau3rL
JSCOXnnW+ZZCLwJBKQe+MBEGA1UdFAQKAggW/0sm37IYDzAKBggqhkjOPQQDAwNn
ADBkAjBWshNFi60aMWl82G3TwIuwBBK+dziLpQzQ+Lz5TdPwNEciYiK4H1xD6rFQ
sHqkqEECMFu4YLBOinu8J8Y7sXPo3Su4hMicW23oENHmLNZQx9ddruZeFJDqKD2E
4c5ZHFHTQg==
-----END X509 CRL-----