1use super::{Authenticated, EntryLookup, Reason, 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}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum EntryOrigin {
59 Complete,
61 Delta,
63}
64
65#[derive(Debug)]
67pub enum CombinedEntry<'r, 'a> {
68 Absent,
70 Revoked {
72 origin: EntryOrigin,
74 entry: &'r RevokedCertificate<'a>,
76 reason: Reason,
78 },
79 Removed {
81 entry: &'r RevokedCertificate<'a>,
83 },
84 Conflict {
86 origin: EntryOrigin,
88 first: &'r RevokedCertificate<'a>,
90 second: &'r RevokedCertificate<'a>,
92 },
93 Unsupported {
95 origin: EntryOrigin,
97 kind: Oid<'a>,
99 span: Span,
101 },
102}
103
104impl<'b, 'r, 'a> BoundDelta<'b, 'r, 'a> {
105 pub fn entry<W: Work>(
111 self,
112 target: &crate::Certificate<'_>,
113 execution: &W,
114 ) -> Result<CombinedEntry<'r, 'a>> {
115 let base = match usable(self.base.entry(target, execution)?, EntryOrigin::Complete) {
116 Ok(entry) => entry,
117 Err(refusal) => return Ok(refusal),
118 };
119 let delta = match usable(self.delta.entry(target, execution)?, EntryOrigin::Delta) {
120 Ok(entry) => entry,
121 Err(refusal) => return Ok(refusal),
122 };
123 let (origin, entry) = match (base, delta) {
124 (_, Some(entry)) => (EntryOrigin::Delta, entry),
125 (Some(entry), None) => (EntryOrigin::Complete, entry),
126 (None, None) => return Ok(CombinedEntry::Absent),
127 };
128 let reason = entry.reason(execution)?.unwrap_or(Reason::Unspecified);
129 Ok(if reason == Reason::RemoveFromCrl {
133 CombinedEntry::Removed { entry }
134 } else {
135 CombinedEntry::Revoked {
136 origin,
137 entry,
138 reason,
139 }
140 })
141 }
142 #[must_use]
144 pub fn base(self) -> &'b Authenticated<'r, 'a> {
145 self.base
146 }
147 #[must_use]
149 pub fn delta(self) -> &'b Authenticated<'r, 'a> {
150 self.delta
151 }
152}
153
154fn usable<'r, 'a>(
155 lookup: EntryLookup<'r, 'a>,
156 origin: EntryOrigin,
157) -> core::result::Result<Option<&'r RevokedCertificate<'a>>, CombinedEntry<'r, 'a>> {
158 match lookup {
159 EntryLookup::Conflict { first, second } => Err(CombinedEntry::Conflict {
160 origin,
161 first,
162 second,
163 }),
164 EntryLookup::Unsupported { kind, span } => {
165 Err(CombinedEntry::Unsupported { origin, kind, span })
166 }
167 EntryLookup::Absent => Ok(None),
168 EntryLookup::Found(entry) => Ok(Some(entry)),
169 }
170}
171
172#[derive(Debug)]
174pub enum DeltaBinding<'b, 'r, 'a> {
175 Bound(BoundDelta<'b, 'r, 'a>),
177 Missing(MissingDeltaField),
179 Rejected(DeltaFailure),
181}
182
183impl<'r, 'a> Authenticated<'r, 'a> {
184 pub fn bind_delta<'b, W: Work>(
188 &'b self,
189 delta: &'b Self,
190 execution: &W,
191 ) -> Result<DeltaBinding<'b, 'r, 'a>> {
192 const FIELD: &str = "certificateList.deltaBinding";
193 let at = delta.crl().span().start;
194 let base_extensions = self.crl().tbs().extensions();
195 let delta_extensions = delta.crl().tbs().extensions();
196 if base_extensions
197 .get(oid::DELTA_CRL_INDICATOR, execution)?
198 .is_some()
199 {
200 return Ok(DeltaBinding::Rejected(DeltaFailure::BaseIsDelta));
201 }
202 let Some(indicator) = delta_extensions.get(oid::DELTA_CRL_INDICATOR, execution)? else {
203 return Ok(DeltaBinding::Missing(MissingDeltaField::Indicator));
204 };
205 if !indicator.is_critical() {
206 return Ok(DeltaBinding::Rejected(DeltaFailure::Criticality));
207 }
208 let minimum = number(indicator, execution)?;
209 let Some(base_number) = base_extensions.get(oid::CRL_NUMBER, execution)? else {
210 return Ok(DeltaBinding::Missing(MissingDeltaField::BaseNumber));
211 };
212 let Some(delta_number) = delta_extensions.get(oid::CRL_NUMBER, execution)? else {
213 return Ok(DeltaBinding::Missing(MissingDeltaField::DeltaNumber));
214 };
215 if base_number.is_critical() || delta_number.is_critical() {
216 return Ok(DeltaBinding::Rejected(DeltaFailure::Criticality));
217 }
218 let base_number = number(base_number, execution)?;
219 let delta_number = number(delta_number, execution)?;
220 match authority_pair(base_extensions, delta_extensions, execution, at)? {
221 AuthorityMatch::Matched => (),
222 AuthorityMatch::Missing(field) => return Ok(DeltaBinding::Missing(field)),
223 AuthorityMatch::Rejected(reason) => return Ok(DeltaBinding::Rejected(reason)),
224 }
225 if !self
229 .crl()
230 .tbs()
231 .issuer()
232 .matches(delta.crl().tbs().issuer(), execution)?
233 {
234 return Ok(DeltaBinding::Rejected(DeltaFailure::Issuer));
235 }
236 if !equal(
237 self.key().key().as_bytes(),
238 delta.key().key().as_bytes(),
239 execution,
240 FIELD,
241 at,
242 )? || !optional_equal(
243 self.key().parameters(),
244 delta.key().parameters(),
245 execution,
246 FIELD,
247 at,
248 )? {
249 return Ok(DeltaBinding::Rejected(DeltaFailure::SigningKey));
250 }
251 let base_scope = base_extensions.get(oid::ISSUING_DISTRIBUTION_POINT, execution)?;
252 let delta_scope = delta_extensions.get(oid::ISSUING_DISTRIBUTION_POINT, execution)?;
253 if base_scope.is_some_and(|extension| !extension.is_critical())
254 || delta_scope.is_some_and(|extension| !extension.is_critical())
255 {
256 return Ok(DeltaBinding::Rejected(DeltaFailure::Criticality));
257 }
258 if !optional_equal(
259 base_scope.map(|extension| extension.value()),
260 delta_scope.map(|extension| extension.value()),
261 execution,
262 FIELD,
263 at,
264 )? {
265 return Ok(DeltaBinding::Rejected(DeltaFailure::Scope));
266 }
267 if compare(base_number, minimum, execution, FIELD, at)? == Ordering::Less {
268 return Ok(DeltaBinding::Rejected(DeltaFailure::BaseTooOld));
269 }
270 if compare(base_number, delta_number, execution, FIELD, at)? != Ordering::Less {
271 return Ok(DeltaBinding::Rejected(DeltaFailure::NumberOrder));
272 }
273 Ok(DeltaBinding::Bound(BoundDelta { base: self, delta }))
274 }
275}
276
277enum AuthorityMatch {
278 Matched,
279 Missing(MissingDeltaField),
280 Rejected(DeltaFailure),
281}
282
283fn authority_pair<'a, W: Work>(
284 base: &Extensions<'a>,
285 delta: &Extensions<'a>,
286 execution: &W,
287 at: u64,
288) -> Result<AuthorityMatch> {
289 let Some(base) = base.get(oid::AUTHORITY_KEY_IDENTIFIER, execution)? else {
290 return Ok(AuthorityMatch::Missing(
291 MissingDeltaField::BaseAuthorityIdentifier,
292 ));
293 };
294 let Some(delta) = delta.get(oid::AUTHORITY_KEY_IDENTIFIER, execution)? else {
295 return Ok(AuthorityMatch::Missing(
296 MissingDeltaField::DeltaAuthorityIdentifier,
297 ));
298 };
299 if base.is_critical() || delta.is_critical() {
300 return Ok(AuthorityMatch::Rejected(DeltaFailure::Criticality));
301 }
302 for extension in [base, delta] {
303 let parsed = AuthorityKeyIdentifier::read(extension, execution)?;
304 if parsed.key_id().is_none() {
307 return Err(Error::new(
308 ErrorCode::MissingField,
309 "extensions.authorityKeyIdentifier.keyIdentifier",
310 extension.span().start,
311 ));
312 }
313 }
314 Ok(
322 if equal(
323 base.value(),
324 delta.value(),
325 execution,
326 "certificateList.deltaBinding",
327 at,
328 )? {
329 AuthorityMatch::Matched
330 } else {
331 AuthorityMatch::Rejected(DeltaFailure::AuthorityIdentifier)
332 },
333 )
334}
335
336pub(super) fn number<'a, W: Work>(extension: &Extension<'a>, execution: &W) -> Result<Integer<'a>> {
337 const FIELD: &str = "extensions.cRLNumber";
338 let mut reader = extension.reader_scoped(execution).field(FIELD)?;
339 let value = reader.read_integer().field(FIELD)?;
340 super::end(&reader, FIELD, extension.span().start)?;
341 if value.is_negative() {
342 return Err(Error::new(
343 ErrorCode::MalformedValue,
344 FIELD,
345 extension.span().start,
346 ));
347 }
348 Ok(value)
349}
350
351fn compare(
352 left: Integer<'_>,
353 right: Integer<'_>,
354 execution: &impl Work,
355 field: &'static str,
356 at: u64,
357) -> Result<Ordering> {
358 let left = magnitude(left.as_bytes());
359 let right = magnitude(right.as_bytes());
360 admit(left.len().max(right.len()), execution, field, at)?;
362 Ok(left.len().cmp(&right.len()).then_with(|| left.cmp(right)))
363}
364
365fn magnitude(bytes: &[u8]) -> &[u8] {
366 match bytes {
367 [0, rest @ ..] => rest,
368 all => all,
369 }
370}
371
372fn optional_equal(
373 left: Option<&[u8]>,
374 right: Option<&[u8]>,
375 execution: &impl Work,
376 field: &'static str,
377 at: u64,
378) -> Result<bool> {
379 execution
380 .charge_work(1)
381 .map_err(|reason| Error::resource(reason, field, at))?;
382 match (left, right) {
383 (Some(left), Some(right)) => equal(left, right, execution, field, at),
384 (None, None) => Ok(true),
385 _ => Ok(false),
386 }
387}
388
389fn equal(
390 left: &[u8],
391 right: &[u8],
392 execution: &impl Work,
393 field: &'static str,
394 at: u64,
395) -> Result<bool> {
396 admit(left.len().max(right.len()), execution, field, at)?;
397 Ok(left == right)
398}
399
400fn admit(width: usize, execution: &impl Work, field: &'static str, at: u64) -> Result<()> {
401 let work = (width as u64)
402 .checked_mul(2)
403 .and_then(|n| n.checked_add(1))
404 .ok_or_else(|| {
405 Error::resource(
406 structured_asn1::memory::MemoryError::WorkExceeded,
407 field,
408 at,
409 )
410 })?;
411 execution
412 .charge_work(work)
413 .map_err(|reason| Error::resource(reason, field, at))
414}