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 kind == oid::EXPIRED_CERTS_ON_CRL {
260 read_expired_certs(extension, execution)?;
261 } else if extension.is_critical() {
262 return Ok(Some(extension));
263 }
264 }
265 Ok(None)
266}
267
268fn validated_issuer<'a, W: Work>(
269 entry: &RevokedCertificate<'a>,
270 indirect: bool,
271 execution: &W,
272) -> Result<Option<OwnedVec<GeneralName<'a>>>> {
273 const FIELD: &str = "extensions.certificateIssuer";
274 let Some(extension) = entry.extensions.get(oid::CERTIFICATE_ISSUER, execution)? else {
275 return Ok(None);
276 };
277 if !indirect || !extension.is_critical() {
278 return Err(Error::new(
279 ErrorCode::MalformedValue,
280 FIELD,
281 extension.span().start,
282 ));
283 }
284 let mut reader = extension::sequence(extension, FIELD, execution)?;
285 let names = general_name::read_list(&mut reader, FIELD, extension.span().start)?;
286 let mut directory = false;
287 for name in &names {
288 execution
289 .charge_work(1)
290 .map_err(|reason| Error::resource(reason, FIELD, extension.span().start))?;
291 if matches!(name, GeneralName::Directory(name) if !name.is_empty()) {
292 directory = true;
293 }
294 }
295 if !directory {
296 return Err(Error::new(
297 ErrorCode::MalformedValue,
298 FIELD,
299 extension.span().start,
300 ));
301 }
302 Ok(Some(names))
303}
304
305fn equal_bytes(left: &[u8], right: &[u8], execution: &impl Work, at: u64) -> Result<bool> {
306 let work = (left.len().max(right.len()) as u64)
307 .checked_mul(2)
308 .and_then(|n| n.checked_add(1))
309 .ok_or_else(|| {
310 Error::resource(
311 structured_asn1::memory::MemoryError::WorkExceeded,
312 "certificateList.entryLookup",
313 at,
314 )
315 })?;
316 execution
317 .charge_work(work)
318 .map_err(|reason| Error::resource(reason, "certificateList.entryLookup", at))?;
319 Ok(left == right)
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub enum Reason {
326 Unspecified,
328 KeyCompromise,
330 AuthorityCompromise,
332 AffiliationChanged,
334 Superseded,
336 CessationOfOperation,
338 CertificateHold,
340 RemoveFromCrl,
342 PrivilegeWithdrawn,
344 AttributeAuthorityCompromise,
346}
347
348impl<'a> RevokedCertificate<'a> {
349 pub fn reason<W: Work>(&self, execution: &W) -> Result<Option<Reason>> {
352 self.extensions
353 .get(oid::CRL_REASON, execution)?
354 .map(|extension| read_reason(extension, execution))
355 .transpose()
356 }
357
358 pub fn invalidity_date<W: Work>(&self, execution: &W) -> Result<Option<Time>> {
360 const FIELD: &str = "extensions.invalidityDate";
361 let Some(extension) = self.extensions.get(oid::INVALIDITY_DATE, execution)? else {
362 return Ok(None);
363 };
364 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
365 let element = reader
366 .expect(Tag::primitive(universal::GENERALIZED_TIME))
367 .field(FIELD)?;
368 super::end(&reader, FIELD, extension.span().start)?;
369 crate::time::read_in(&reader, &element, FIELD).map(Some)
370 }
371
372 pub fn certificate_issuer<W: Work>(
375 &self,
376 execution: &W,
377 ) -> Result<Option<OwnedVec<GeneralName<'a>>>> {
378 const FIELD: &str = "extensions.certificateIssuer";
379 let Some(extension) = self.extensions.get(oid::CERTIFICATE_ISSUER, execution)? else {
380 return Ok(None);
381 };
382 let mut reader = extension::sequence(extension, FIELD, execution)?;
383 general_name::read_list(&mut reader, FIELD, extension.span().start).map(Some)
384 }
385}
386
387impl super::TbsCertList<'_> {
388 pub fn expired_certs_on_crl<W: Work>(
394 &self,
395 execution: &W,
396 ) -> Result<Option<crate::temporal::Reading>> {
397 self.extensions
398 .get(oid::EXPIRED_CERTS_ON_CRL, execution)?
399 .map(|extension| read_expired_certs(extension, execution))
400 .transpose()
401 }
402}
403
404fn read_expired_certs<W: Work>(
407 extension: &Extension<'_>,
408 execution: &W,
409) -> Result<crate::temporal::Reading> {
410 const FIELD: &str = "extensions.expiredCertsOnCRL";
411 if extension.is_critical() {
412 return Err(Error::new(
413 ErrorCode::MalformedValue,
414 FIELD,
415 extension.span().start,
416 ));
417 }
418 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
419 let element = reader
420 .expect(Tag::primitive(universal::GENERALIZED_TIME))
421 .field(FIELD)?;
422 super::end(&reader, FIELD, extension.span().start)?;
423 crate::time::read_generalized_in(&reader, &element, FIELD)
424}
425
426fn read_reason<W: Work>(extension: &Extension<'_>, execution: &W) -> Result<Reason> {
427 const FIELD: &str = "extensions.reasonCode";
428 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
429 let element = reader.expect(Tag::primitive(10)).field(FIELD)?;
430 super::end(&reader, FIELD, extension.span().start)?;
431 reason_element(&reader, &element, FIELD)
432}
433
434pub(crate) fn reason_element(
436 parent: &structured_asn1::ScopedReader<'_, '_>,
437 element: &structured_asn1::Element<'_>,
438 field: &'static str,
439) -> Result<Reason> {
440 parent.charge(9).field(field)?;
441 let value = parent.integer(element).field(field)?;
442 match value.as_u64() {
446 Some(0) => Ok(Reason::Unspecified),
447 Some(1) => Ok(Reason::KeyCompromise),
448 Some(2) => Ok(Reason::AuthorityCompromise),
449 Some(3) => Ok(Reason::AffiliationChanged),
450 Some(4) => Ok(Reason::Superseded),
451 Some(5) => Ok(Reason::CessationOfOperation),
452 Some(6) => Ok(Reason::CertificateHold),
453 Some(8) => Ok(Reason::RemoveFromCrl),
454 Some(9) => Ok(Reason::PrivilegeWithdrawn),
455 Some(10) => Ok(Reason::AttributeAuthorityCompromise),
456 _ => Err(Error::new(
457 ErrorCode::MalformedValue,
458 field,
459 element.span().start,
460 )),
461 }
462}