1use super::{Authenticated, EntryLookup, Reason, RevocationTarget, RevokedCertificate};
4use crate::error::{Error, ErrorCode, Located, Result};
5use crate::extension::{Extension, Extensions};
6use crate::identifier::AuthorityKeyIdentifier;
7use crate::oid;
8use core::cmp::Ordering;
9use structured_asn1::memory::Work;
10use structured_asn1::{Integer, Oid, Span};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum MissingDeltaField {
15 BaseNumber,
17 DeltaNumber,
19 Indicator,
21 BaseAuthorityIdentifier,
23 DeltaAuthorityIdentifier,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum DeltaFailure {
30 BaseIsDelta,
32 Issuer,
34 SigningKey,
36 Scope,
38 BaseTooOld,
40 NumberOrder,
42 Criticality,
44 AuthorityIdentifier,
46}
47
48#[derive(Debug, Clone, Copy)]
51pub struct BoundDelta<'b, 'r, 'a> {
52 base: &'b Authenticated<'r, 'a>,
53 delta: &'b Authenticated<'r, 'a>,
54 delta_number: Integer<'a>,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum EntryOrigin {
60 Complete,
62 Delta,
64}
65
66#[derive(Debug)]
68pub enum CombinedEntry<'r, 'a> {
69 Absent,
71 Revoked {
73 origin: EntryOrigin,
75 entry: &'r RevokedCertificate<'a>,
77 reason: Reason,
79 },
80 Removed {
82 entry: &'r RevokedCertificate<'a>,
84 },
85 Conflict {
87 origin: EntryOrigin,
89 first: &'r RevokedCertificate<'a>,
91 second: &'r RevokedCertificate<'a>,
93 },
94 Unsupported {
96 origin: EntryOrigin,
98 kind: Oid<'a>,
100 span: Span,
102 },
103}
104
105impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
106 pub fn compare_delta_number<W: Work>(
115 self,
116 other: BoundDelta<'_, '_, '_>,
117 execution: &W,
118 ) -> Result<Ordering> {
119 const FIELD: &str = "certificateList.deltaNumberOrder";
120 let at = self.delta.crl().span().start;
121 let left = self.delta_number.as_bytes();
122 let right = other.delta_number.as_bytes();
123 let work = comparison_work(left.len().max(right.len()), FIELD, at)?;
126 self.delta
127 .crl()
128 .signature_algorithm()
129 .execution_origin()
130 .charge_work_with(
131 other.delta.crl().signature_algorithm().execution_origin(),
132 execution,
133 work,
134 )
135 .map_err(|reason| Error::resource(reason, FIELD, at))?;
136 Ok(compare_magnitudes(magnitude(left), magnitude(right)))
137 }
138
139 pub fn entry<'t, T: RevocationTarget<'t>, W: Work>(
145 self,
146 target: &T,
147 execution: &W,
148 ) -> Result<CombinedEntry<'r, 'a>> {
149 let base = match usable(self.base.entry(target, execution)?, EntryOrigin::Complete) {
150 Ok(entry) => entry,
151 Err(refusal) => return Ok(refusal),
152 };
153 let delta = match usable(self.delta.entry(target, execution)?, EntryOrigin::Delta) {
154 Ok(entry) => entry,
155 Err(refusal) => return Ok(refusal),
156 };
157 let (origin, entry) = match (base, delta) {
158 (_, Some(entry)) => (EntryOrigin::Delta, entry),
159 (Some(entry), None) => (EntryOrigin::Complete, entry),
160 (None, None) => return Ok(CombinedEntry::Absent),
161 };
162 let reason = entry.reason(execution)?.unwrap_or(Reason::Unspecified);
163 Ok(if reason == Reason::RemoveFromCrl {
167 CombinedEntry::Removed { entry }
168 } else {
169 CombinedEntry::Revoked {
170 origin,
171 entry,
172 reason,
173 }
174 })
175 }
176 #[must_use]
178 pub fn base(self) -> &'b Authenticated<'r, 'a> {
179 self.base
180 }
181 #[must_use]
183 pub fn delta(self) -> &'b Authenticated<'r, 'a> {
184 self.delta
185 }
186}
187
188fn usable<'r, 'a>(
189 lookup: EntryLookup<'r, 'a>,
190 origin: EntryOrigin,
191) -> core::result::Result<Option<&'r RevokedCertificate<'a>>, CombinedEntry<'r, 'a>> {
192 match lookup {
193 EntryLookup::Conflict { first, second } => Err(CombinedEntry::Conflict {
194 origin,
195 first,
196 second,
197 }),
198 EntryLookup::Unsupported { kind, span } => {
199 Err(CombinedEntry::Unsupported { origin, kind, span })
200 }
201 EntryLookup::Absent => Ok(None),
202 EntryLookup::Found(entry) => Ok(Some(entry)),
203 }
204}
205
206#[derive(Debug)]
208pub enum DeltaBinding<'b, 'r, 'a> {
209 Bound(BoundDelta<'b, 'r, 'a>),
211 Missing(MissingDeltaField),
213 Rejected(DeltaFailure),
215}
216
217impl<'r, 'a> Authenticated<'r, 'a> {
218 pub fn bind_delta<'b, W: Work>(
222 &'b self,
223 delta: &'b Self,
224 execution: &W,
225 ) -> Result<DeltaBinding<'b, 'r, 'a>> {
226 const FIELD: &str = "certificateList.deltaBinding";
227 let at = delta.crl().span().start;
228 let base_extensions = self.crl().tbs().extensions();
229 let delta_extensions = delta.crl().tbs().extensions();
230 if base_extensions
231 .get(oid::DELTA_CRL_INDICATOR, execution)?
232 .is_some()
233 {
234 return Ok(DeltaBinding::Rejected(DeltaFailure::BaseIsDelta));
235 }
236 let Some(indicator) = delta_extensions.get(oid::DELTA_CRL_INDICATOR, execution)? else {
237 return Ok(DeltaBinding::Missing(MissingDeltaField::Indicator));
238 };
239 if !indicator.is_critical() {
240 return Ok(DeltaBinding::Rejected(DeltaFailure::Criticality));
241 }
242 let minimum = number(indicator, execution)?;
243 let Some(base_number) = base_extensions.get(oid::CRL_NUMBER, execution)? else {
244 return Ok(DeltaBinding::Missing(MissingDeltaField::BaseNumber));
245 };
246 let Some(delta_number) = delta_extensions.get(oid::CRL_NUMBER, execution)? else {
247 return Ok(DeltaBinding::Missing(MissingDeltaField::DeltaNumber));
248 };
249 if base_number.is_critical() || delta_number.is_critical() {
250 return Ok(DeltaBinding::Rejected(DeltaFailure::Criticality));
251 }
252 let base_number = number(base_number, execution)?;
253 let delta_number = number(delta_number, execution)?;
254 match authority_pair(base_extensions, delta_extensions, execution, at)? {
255 AuthorityMatch::Matched => (),
256 AuthorityMatch::Missing(field) => return Ok(DeltaBinding::Missing(field)),
257 AuthorityMatch::Rejected(reason) => return Ok(DeltaBinding::Rejected(reason)),
258 }
259 if !self
263 .crl()
264 .tbs()
265 .issuer()
266 .matches(delta.crl().tbs().issuer(), execution)?
267 {
268 return Ok(DeltaBinding::Rejected(DeltaFailure::Issuer));
269 }
270 if !equal(
271 self.key().key().as_bytes(),
272 delta.key().key().as_bytes(),
273 execution,
274 FIELD,
275 at,
276 )? || !optional_equal(
277 self.key().parameters(),
278 delta.key().parameters(),
279 execution,
280 FIELD,
281 at,
282 )? {
283 return Ok(DeltaBinding::Rejected(DeltaFailure::SigningKey));
284 }
285 let base_scope = base_extensions.get(oid::ISSUING_DISTRIBUTION_POINT, execution)?;
286 let delta_scope = delta_extensions.get(oid::ISSUING_DISTRIBUTION_POINT, execution)?;
287 if base_scope.is_some_and(|extension| !extension.is_critical())
288 || delta_scope.is_some_and(|extension| !extension.is_critical())
289 {
290 return Ok(DeltaBinding::Rejected(DeltaFailure::Criticality));
291 }
292 if !optional_equal(
293 base_scope.map(|extension| extension.value()),
294 delta_scope.map(|extension| extension.value()),
295 execution,
296 FIELD,
297 at,
298 )? {
299 return Ok(DeltaBinding::Rejected(DeltaFailure::Scope));
300 }
301 if compare(base_number, minimum, execution, FIELD, at)? == Ordering::Less {
302 return Ok(DeltaBinding::Rejected(DeltaFailure::BaseTooOld));
303 }
304 if compare(base_number, delta_number, execution, FIELD, at)? != Ordering::Less {
305 return Ok(DeltaBinding::Rejected(DeltaFailure::NumberOrder));
306 }
307 Ok(DeltaBinding::Bound(BoundDelta {
308 base: self,
309 delta,
310 delta_number,
311 }))
312 }
313}
314
315enum AuthorityMatch {
316 Matched,
317 Missing(MissingDeltaField),
318 Rejected(DeltaFailure),
319}
320
321fn authority_pair<'a, W: Work>(
322 base: &Extensions<'a>,
323 delta: &Extensions<'a>,
324 execution: &W,
325 at: u64,
326) -> Result<AuthorityMatch> {
327 let Some(base) = base.get(oid::AUTHORITY_KEY_IDENTIFIER, execution)? else {
328 return Ok(AuthorityMatch::Missing(
329 MissingDeltaField::BaseAuthorityIdentifier,
330 ));
331 };
332 let Some(delta) = delta.get(oid::AUTHORITY_KEY_IDENTIFIER, execution)? else {
333 return Ok(AuthorityMatch::Missing(
334 MissingDeltaField::DeltaAuthorityIdentifier,
335 ));
336 };
337 if base.is_critical() || delta.is_critical() {
338 return Ok(AuthorityMatch::Rejected(DeltaFailure::Criticality));
339 }
340 for extension in [base, delta] {
341 let parsed = AuthorityKeyIdentifier::read(extension, execution)?;
342 if parsed.key_id().is_none() {
345 return Err(Error::new(
346 ErrorCode::MissingField,
347 "extensions.authorityKeyIdentifier.keyIdentifier",
348 extension.span().start,
349 ));
350 }
351 }
352 Ok(
360 if equal(
361 base.value(),
362 delta.value(),
363 execution,
364 "certificateList.deltaBinding",
365 at,
366 )? {
367 AuthorityMatch::Matched
368 } else {
369 AuthorityMatch::Rejected(DeltaFailure::AuthorityIdentifier)
370 },
371 )
372}
373
374pub(super) fn number<'a, W: Work>(extension: &Extension<'a>, execution: &W) -> Result<Integer<'a>> {
375 const FIELD: &str = "extensions.cRLNumber";
376 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
377 let value = reader.read_integer().field(FIELD)?;
378 super::end(&reader, FIELD, extension.span().start)?;
379 if value.is_negative() {
380 return Err(Error::new(
381 ErrorCode::MalformedValue,
382 FIELD,
383 extension.span().start,
384 ));
385 }
386 Ok(value)
387}
388
389fn compare(
390 left: Integer<'_>,
391 right: Integer<'_>,
392 execution: &impl Work,
393 field: &'static str,
394 at: u64,
395) -> Result<Ordering> {
396 let left = magnitude(left.as_bytes());
397 let right = magnitude(right.as_bytes());
398 admit(left.len().max(right.len()), execution, field, at)?;
400 Ok(compare_magnitudes(left, right))
401}
402
403fn compare_magnitudes(left: &[u8], right: &[u8]) -> Ordering {
404 left.len().cmp(&right.len()).then_with(|| left.cmp(right))
405}
406
407fn magnitude(bytes: &[u8]) -> &[u8] {
408 match bytes {
409 [0, rest @ ..] => rest,
410 all => all,
411 }
412}
413
414fn optional_equal(
415 left: Option<&[u8]>,
416 right: Option<&[u8]>,
417 execution: &impl Work,
418 field: &'static str,
419 at: u64,
420) -> Result<bool> {
421 execution
422 .charge_work(1)
423 .map_err(|reason| Error::resource(reason, field, at))?;
424 match (left, right) {
425 (Some(left), Some(right)) => equal(left, right, execution, field, at),
426 (None, None) => Ok(true),
427 _ => Ok(false),
428 }
429}
430
431fn equal(
432 left: &[u8],
433 right: &[u8],
434 execution: &impl Work,
435 field: &'static str,
436 at: u64,
437) -> Result<bool> {
438 admit(left.len().max(right.len()), execution, field, at)?;
439 Ok(left == right)
440}
441
442fn admit(width: usize, execution: &impl Work, field: &'static str, at: u64) -> Result<()> {
443 let work = comparison_work(width, field, at)?;
444 execution
445 .charge_work(work)
446 .map_err(|reason| Error::resource(reason, field, at))
447}
448
449fn comparison_work(width: usize, field: &'static str, at: u64) -> Result<u64> {
450 (width as u64)
451 .checked_mul(2)
452 .and_then(|n| n.checked_add(1))
453 .ok_or_else(|| {
454 Error::resource(
455 structured_asn1::memory::MemoryError::WorkExceeded,
456 field,
457 at,
458 )
459 })
460}