Skip to main content

structured_cms/
directory.rs

1//! Accounted certificate hint indexes, without signer identity or trust verdicts.
2
3use crate::Error as ProcessingError;
4use crate::signer::SignerIdentifier;
5use core::cmp::Ordering;
6use core::fmt;
7use structured_asn1::Reader;
8use structured_asn1::memory::{Execution, Memory, MemoryError, OwnedVec, SortError, Work};
9use structured_pkix::{Certificate, Name, Ordered};
10
11/// A directory operation has either an original document cause or a storage/
12/// metadata admission failure that has no document position of its own.
13#[derive(Debug)]
14pub enum Error {
15    /// Original certificate, name or signer-hint processing failure.
16    Processing(ProcessingError),
17    /// Source-free retained catalogue work or storage refusal.
18    Resource(MemoryError),
19}
20
21impl Error {
22    fn pkix(source: structured_pkix::Error) -> Self {
23        Self::Processing(ProcessingError::pkix(source))
24    }
25    fn resource(reason: MemoryError, field: &'static str, at: u64) -> Self {
26        Self::Processing(ProcessingError::resource(reason, field, at))
27    }
28    fn admission(reason: MemoryError, origin: Option<u64>) -> Self {
29        match origin {
30            Some(at) => Self::resource(reason, "signedData.certificates", at),
31            None => Self::Resource(reason),
32        }
33    }
34}
35
36impl fmt::Display for Error {
37    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            Self::Processing(source) => source.fmt(formatter),
40            Self::Resource(source) => source.fmt(formatter),
41        }
42    }
43}
44
45#[cfg(feature = "std")]
46impl std::error::Error for Error {
47    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
48        match self {
49            Self::Processing(source) => Some(source),
50            Self::Resource(source) => Some(source),
51        }
52    }
53}
54
55/// A complete catalogue operation; refusal never becomes a match answer.
56pub type Result<T> = core::result::Result<T, Error>;
57
58/// What the complete catalogue can establish about one certificate hint.
59/// Matching a hint never authenticates that certificate or its path.
60#[derive(Debug)]
61pub enum Match<'c, 'a> {
62    /// No certificate answers the complete hint, with no unreadable SKI gap.
63    None,
64    /// Exactly one certificate answers the complete hint.
65    One(&'c Certificate<'a>),
66    /// More than one certificate answers the complete hint; no candidate chosen.
67    Ambiguous,
68    /// An SKI value could not be read, so uniqueness/absence is unestablished.
69    /// Original diagnostics are retained by [`Directory::unreadable_identifiers`].
70    Unreadable,
71}
72
73/// One caller-selected certificate's hint agreement, without uniqueness/trust.
74#[derive(Debug)]
75pub enum CertificateAgreement {
76    /// The original issuer/serial or SKI hint agrees.
77    Matches,
78    /// The complete readable hint names something else.
79    Differs,
80    /// Original certificate SKI syntax prevents a comparison.
81    Unreadable(structured_pkix::Error),
82}
83
84/// Compares a selected certificate without constructing an issuer directory.
85///
86/// This uses the same paid byte/segmented and shared Name kernels as directory
87/// search. Original certificate parsing may be a different session; lazy SKI
88/// keeps that origin and also obeys the current query operation. This establishes
89/// neither uniqueness in a pool nor signature/path trust for the selected key.
90pub fn matches_certificate_in(
91    named: &SignerIdentifier<'_>,
92    certificate: &Certificate<'_>,
93    parent: &Reader<'_>,
94) -> Result<CertificateAgreement> {
95    let execution = parent.execution();
96    let at = certificate.span().start;
97    match named {
98        SignerIdentifier::IssuerAndSerial { issuer, serial } => {
99            if bytes_order(
100                serial.as_bytes(),
101                certificate.tbs().serial().as_bytes(),
102                &execution,
103                at,
104            )? != Ordering::Equal
105            {
106                return Ok(CertificateAgreement::Differs);
107            }
108            if issuer
109                .matches_in(certificate.tbs().issuer(), parent)
110                .map_err(Error::pkix)?
111            {
112                Ok(CertificateAgreement::Matches)
113            } else {
114                Ok(CertificateAgreement::Differs)
115            }
116        }
117        SignerIdentifier::SubjectKeyIdentifier(wanted) => {
118            let Some(extension) = certificate
119                .tbs()
120                .extensions()
121                .get(structured_pkix::oid::SUBJECT_KEY_IDENTIFIER, &execution)
122                .map_err(Error::pkix)?
123            else {
124                return Ok(CertificateAgreement::Differs);
125            };
126            let identifier =
127                match structured_pkix::identifier::SubjectKeyIdentifier::read_in(extension, parent)
128                {
129                    Ok(identifier) => identifier,
130                    Err(error)
131                        if error.is_resource()
132                            || error.code() == structured_pkix::ErrorCode::ForeignReader =>
133                    {
134                        return Err(Error::pkix(error));
135                    }
136                    Err(error) => return Ok(CertificateAgreement::Unreadable(error)),
137                };
138            if segmented_matches(wanted, identifier.as_bytes(), &execution, at)? {
139                Ok(CertificateAgreement::Matches)
140            } else {
141                Ok(CertificateAgreement::Differs)
142            }
143        }
144    }
145}
146
147/// Original malformed identifier diagnostic, independently of resource failures.
148#[derive(Debug)]
149pub struct Unreadable<'c, 'a> {
150    certificate: &'c Certificate<'a>,
151    reason: structured_pkix::Error,
152}
153impl<'c, 'a> Unreadable<'c, 'a> {
154    /// Certificate whose actual extension was unreadable.
155    #[must_use]
156    pub fn certificate(&self) -> &'c Certificate<'a> {
157        self.certificate
158    }
159    /// Original located encoding/schema cause, without conversion to absence.
160    #[must_use]
161    pub fn reason(&self) -> &structured_pkix::Error {
162        &self.reason
163    }
164}
165
166struct Identifier<'a> {
167    bytes: &'a [u8],
168    index: usize,
169}
170
171/// Prepared issuer/serial and SKI indexes retaining their own capacity charges.
172///
173/// Names are prepared once per certificate, not inside sorting comparators.
174/// Owner type determines storage/execution lifetime, never semantic ordering.
175/// Every new lookup explicitly funds its current domain. Unknown/malformed SKI
176/// gaps cannot become a completed None/One result; resource refusal aborts.
177pub struct Directory<'c, 'a, W: Work = Memory> {
178    certificates: OwnedVec<&'c Certificate<'a>, W>,
179    issuers: OwnedVec<Ordered<'a, W>, W>,
180    serials: OwnedVec<usize, W>,
181    identifiers: OwnedVec<Identifier<'a>, W>,
182    unreadable: OwnedVec<Unreadable<'c, 'a>, W>,
183}
184
185impl<'c, 'a> Directory<'c, 'a> {
186    /// Independently retained directory over certificates from any source session.
187    /// Current Memory funds all construction; deferred SKI parsing preserves
188    /// each original certificate's reader/depth and its cumulative parse ceiling.
189    pub fn of(certificates: &[&'c Certificate<'a>], execution: &Memory) -> Result<Self> {
190        let mut kept = execution.vector();
191        kept.try_extend_from_slice(certificates)
192            .map_err(Error::Resource)?;
193        build(
194            kept,
195            execution,
196            None,
197            |name| name.ordered(execution),
198            |extension| {
199                structured_pkix::identifier::SubjectKeyIdentifier::read(extension, execution)
200            },
201        )
202    }
203}
204
205impl<'c, 'a, 'p> Directory<'c, 'a, Execution<'p>> {
206    /// Scoped directory over carried certificates from this one parser session.
207    ///
208    /// The source identity of every certificate is checked before allocation,
209    /// normalization or lazy SKI parsing. A foreign pool belongs in [`Directory::of`];
210    /// account equality cannot make it part of the producing document session.
211    pub fn of_in(certificates: &[&'c Certificate<'a>], parent: &'p Reader<'_>) -> Result<Self>
212    where
213        'a: 'p,
214    {
215        let execution = parent.execution();
216        // Preflight the borrowed catalogue before any retained storage or
217        // deferred processing. One handle copy then feeds the shared builder.
218        for certificate in certificates {
219            execution.charge_work(1).map_err(|reason| {
220                Error::resource(reason, "signedData.certificates", parent.offset())
221            })?;
222            if !certificate
223                .tbs()
224                .public_key()
225                .algorithm()
226                .same_session(parent)
227            {
228                return Err(Error::pkix(structured_pkix::Error::new(
229                    structured_pkix::ErrorCode::ForeignReader,
230                    "signedData.certificates",
231                    certificate.span().start,
232                )));
233            }
234        }
235        let mut gathered = execution.vector();
236        gathered
237            .try_extend_from_slice(certificates)
238            .map_err(|reason| Error::admission(reason, Some(parent.offset())))?;
239        build(
240            gathered,
241            &execution,
242            Some(parent.offset()),
243            |name| name.ordered_in(parent),
244            |extension| {
245                structured_pkix::identifier::SubjectKeyIdentifier::read_in(extension, parent)
246            },
247        )
248    }
249}
250
251impl<'c, 'a, W: Work> Directory<'c, 'a, W> {
252    /// Indexed certificate count, without rescanning.
253    #[must_use]
254    pub fn len(&self) -> usize {
255        self.certificates.len()
256    }
257    /// Whether this directory carries no certificate hints.
258    #[must_use]
259    pub fn is_empty(&self) -> bool {
260        self.certificates.is_empty()
261    }
262    /// Original malformed SKI diagnostics, with their actual certificates.
263    #[must_use]
264    pub fn unreadable_identifiers(&self) -> &[Unreadable<'c, 'a>] {
265        &self.unreadable
266    }
267
268    /// Retained query using current aggregate funding, independent of storage owner.
269    pub fn find(&self, named: &SignerIdentifier<'_>, execution: &Memory) -> Result<Match<'c, 'a>> {
270        self.find_with(named, execution, None, |name| name.ordered(execution))
271    }
272    /// Producing query using both current local and aggregate ceilings.
273    /// A retained pool and scoped pool use the same index/search kernel.
274    pub fn find_in(
275        &self,
276        named: &SignerIdentifier<'_>,
277        parent: &Reader<'_>,
278    ) -> Result<Match<'c, 'a>> {
279        self.find_with(named, &parent.execution(), Some(parent.offset()), |name| {
280            name.ordered_in(parent)
281        })
282    }
283
284    fn find_with<'n, V: Work, E: Work>(
285        &self,
286        named: &SignerIdentifier<'n>,
287        execution: &E,
288        origin: Option<u64>,
289        prepare: impl FnOnce(&Name<'n>) -> structured_pkix::Result<Ordered<'n, V>>,
290    ) -> Result<Match<'c, 'a>> {
291        match named {
292            SignerIdentifier::IssuerAndSerial { issuer, serial } => {
293                let asked = prepare(issuer).map_err(Error::pkix)?;
294                let against = |index: usize| -> Result<Ordering> {
295                    let held = self.certificates[index].tbs().serial().as_bytes();
296                    let order = bytes_order(
297                        held,
298                        serial.as_bytes(),
299                        execution,
300                        self.certificates[index].span().start,
301                    )?;
302                    if order != Ordering::Equal {
303                        return Ok(order);
304                    }
305                    self.issuers[index]
306                        .order(&asked, execution)
307                        .map_err(Error::pkix)
308                };
309                let from = lower_bound(self.serials.len(), execution, origin, |at| {
310                    against(self.serials[at])
311                })?;
312                let mut first = None;
313                for &index in &self.serials[from..] {
314                    if against(index)? != Ordering::Equal {
315                        break;
316                    }
317                    if first.is_some() {
318                        return Ok(Match::Ambiguous);
319                    }
320                    first = Some(self.certificates[index]);
321                }
322                Ok(match first {
323                    Some(certificate) => Match::One(certificate),
324                    None => Match::None,
325                })
326            }
327            SignerIdentifier::SubjectKeyIdentifier(wanted) => {
328                let mut first = None;
329                if let Some(bytes) = wanted.as_one() {
330                    let from = lower_bound(self.identifiers.len(), execution, origin, |at| {
331                        bytes_order(
332                            self.identifiers[at].bytes,
333                            bytes,
334                            execution,
335                            self.certificates[self.identifiers[at].index].span().start,
336                        )
337                    })?;
338                    for held in &self.identifiers[from..] {
339                        if bytes_order(
340                            held.bytes,
341                            bytes,
342                            execution,
343                            self.certificates[held.index].span().start,
344                        )? != Ordering::Equal
345                        {
346                            break;
347                        }
348                        if first.is_some() {
349                            return Ok(Match::Ambiguous);
350                        }
351                        first = Some(self.certificates[held.index]);
352                    }
353                } else {
354                    // Compare the original segmented identifier without joining.
355                    for held in &self.identifiers {
356                        let at = self.certificates[held.index].span().start;
357                        execution
358                            .charge_work(1)
359                            .map_err(|reason| Error::resource(reason, "signerInfo.sid", at))?;
360                        if segmented_matches(wanted, held.bytes, execution, at)? {
361                            if first.is_some() {
362                                return Ok(Match::Ambiguous);
363                            }
364                            first = Some(self.certificates[held.index]);
365                        }
366                    }
367                }
368                if !self.unreadable.is_empty() {
369                    return Ok(Match::Unreadable);
370                }
371                Ok(match first {
372                    Some(certificate) => Match::One(certificate),
373                    None => Match::None,
374                })
375            }
376        }
377    }
378}
379
380fn build<'c, 'a, W: Work>(
381    kept: OwnedVec<&'c Certificate<'a>, W>,
382    execution: &W,
383    origin: Option<u64>,
384    mut prepare: impl FnMut(&Name<'a>) -> structured_pkix::Result<Ordered<'a, W>>,
385    mut read_identifier: impl FnMut(
386        &structured_pkix::Extension<'a>,
387    ) -> structured_pkix::Result<
388        structured_pkix::identifier::SubjectKeyIdentifier<'a>,
389    >,
390) -> Result<Directory<'c, 'a, W>> {
391    let mut issuers = execution.vector();
392    let mut serials = execution.vector();
393    let mut identifiers = execution.vector();
394    let mut unreadable = execution.vector();
395    for (index, certificate) in kept.iter().enumerate() {
396        let at = certificate.span().start;
397        execution
398            .charge_work(1)
399            .map_err(|reason| Error::resource(reason, "signedData.certificates", at))?;
400        issuers
401            .try_push(prepare(certificate.tbs().issuer()).map_err(Error::pkix)?)
402            .map_err(|reason| Error::resource(reason, "signedData.certificates", at))?;
403        serials
404            .try_push(index)
405            .map_err(|reason| Error::resource(reason, "signedData.certificates", at))?;
406        let extension = certificate
407            .tbs()
408            .extensions()
409            .get(structured_pkix::oid::SUBJECT_KEY_IDENTIFIER, execution)
410            .map_err(Error::pkix)?;
411        if let Some(extension) = extension {
412            match read_identifier(extension) {
413                Ok(identifier) => identifiers
414                    .try_push(Identifier {
415                        bytes: identifier.as_bytes(),
416                        index,
417                    })
418                    .map_err(|reason| {
419                        Error::resource(
420                            reason,
421                            "extensions.subjectKeyIdentifier",
422                            extension.span().start,
423                        )
424                    })?,
425                Err(reason) if reason.is_resource() => return Err(Error::pkix(reason)),
426                Err(reason) => unreadable
427                    .try_push(Unreadable {
428                        certificate,
429                        reason,
430                    })
431                    .map_err(|reason| {
432                        Error::resource(
433                            reason,
434                            "extensions.subjectKeyIdentifier",
435                            extension.span().start,
436                        )
437                    })?,
438            }
439        }
440    }
441    serials
442        .try_sort_unstable_by(|left, right| {
443            let order = bytes_order(
444                kept[*left].tbs().serial().as_bytes(),
445                kept[*right].tbs().serial().as_bytes(),
446                execution,
447                kept[*left].span().start,
448            )?;
449            if order != Ordering::Equal {
450                return Ok(order);
451            }
452            issuers[*left]
453                .order(&issuers[*right], execution)
454                .map_err(Error::pkix)
455        })
456        .map_err(|error| sort_error(error, origin))?;
457    identifiers
458        .try_sort_unstable_by(|left, right| {
459            bytes_order(
460                left.bytes,
461                right.bytes,
462                execution,
463                kept[left.index].span().start,
464            )
465        })
466        .map_err(|error| sort_error(error, origin))?;
467    Ok(Directory {
468        certificates: kept,
469        issuers,
470        serials,
471        identifiers,
472        unreadable,
473    })
474}
475
476fn sort_error(error: SortError<Error>, origin: Option<u64>) -> Error {
477    match error {
478        SortError::Resource(reason) => Error::admission(reason, origin),
479        SortError::Comparison(error) => error,
480    }
481}
482fn bytes_order<W: Work>(left: &[u8], right: &[u8], execution: &W, at: u64) -> Result<Ordering> {
483    execution
484        .charge_work(1 + left.len().max(right.len()) as u64)
485        .map_err(|reason| Error::resource(reason, "signerInfo.sid", at))?;
486    Ok(left.cmp(right))
487}
488fn lower_bound<W: Work>(
489    length: usize,
490    execution: &W,
491    origin: Option<u64>,
492    mut compare: impl FnMut(usize) -> Result<Ordering>,
493) -> Result<usize> {
494    let mut from = 0;
495    let mut to = length;
496    while from < to {
497        execution
498            .charge_work(1)
499            .map_err(|reason| Error::admission(reason, origin))?;
500        let middle = from + (to - from) / 2;
501        if compare(middle)? == Ordering::Less {
502            from = middle + 1;
503        } else {
504            to = middle;
505        }
506    }
507    Ok(from)
508}
509fn segmented_matches<W: Work>(
510    wanted: &crate::Octets<'_>,
511    held: &[u8],
512    execution: &W,
513    at: u64,
514) -> Result<bool> {
515    execution
516        .charge_work(1)
517        .map_err(|reason| Error::resource(reason, "signerInfo.sid", at))?;
518    if wanted.len() != held.len() {
519        return Ok(false);
520    }
521    let mut offset = 0;
522    for piece in wanted.pieces() {
523        let bytes = piece.as_bytes();
524        execution
525            .charge_work(1 + bytes.len() as u64)
526            .map_err(|reason| Error::resource(reason, "signerInfo.sid", at))?;
527        if bytes != &held[offset..offset + bytes.len()] {
528            return Ok(false);
529        }
530        offset += bytes.len();
531    }
532    debug_assert_eq!(offset, held.len());
533    Ok(true)
534}