1use super::{Authenticated, IssuingDistributionPoint, RevokedCertificate};
4use crate::error::{Error, ErrorCode, Located, Result};
5use crate::extension::{self, Extension};
6use crate::general_name::{self, GeneralName};
7use crate::{Certificate, Time, oid};
8use structured_asn1::memory::{OwnedVec, Work};
9use structured_asn1::{Oid, Span, Tag, universal};
10
11#[derive(Debug, Clone, Copy)]
14pub enum EntryLookup<'r, 'a> {
15 Absent,
17 Found(&'r RevokedCertificate<'a>),
19 Conflict {
21 first: &'r RevokedCertificate<'a>,
23 second: &'r RevokedCertificate<'a>,
25 },
26 Unsupported {
28 kind: Oid<'a>,
30 span: Span,
32 },
33}
34
35impl<'r, 'a> Authenticated<'r, 'a> {
36 pub fn entry<W: Work>(
40 &self,
41 target: &Certificate<'_>,
42 execution: &W,
43 ) -> Result<EntryLookup<'r, 'a>> {
44 const FIELD: &str = "certificateList.entryLookup";
45 let crl = self.crl();
46 execution
47 .charge_work(1)
48 .map_err(|reason| Error::resource(reason, FIELD, crl.span().start))?;
49 let extensions = crl.tbs().extensions();
50 let is_delta = delta_source(crl, execution)?;
51 let idp = extensions
52 .get(oid::ISSUING_DISTRIBUTION_POINT, execution)?
53 .map(|extension| IssuingDistributionPoint::read(extension, execution))
54 .transpose()?;
55 let indirect = idp
56 .as_ref()
57 .is_some_and(IssuingDistributionPoint::is_indirect);
58 if let Some(extension) = check_source_metadata(crl, is_delta, execution)? {
59 return Ok(EntryLookup::Unsupported {
60 kind: extension.kind(),
61 span: extension.span(),
62 });
63 }
64 let target_serial = target.tbs().serial().as_bytes();
65 let target_issuer = target.tbs().issuer().as_bytes();
66 let mut inherited = None;
67 let mut default_matches = None;
68 let mut found = None;
69 let mut conflict = None;
70 for entry in crl.tbs().revoked_certificates() {
71 execution
72 .charge_work(1)
73 .map_err(|reason| Error::resource(reason, FIELD, entry.span.start))?;
74 for extension in entry.extensions.criticals(execution) {
78 let extension = extension?;
79 let kind = extension.kind();
80 execution
81 .charge_work(19)
82 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
83 if kind != oid::CRL_REASON
84 && kind != oid::INVALIDITY_DATE
85 && kind != oid::CERTIFICATE_ISSUER
86 {
87 return Ok(EntryLookup::Unsupported {
88 kind,
89 span: extension.span(),
90 });
91 }
92 }
93 validate_status_extensions(entry, is_delta, execution)?;
95 if let Some(names) = validated_issuer(entry, indirect, execution)? {
96 inherited = Some(names);
97 }
98 let mut issuer_matches = false;
103 if let Some(names) = &inherited {
104 for name in names {
105 execution
106 .charge_work(1)
107 .map_err(|reason| Error::resource(reason, FIELD, entry.span.start))?;
108 if let GeneralName::Directory(name) = name
109 && equal_bytes(name.as_bytes(), target_issuer, execution, entry.span.start)?
110 {
111 issuer_matches = true;
112 break;
113 }
114 }
115 } else {
116 issuer_matches = if let Some(matched) = default_matches {
117 matched
118 } else {
119 let matched = crl
120 .tbs()
121 .issuer()
122 .matches(target.tbs().issuer(), execution)?;
123 default_matches = Some(matched);
124 matched
125 };
126 }
127 if issuer_matches
128 && equal_bytes(
129 entry.serial.as_bytes(),
130 target_serial,
131 execution,
132 entry.span.start,
133 )?
134 {
135 if let Some(previous) = found {
136 if conflict.is_none() {
137 conflict = Some((previous, entry));
138 }
139 } else {
140 found = Some(entry);
141 }
142 }
143 }
144 if let Some((first, second)) = conflict {
145 return Ok(EntryLookup::Conflict { first, second });
146 }
147 Ok(match found {
148 Some(entry) => EntryLookup::Found(entry),
149 None => EntryLookup::Absent,
150 })
151 }
152}
153
154fn validate_status_extensions<W: Work>(
155 entry: &RevokedCertificate<'_>,
156 delta: bool,
157 execution: &W,
158) -> Result<()> {
159 if entry.reason(execution)? == Some(Reason::RemoveFromCrl) && !delta {
163 return Err(Error::new(
164 ErrorCode::MalformedValue,
165 "extensions.reasonCode",
166 entry.span.start,
167 ));
168 }
169 entry.invalidity_date(execution)?;
170 Ok(())
171}
172
173fn delta_source<W: Work>(crl: &super::CertificateList<'_>, execution: &W) -> Result<bool> {
174 let Some(indicator) = crl
175 .tbs()
176 .extensions()
177 .get(oid::DELTA_CRL_INDICATOR, execution)?
178 else {
179 return Ok(false);
180 };
181 if !indicator.is_critical() {
182 return Err(Error::new(
183 ErrorCode::MalformedValue,
184 "extensions.deltaCRLIndicator",
185 indicator.span().start,
186 ));
187 }
188 super::delta::number(indicator, execution)?;
189 Ok(true)
190}
191
192fn check_source_metadata<'r, 'a, W: Work>(
193 crl: &'r super::CertificateList<'a>,
194 is_delta: bool,
195 execution: &W,
196) -> Result<Option<&'r Extension<'a>>> {
197 const FIELD: &str = "certificateList.extensions";
198 for extension in crl.tbs().extensions().iter(execution) {
199 let extension = extension?;
200 execution
201 .charge_work(52)
204 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
205 let kind = extension.kind();
206 let malformed = || Error::new(ErrorCode::MalformedValue, FIELD, extension.span().start);
207 if kind == oid::ISSUING_DISTRIBUTION_POINT {
208 if !extension.is_critical() {
211 return Err(malformed());
212 }
213 } else if kind == oid::CRL_NUMBER {
214 if extension.is_critical() {
218 return Err(malformed());
219 }
220 super::delta::number(extension, execution)?;
221 } else if kind == oid::DELTA_CRL_INDICATOR {
222 } else if kind == oid::AUTHORITY_KEY_IDENTIFIER {
224 if extension.is_critical() {
228 return Err(malformed());
229 }
230 let identifier = crate::identifier::AuthorityKeyIdentifier::read(extension, execution)?;
231 if identifier.key_id().is_none() {
232 return Err(malformed());
233 }
234 } else if kind == oid::ISSUER_ALT_NAME {
235 general_name::read_alternative(extension, execution)?;
236 } else if kind == oid::AUTHORITY_INFO_ACCESS {
237 if extension.is_critical() {
241 return Err(malformed());
242 }
243 } else if kind == oid::FRESHEST_CRL {
244 if extension.is_critical() || is_delta {
248 return Err(malformed());
249 }
250 let points = crate::access::DistributionPoints::read(extension, execution)?;
251 for point in points.points() {
252 execution
253 .charge_work(1)
254 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
255 if point.reasons().is_some() || !point.issuer().is_empty() {
256 return Err(malformed());
257 }
258 }
259 } else if extension.is_critical() {
260 return Ok(Some(extension));
261 }
262 }
263 Ok(None)
264}
265
266fn validated_issuer<'a, W: Work>(
267 entry: &RevokedCertificate<'a>,
268 indirect: bool,
269 execution: &W,
270) -> Result<Option<OwnedVec<GeneralName<'a>>>> {
271 const FIELD: &str = "extensions.certificateIssuer";
272 let Some(extension) = entry.extensions.get(oid::CERTIFICATE_ISSUER, execution)? else {
273 return Ok(None);
274 };
275 if !indirect || !extension.is_critical() {
276 return Err(Error::new(
277 ErrorCode::MalformedValue,
278 FIELD,
279 extension.span().start,
280 ));
281 }
282 let mut reader = extension::sequence(extension, FIELD, execution)?;
283 let names = general_name::read_list(&mut reader, FIELD, extension.span().start)?;
284 let mut directory = false;
285 for name in &names {
286 execution
287 .charge_work(1)
288 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
289 if matches!(name, GeneralName::Directory(name) if !name.is_empty()) {
290 directory = true;
291 }
292 }
293 if !directory {
294 return Err(Error::new(
295 ErrorCode::MalformedValue,
296 FIELD,
297 extension.span().start,
298 ));
299 }
300 Ok(Some(names))
301}
302
303fn equal_bytes(left: &[u8], right: &[u8], execution: &impl Work, at: u64) -> Result<bool> {
304 let work = (left.len().max(right.len()) as u64)
305 .checked_mul(2)
306 .and_then(|n| n.checked_add(1))
307 .ok_or_else(|| {
308 Error::resource(
309 structured_asn1::memory::MemoryError::WorkExceeded,
310 "certificateList.entryLookup",
311 at,
312 )
313 })?;
314 execution
315 .charge_work(work)
316 .map_err(|reason| Error::resource(reason, "certificateList.entryLookup", at))?;
317 Ok(left == right)
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub enum Reason {
324 Unspecified,
326 KeyCompromise,
328 AuthorityCompromise,
330 AffiliationChanged,
332 Superseded,
334 CessationOfOperation,
336 CertificateHold,
338 RemoveFromCrl,
340 PrivilegeWithdrawn,
342 AttributeAuthorityCompromise,
344}
345
346impl<'a> RevokedCertificate<'a> {
347 pub fn reason<W: Work>(&self, execution: &W) -> Result<Option<Reason>> {
350 self.extensions
351 .get(oid::CRL_REASON, execution)?
352 .map(|extension| read_reason(extension, execution))
353 .transpose()
354 }
355
356 pub fn invalidity_date<W: Work>(&self, execution: &W) -> Result<Option<Time>> {
358 const FIELD: &str = "extensions.invalidityDate";
359 let Some(extension) = self.extensions.get(oid::INVALIDITY_DATE, execution)? else {
360 return Ok(None);
361 };
362 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
363 let element = reader
364 .expect(Tag::primitive(universal::GENERALIZED_TIME))
365 .field(FIELD)?;
366 super::end(&reader, FIELD, extension.span().start)?;
367 crate::time::read_in(&reader, &element, FIELD).map(Some)
368 }
369
370 pub fn certificate_issuer<W: Work>(
373 &self,
374 execution: &W,
375 ) -> Result<Option<OwnedVec<GeneralName<'a>>>> {
376 const FIELD: &str = "extensions.certificateIssuer";
377 let Some(extension) = self.extensions.get(oid::CERTIFICATE_ISSUER, execution)? else {
378 return Ok(None);
379 };
380 let mut reader = extension::sequence(extension, FIELD, execution)?;
381 general_name::read_list(&mut reader, FIELD, extension.span().start).map(Some)
382 }
383}
384
385fn read_reason<W: Work>(extension: &Extension<'_>, execution: &W) -> Result<Reason> {
386 const FIELD: &str = "extensions.reasonCode";
387 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
388 let element = reader.expect(Tag::primitive(10)).field(FIELD)?;
389 super::end(&reader, FIELD, extension.span().start)?;
390 reason_element(&reader, &element, FIELD)
391}
392
393pub(crate) fn reason_element(
395 parent: &structured_asn1::ScopedReader<'_, '_>,
396 element: &structured_asn1::Element<'_>,
397 field: &'static str,
398) -> Result<Reason> {
399 parent.charge(9).field(field)?;
400 let value = parent.integer(element).field(field)?;
401 match value.as_u64() {
405 Some(0) => Ok(Reason::Unspecified),
406 Some(1) => Ok(Reason::KeyCompromise),
407 Some(2) => Ok(Reason::AuthorityCompromise),
408 Some(3) => Ok(Reason::AffiliationChanged),
409 Some(4) => Ok(Reason::Superseded),
410 Some(5) => Ok(Reason::CessationOfOperation),
411 Some(6) => Ok(Reason::CertificateHold),
412 Some(8) => Ok(Reason::RemoveFromCrl),
413 Some(9) => Ok(Reason::PrivilegeWithdrawn),
414 Some(10) => Ok(Reason::AttributeAuthorityCompromise),
415 _ => Err(Error::new(
416 ErrorCode::MalformedValue,
417 field,
418 element.span().start,
419 )),
420 }
421}