...

Source file src/github.com/letsencrypt/boulder/linter/lints/cpcps/lint_crl_has_idp.go

Documentation: github.com/letsencrypt/boulder/linter/lints/cpcps

     1  package cpcps
     2  
     3  import (
     4  	"net/url"
     5  
     6  	"github.com/zmap/zcrypto/encoding/asn1"
     7  	"github.com/zmap/zcrypto/x509"
     8  	"github.com/zmap/zlint/v3/lint"
     9  	"golang.org/x/crypto/cryptobyte"
    10  	cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
    11  
    12  	"github.com/letsencrypt/boulder/linter/lints"
    13  )
    14  
    15  type crlHasIDP struct{}
    16  
    17  /************************************************
    18  Various root programs (and the BRs, after Ballot SC-063 passes) require that
    19  sharded/partitioned CRLs have a specifically-encoded Issuing Distribution Point
    20  extension. Since there's no way to tell from the CRL itself whether or not it
    21  is sharded, we apply this lint universally to all CRLs, but as part of the Let's
    22  Encrypt-specific suite of lints.
    23  ************************************************/
    24  
    25  func init() {
    26  	lint.RegisterRevocationListLint(&lint.RevocationListLint{
    27  		LintMetadata: lint.LintMetadata{
    28  			Name:          "e_crl_has_idp",
    29  			Description:   "Let's Encrypt CRLs must have the Issuing Distribution Point extension with appropriate contents",
    30  			Citation:      "",
    31  			Source:        lints.LetsEncryptCPS,
    32  			EffectiveDate: lints.CPSV33Date,
    33  		},
    34  		Lint: NewCrlHasIDP,
    35  	})
    36  }
    37  
    38  func NewCrlHasIDP() lint.RevocationListLintInterface {
    39  	return &crlHasIDP{}
    40  }
    41  
    42  func (l *crlHasIDP) CheckApplies(c *x509.RevocationList) bool {
    43  	return true
    44  }
    45  
    46  func (l *crlHasIDP) Execute(c *x509.RevocationList) *lint.LintResult {
    47  	/*
    48  		Let's Encrypt issues CRLs for two distinct purposes:
    49  		   1) CRLs containing subscriber certificates created by the
    50  		      crl-updater. These CRLs must have only the distributionPoint and
    51  		      onlyContainsUserCerts fields set.
    52  		   2) CRLs containing subordinate CA certificates created by the
    53  		      ceremony tool. These CRLs must only have the onlyContainsCACerts
    54  		      field set.
    55  	*/
    56  
    57  	idpOID := asn1.ObjectIdentifier{2, 5, 29, 28} // id-ce-issuingDistributionPoint
    58  	idpe := lints.GetExtWithOID(c.Extensions, idpOID)
    59  	if idpe == nil {
    60  		return &lint.LintResult{
    61  			Status:  lint.Warn,
    62  			Details: "CRL missing IssuingDistributionPoint",
    63  		}
    64  	}
    65  	if !idpe.Critical {
    66  		return &lint.LintResult{
    67  			Status:  lint.Error,
    68  			Details: "IssuingDistributionPoint MUST be critical",
    69  		}
    70  	}
    71  
    72  	// Step inside the outer issuingDistributionPoint sequence to get access to
    73  	// its constituent fields: distributionPoint [0],
    74  	// onlyContainsUserCerts [1], and onlyContainsCACerts [2].
    75  	idpv := cryptobyte.String(idpe.Value)
    76  	if !idpv.ReadASN1(&idpv, cryptobyte_asn1.SEQUENCE) {
    77  		return &lint.LintResult{
    78  			Status:  lint.Warn,
    79  			Details: "Failed to read issuingDistributionPoint",
    80  		}
    81  	}
    82  
    83  	var dpName cryptobyte.String
    84  	var distributionPointExists bool
    85  	distributionPointTag := cryptobyte_asn1.Tag(0).ContextSpecific().Constructed()
    86  	if !idpv.ReadOptionalASN1(&dpName, &distributionPointExists, distributionPointTag) {
    87  		return &lint.LintResult{
    88  			Status:  lint.Warn,
    89  			Details: "Failed to read IssuingDistributionPoint distributionPoint",
    90  		}
    91  	}
    92  
    93  	idp := lints.NewIssuingDistributionPoint()
    94  	if distributionPointExists {
    95  		lintErr := parseSingleDistributionPointName(&dpName, idp)
    96  		if lintErr != nil {
    97  			return lintErr
    98  		}
    99  	}
   100  
   101  	onlyContainsUserCertsTag := cryptobyte_asn1.Tag(1).ContextSpecific()
   102  	if !lints.ReadOptionalASN1BooleanWithTag(&idpv, &idp.OnlyContainsUserCerts, onlyContainsUserCertsTag, false) {
   103  		return &lint.LintResult{
   104  			Status:  lint.Error,
   105  			Details: "Failed to read IssuingDistributionPoint onlyContainsUserCerts",
   106  		}
   107  	}
   108  
   109  	onlyContainsCACertsTag := cryptobyte_asn1.Tag(2).ContextSpecific()
   110  	if !lints.ReadOptionalASN1BooleanWithTag(&idpv, &idp.OnlyContainsCACerts, onlyContainsCACertsTag, false) {
   111  		return &lint.LintResult{
   112  			Status:  lint.Error,
   113  			Details: "Failed to read IssuingDistributionPoint onlyContainsCACerts",
   114  		}
   115  	}
   116  
   117  	if !idpv.Empty() {
   118  		return &lint.LintResult{
   119  			Status:  lint.Error,
   120  			Details: "Unexpected IssuingDistributionPoint fields were found",
   121  		}
   122  	}
   123  
   124  	if idp.OnlyContainsUserCerts {
   125  		if idp.OnlyContainsCACerts {
   126  			return &lint.LintResult{
   127  				Status:  lint.Error,
   128  				Details: "IssuingDistributionPoint should not have both onlyContainsUserCerts: TRUE and onlyContainsCACerts: TRUE",
   129  			}
   130  		}
   131  		if idp.DistributionPointURI == nil {
   132  			return &lint.LintResult{
   133  				Status:  lint.Error,
   134  				Details: "IssuingDistributionPoint should have both DistributionPointName and onlyContainsUserCerts: TRUE",
   135  			}
   136  		}
   137  	} else if idp.OnlyContainsCACerts {
   138  		if idp.DistributionPointURI != nil {
   139  			return &lint.LintResult{
   140  				Status:  lint.Error,
   141  				Details: "IssuingDistributionPoint should not have both DistributionPointName and onlyContainsCACerts: TRUE",
   142  			}
   143  		}
   144  	} else {
   145  		return &lint.LintResult{
   146  			Status:  lint.Error,
   147  			Details: "Neither onlyContainsUserCerts nor onlyContainsCACerts was set",
   148  		}
   149  	}
   150  
   151  	return &lint.LintResult{Status: lint.Pass}
   152  }
   153  
   154  // parseSingleDistributionPointName examines the provided distributionPointName
   155  // and updates idp with the URI if it is found. The distribution point name is
   156  // checked for validity and returns a non-nil LintResult if there were any
   157  // problems.
   158  func parseSingleDistributionPointName(distributionPointName *cryptobyte.String, idp *lints.IssuingDistributionPoint) *lint.LintResult {
   159  	fullNameTag := cryptobyte_asn1.Tag(0).ContextSpecific().Constructed()
   160  	if !distributionPointName.ReadASN1(distributionPointName, fullNameTag) {
   161  		return &lint.LintResult{
   162  			Status:  lint.Warn,
   163  			Details: "Failed to read IssuingDistributionPoint distributionPoint fullName",
   164  		}
   165  	}
   166  
   167  	var uriBytes []byte
   168  	uriTag := cryptobyte_asn1.Tag(6).ContextSpecific()
   169  	if !distributionPointName.ReadASN1Bytes(&uriBytes, uriTag) {
   170  		return &lint.LintResult{
   171  			Status:  lint.Warn,
   172  			Details: "Failed to read IssuingDistributionPoint URI",
   173  		}
   174  	}
   175  	var err error
   176  	idp.DistributionPointURI, err = url.Parse(string(uriBytes))
   177  	if err != nil {
   178  		return &lint.LintResult{
   179  			Status:  lint.Error,
   180  			Details: "Failed to parse IssuingDistributionPoint URI",
   181  		}
   182  	}
   183  	if idp.DistributionPointURI.Scheme != "http" {
   184  		return &lint.LintResult{
   185  			Status:  lint.Error,
   186  			Details: "IssuingDistributionPoint URI MUST use http scheme",
   187  		}
   188  	}
   189  	if !distributionPointName.Empty() {
   190  		return &lint.LintResult{
   191  			Status:  lint.Warn,
   192  			Details: "IssuingDistributionPoint should contain only one distributionPoint",
   193  		}
   194  	}
   195  
   196  	return nil
   197  }
   198  

View as plain text