1use super::{Authenticated, IssuingDistributionPoint, RevocationTarget, RevokedCertificate};
4use crate::error::{Error, ErrorCode, Located, Result};
5use crate::extension::{self, Extension};
6use crate::general_name::{self, GeneralName};
7use crate::{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<'t, T: RevocationTarget<'t>, W: Work>(
40 &self,
41 target: &T,
42 execution: &W,
43 ) -> Result<EntryLookup<'r, 'a>> {
44 const FIELD: &str = "certificateList.entryLookup";
45 let crl = self.crl();
46 let execution = &crl.tbs().signature().query_in(execution);
47 let target_execution = target.execution_in(execution);
48 target_execution
49 .charge_work(1)
50 .map_err(|e| Error::resource(e, FIELD, target.span().start))?;
51 execution
52 .charge_work(1)
53 .map_err(|reason| Error::resource(reason, FIELD, crl.span().start))?;
54 let is_delta = delta_source(crl, execution)?;
55 let indirect = indirect_source(crl, execution)?;
56 if let Some(extension) = check_source_metadata(crl, is_delta, execution)? {
57 return Ok(EntryLookup::Unsupported {
58 kind: extension.kind(),
59 span: extension.span(),
60 });
61 }
62 let target_serial = target.serial().as_bytes();
63 let target_issuer = target.issuer().as_bytes();
64 let mut inherited = None;
65 let mut default_matches = None;
66 let mut found = None;
67 let mut conflict = None;
68 for entry in crl.tbs().revoked_certificates() {
69 execution
70 .charge_work(1)
71 .map_err(|reason| Error::resource(reason, FIELD, entry.span.start))?;
72 if let Some(extension) = unsupported_entry(entry, execution)? {
76 return Ok(EntryLookup::Unsupported {
77 kind: extension.kind(),
78 span: extension.span(),
79 });
80 }
81 validate_status_extensions(entry, is_delta, execution)?;
83 if let Some(names) = validated_issuer(entry, indirect, execution)? {
84 inherited = Some(names);
85 }
86 let mut issuer_matches = false;
91 if let Some(names) = &inherited {
92 for name in names {
93 execution
94 .charge_work(1)
95 .map_err(|reason| Error::resource(reason, FIELD, entry.span.start))?;
96 if let GeneralName::Directory(name) = name {
97 target_execution
98 .charge_work(target_issuer.len() as u64)
99 .map_err(|e| Error::resource(e, FIELD, target.span().start))?;
100 if equal_bytes(name.as_bytes(), target_issuer, execution, entry.span.start)?
101 {
102 issuer_matches = true;
103 break;
104 }
105 }
106 }
107 } else {
108 issuer_matches = if let Some(matched) = default_matches {
109 matched
110 } else {
111 let matched = target
112 .issuer()
113 .matches(crl.tbs().issuer(), &target_execution)?;
114 default_matches = Some(matched);
115 matched
116 };
117 }
118 if issuer_matches {
119 target_execution
120 .charge_work(target_serial.len() as u64)
121 .map_err(|e| Error::resource(e, FIELD, target.span().start))?;
122 }
123 if issuer_matches
124 && equal_bytes(
125 entry.serial.as_bytes(),
126 target_serial,
127 execution,
128 entry.span.start,
129 )?
130 {
131 if let Some(previous) = found {
132 if conflict.is_none() {
133 conflict = Some((previous, entry));
134 }
135 } else {
136 found = Some(entry);
137 }
138 }
139 }
140 if let Some((first, second)) = conflict {
141 return Ok(EntryLookup::Conflict { first, second });
142 }
143 Ok(match found {
144 Some(entry) => EntryLookup::Found(entry),
145 None => EntryLookup::Absent,
146 })
147 }
148}
149
150fn indirect_source(crl: &super::CertificateList<'_>, execution: &impl Work) -> Result<bool> {
151 let idp = crl
152 .tbs()
153 .extensions()
154 .get(oid::ISSUING_DISTRIBUTION_POINT, execution)?
155 .map(|extension| IssuingDistributionPoint::read(extension, execution))
156 .transpose()?;
157 Ok(idp
158 .as_ref()
159 .is_some_and(IssuingDistributionPoint::is_indirect))
160}
161
162fn unsupported_entry<'r, 'a>(
163 entry: &'r RevokedCertificate<'a>,
164 execution: &impl Work,
165) -> Result<Option<&'r Extension<'a>>> {
166 for extension in entry.extensions.criticals(execution) {
167 let extension = extension?;
168 execution.charge_work(19).map_err(|reason| {
169 Error::resource(
170 reason,
171 "certificateList.entryLookup",
172 extension.span().start,
173 )
174 })?;
175 let kind = extension.kind();
176 if kind != oid::CRL_REASON
177 && kind != oid::INVALIDITY_DATE
178 && kind != oid::CERTIFICATE_ISSUER
179 {
180 return Ok(Some(extension));
181 }
182 }
183 Ok(None)
184}
185
186fn validate_status_extensions<W: Work>(
187 entry: &RevokedCertificate<'_>,
188 delta: bool,
189 execution: &W,
190) -> Result<()> {
191 if entry.reason(execution)? == Some(Reason::RemoveFromCrl) && !delta {
195 return Err(Error::new(
196 ErrorCode::MalformedValue,
197 "extensions.reasonCode",
198 entry.span.start,
199 ));
200 }
201 entry.invalidity_date(execution)?;
202 Ok(())
203}
204
205fn delta_source<W: Work>(crl: &super::CertificateList<'_>, execution: &W) -> Result<bool> {
206 let Some(indicator) = crl
207 .tbs()
208 .extensions()
209 .get(oid::DELTA_CRL_INDICATOR, execution)?
210 else {
211 return Ok(false);
212 };
213 if !indicator.is_critical() {
214 return Err(Error::new(
215 ErrorCode::MalformedValue,
216 "extensions.deltaCRLIndicator",
217 indicator.span().start,
218 ));
219 }
220 super::delta::number(indicator, execution)?;
221 Ok(true)
222}
223
224fn check_source_metadata<'r, 'a, W: Work>(
225 crl: &'r super::CertificateList<'a>,
226 is_delta: bool,
227 execution: &W,
228) -> Result<Option<&'r Extension<'a>>> {
229 const FIELD: &str = "certificateList.extensions";
230 for extension in crl.tbs().extensions().iter(execution) {
231 let extension = extension?;
232 execution
233 .charge_work(52)
236 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
237 let kind = extension.kind();
238 let malformed = || Error::new(ErrorCode::MalformedValue, FIELD, extension.span().start);
239 if kind == oid::ISSUING_DISTRIBUTION_POINT {
240 if !extension.is_critical() {
243 return Err(malformed());
244 }
245 } else if kind == oid::CRL_NUMBER {
246 if extension.is_critical() {
250 return Err(malformed());
251 }
252 super::delta::number(extension, execution)?;
253 } else if kind == oid::DELTA_CRL_INDICATOR {
254 } else if kind == oid::AUTHORITY_KEY_IDENTIFIER {
256 if extension.is_critical() {
260 return Err(malformed());
261 }
262 let identifier = crate::identifier::AuthorityKeyIdentifier::read(extension, execution)?;
263 if identifier.key_id().is_none() {
264 return Err(malformed());
265 }
266 } else if kind == oid::ISSUER_ALT_NAME {
267 general_name::read_alternative(extension, execution)?;
268 } else if kind == oid::AUTHORITY_INFO_ACCESS {
269 if extension.is_critical() {
273 return Err(malformed());
274 }
275 } else if kind == oid::FRESHEST_CRL {
276 if extension.is_critical() || is_delta {
280 return Err(malformed());
281 }
282 let points = crate::access::DistributionPoints::read(extension, execution)?;
283 for point in points.points() {
284 execution
285 .charge_work(1)
286 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
287 if point.reasons().is_some() || !point.issuer().is_empty() {
288 return Err(malformed());
289 }
290 }
291 } else if kind == oid::EXPIRED_CERTS_ON_CRL {
292 read_expired_certs(extension, execution)?;
293 } else if extension.is_critical() {
294 return Ok(Some(extension));
295 }
296 }
297 Ok(None)
298}
299
300fn validated_issuer<'a, W: Work>(
301 entry: &RevokedCertificate<'a>,
302 indirect: bool,
303 execution: &W,
304) -> Result<Option<OwnedVec<GeneralName<'a>>>> {
305 const FIELD: &str = "extensions.certificateIssuer";
306 let Some(extension) = entry.extensions.get(oid::CERTIFICATE_ISSUER, execution)? else {
307 return Ok(None);
308 };
309 if !indirect || !extension.is_critical() {
310 return Err(Error::new(
311 ErrorCode::MalformedValue,
312 FIELD,
313 extension.span().start,
314 ));
315 }
316 let mut reader = extension::sequence(extension, FIELD, execution)?;
317 let names = general_name::read_list(&mut reader, FIELD, extension.span().start)?;
318 let mut directory = false;
319 for name in &names {
320 execution
321 .charge_work(1)
322 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
323 if matches!(name, GeneralName::Directory(name) if !name.is_empty()) {
324 directory = true;
325 }
326 }
327 if !directory {
328 return Err(Error::new(
329 ErrorCode::MalformedValue,
330 FIELD,
331 extension.span().start,
332 ));
333 }
334 Ok(Some(names))
335}
336
337fn equal_bytes(left: &[u8], right: &[u8], execution: &impl Work, at: u64) -> Result<bool> {
338 let work = (left.len().max(right.len()) as u64)
339 .checked_mul(2)
340 .and_then(|n| n.checked_add(1))
341 .ok_or_else(|| {
342 Error::resource(
343 structured_asn1::memory::MemoryError::WorkExceeded,
344 "certificateList.entryLookup",
345 at,
346 )
347 })?;
348 execution
349 .charge_work(work)
350 .map_err(|reason| Error::resource(reason, "certificateList.entryLookup", at))?;
351 Ok(left == right)
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq)]
357pub enum Reason {
358 Unspecified,
360 KeyCompromise,
362 AuthorityCompromise,
364 AffiliationChanged,
366 Superseded,
368 CessationOfOperation,
370 CertificateHold,
372 RemoveFromCrl,
374 PrivilegeWithdrawn,
376 AttributeAuthorityCompromise,
378}
379
380impl<'a> RevokedCertificate<'a> {
381 pub fn reason<W: Work>(&self, execution: &W) -> Result<Option<Reason>> {
384 self.extensions
385 .get(oid::CRL_REASON, execution)?
386 .map(|extension| read_reason(extension, execution))
387 .transpose()
388 }
389
390 pub fn invalidity_date<W: Work>(&self, execution: &W) -> Result<Option<Time>> {
392 const FIELD: &str = "extensions.invalidityDate";
393 let Some(extension) = self.extensions.get(oid::INVALIDITY_DATE, execution)? else {
394 return Ok(None);
395 };
396 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
397 let element = reader
398 .expect(Tag::primitive(universal::GENERALIZED_TIME))
399 .field(FIELD)?;
400 super::end(&reader, FIELD, extension.span().start)?;
401 crate::time::read_in(&reader, &element, FIELD).map(Some)
402 }
403
404 pub fn certificate_issuer<W: Work>(
407 &self,
408 execution: &W,
409 ) -> Result<Option<OwnedVec<GeneralName<'a>>>> {
410 const FIELD: &str = "extensions.certificateIssuer";
411 let Some(extension) = self.extensions.get(oid::CERTIFICATE_ISSUER, execution)? else {
412 return Ok(None);
413 };
414 let mut reader = extension::sequence(extension, FIELD, execution)?;
415 general_name::read_list(&mut reader, FIELD, extension.span().start).map(Some)
416 }
417}
418
419impl super::TbsCertList<'_> {
420 pub fn expired_certs_on_crl<W: Work>(
426 &self,
427 execution: &W,
428 ) -> Result<Option<crate::temporal::Reading>> {
429 self.extensions
430 .get(oid::EXPIRED_CERTS_ON_CRL, execution)?
431 .map(|extension| read_expired_certs(extension, execution))
432 .transpose()
433 }
434}
435
436fn read_expired_certs<W: Work>(
439 extension: &Extension<'_>,
440 execution: &W,
441) -> Result<crate::temporal::Reading> {
442 const FIELD: &str = "extensions.expiredCertsOnCRL";
443 if extension.is_critical() {
444 return Err(Error::new(
445 ErrorCode::MalformedValue,
446 FIELD,
447 extension.span().start,
448 ));
449 }
450 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
451 let element = reader
452 .expect(Tag::primitive(universal::GENERALIZED_TIME))
453 .field(FIELD)?;
454 super::end(&reader, FIELD, extension.span().start)?;
455 crate::time::read_generalized_in(&reader, &element, FIELD)
456}
457
458fn read_reason<W: Work>(extension: &Extension<'_>, execution: &W) -> Result<Reason> {
459 const FIELD: &str = "extensions.reasonCode";
460 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
461 let element = reader.expect(Tag::primitive(10)).field(FIELD)?;
462 super::end(&reader, FIELD, extension.span().start)?;
463 reason_element(&reader, &element, FIELD)
464}
465
466pub(crate) fn reason_element(
468 parent: &structured_asn1::ScopedReader<'_, '_>,
469 element: &structured_asn1::Element<'_>,
470 field: &'static str,
471) -> Result<Reason> {
472 parent.charge(9).field(field)?;
473 let value = parent.integer(element).field(field)?;
474 match value.as_u64() {
478 Some(0) => Ok(Reason::Unspecified),
479 Some(1) => Ok(Reason::KeyCompromise),
480 Some(2) => Ok(Reason::AuthorityCompromise),
481 Some(3) => Ok(Reason::AffiliationChanged),
482 Some(4) => Ok(Reason::Superseded),
483 Some(5) => Ok(Reason::CessationOfOperation),
484 Some(6) => Ok(Reason::CertificateHold),
485 Some(8) => Ok(Reason::RemoveFromCrl),
486 Some(9) => Ok(Reason::PrivilegeWithdrawn),
487 Some(10) => Ok(Reason::AttributeAuthorityCompromise),
488 _ => Err(Error::new(
489 ErrorCode::MalformedValue,
490 field,
491 element.span().start,
492 )),
493 }
494}