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
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
56
57 idpOID := asn1.ObjectIdentifier{2, 5, 29, 28}
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
73
74
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
155
156
157
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