Skip to main content

structured_xades/baseline/
basic.rs

1//! Original B-B field/header/selected-key facts without repeated cryptography.
2
3use crate::attributes::{self, AttributeXml};
4use crate::binary::{self, BinaryXml, Location};
5use crate::certificate::{self, CheckedPathCertificate};
6use crate::hashes::ParticipantHashes;
7use crate::header;
8use crate::pki::CounterPaths;
9use crate::prepare::Fields;
10use crate::scalar::ScalarXml;
11use crate::xml::{Access, AccessError, Xml};
12use structured_asn1::{
13    Reader,
14    memory::{MemoryError, OwnedVec, Work},
15};
16use structured_ltv::baseline::BasicStage;
17
18#[path = "hints.rs"]
19mod hints;
20
21/// Privately constructed original receipts to combine. Their public input
22/// slots permit selecting contexts, never writing a successful fact. `basic`
23/// verifies source/catalogue/whole-counter agreement before source callbacks.
24#[derive(Clone, Copy)]
25pub struct BasicInput<'receipt, 'prepared, 'path, 'document, N, D, G> {
26    /// Original root declared attributes, URI/OID and zoned claimed-time checks.
27    pub fields: &'receipt Fields<'prepared, N, D, G>,
28    /// Root AND every incorporated counter's actual-use MD5 guard.
29    pub hashes: &'receipt ParticipantHashes<N, D, G>,
30    /// Actual selected key and all certificate digests against the checked path.
31    pub certificate: &'receipt CheckedPathCertificate<'path, 'document, N, D, G>,
32    /// Complete original counter/key/path catalogue.
33    pub counters: &'receipt CounterPaths<'path, 'document, N, D, G>,
34}
35
36/// Original certificate-reference catalogue that supplied a written issuer hint.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum HintSource {
39    /// Protected `SigningCertificateV2`, with its original certificate ordinal.
40    Protected(usize),
41    /// Ordinary or attribute unsigned reference in the same preparation.
42    ValidationReference {
43        /// Original property ordinal, retaining its role in the preparation.
44        property: usize,
45        /// Original certificate ordinal within that property.
46        entry: usize,
47    },
48}
49
50/// Original DER `IssuerSerialV2` syntax diagnostics, retaining source and capacity.
51/// This report does not assert hint/certificate agreement. The digest remains
52/// authoritative for binding; optional retrieval hints do not replace it.
53pub struct Hint<N, D, G> {
54    location: Location<N, D, G>,
55    faults: OwnedVec<structured_cms::Error>,
56    source: HintSource,
57}
58impl<N: Copy, D: Copy, G: Copy> Hint<N, D, G> {
59    /// Original protected/unsigned catalogue and ordinals, not inferred authority.
60    #[must_use]
61    pub fn source(&self) -> HintSource {
62        self.source
63    }
64    /// Original XML binary enclosure; inner offsets remain decoded coordinates.
65    #[must_use]
66    pub fn location(&self) -> Location<N, D, G> {
67        self.location
68    }
69    /// Complete encoding/schema diagnostics, without cloning or discarding them.
70    /// An empty slice does not assert matching issuer/serial or certificate trust.
71    #[must_use]
72    pub fn faults(&self) -> &[structured_cms::Error] {
73        &self.faults
74    }
75}
76
77/// Completed original root/counter field/header/key/digest facts. Negative key,
78/// digest, inclusion and readable hint outcomes remain available. This does not
79/// claim timestamp chronology, material completeness, arbitrary open-format
80/// semantics or application policy/trust acceptance, and is not a B-T/LT/LTA.
81pub struct Basic<'receipt, 'prepared, 'path, 'document, N, D, G> {
82    input: BasicInput<'receipt, 'prepared, 'path, 'document, N, D, G>,
83    included: Option<Location<N, D, G>>,
84    hints: OwnedVec<Hint<N, D, G>>,
85    hint_encodings_conform: bool,
86}
87
88impl<'r, 'p, 'v, 'd, N: Copy, D: Copy, G: Copy> Basic<'r, 'p, 'v, 'd, N, D, G> {
89    /// Same original receipt inputs, without re-verifying or copying them.
90    #[must_use]
91    pub fn inputs(&self) -> &BasicInput<'r, 'p, 'v, 'd, N, D, G> {
92        &self.input
93    }
94    /// Actual exact-DER signing-certificate occurrence in the original `KeyInfo`.
95    #[must_use]
96    pub fn included(&self) -> Option<Location<N, D, G>> {
97        self.included
98    }
99    /// Every present original protected/ordinary/attribute issuer hint and its
100    /// retained syntax diagnostics. Catalogue roles remain available through its
101    /// source; issuer/serial agreement is not claimed by these encoding reports.
102    #[must_use]
103    pub fn hints(&self) -> &[Hint<N, D, G>] {
104        &self.hints
105    }
106    /// Only the completed field/header/selected-key/digest conditions above.
107    /// Optional hints must have conforming DER syntax; their contents do not
108    /// replace the actual certificate digest binding (EN319132-1 §5.2.2 Note2).
109    /// This constant-time getter does not scan attacker-sized hint catalogues.
110    #[must_use]
111    pub fn all_root_requirements_met(&self) -> bool {
112        self.input.certificate.is_bound() && self.included.is_some() && self.hint_encodings_conform
113    }
114}
115
116impl<'v, 'd, N: Copy, D: Copy, G: Copy> BasicStage<'v, 'd> for Basic<'_, '_, 'v, 'd, N, D, G> {
117    type Id = N;
118    type Document = D;
119    type Generation = G;
120    type Signer = CheckedPathCertificate<'v, 'd, N, D, G>;
121    type Counters = CounterPaths<'v, 'd, N, D, G>;
122
123    fn signer(&self) -> &Self::Signer {
124        self.input.certificate
125    }
126    fn counters(&self) -> &Self::Counters {
127        self.input.counters
128    }
129    fn all_requirements_met(&self) -> bool {
130        self.all_root_requirements_met()
131    }
132}
133
134/// Original refusal at its actual processing boundary, never a negative key or
135/// certificate comparison and never a successful partial basic report.
136#[derive(Debug)]
137pub enum BasicError<E, N, D, G> {
138    /// Receipts belong to a different source/selection/catalogue/counter set.
139    Foreign {
140        /// Original selected root.
141        signature: N,
142    },
143    /// Original live-source/current-operation refusal.
144    Access(AccessError<E, N>),
145    /// Original counter attribute/scalar/source refusal.
146    Attributes(attributes::Error<E, N>),
147    /// Exact original certificate-inclusion binary/metadata refusal.
148    Inclusion(binary::Error<E, header::InclusionError, N, D, G>),
149    /// Original optional hint's decoding/DER/schema/callback refusal.
150    Hint(binary::Error<E, certificate::IssuerError<core::convert::Infallible>, N, D, G>),
151    /// Metadata/retained-catalogue admission failed before work.
152    Resource {
153        /// Original selecting node.
154        node: N,
155        /// Exact resource cause.
156        cause: MemoryError,
157    },
158}
159
160/// Typed original-source basic assembly result.
161pub type BasicResult<'receipt, 'prepared, 'path, 'document, X> = Result<
162    Basic<
163        'receipt,
164        'prepared,
165        'path,
166        'document,
167        <X as Xml>::Id,
168        <X as Xml>::Document,
169        <X as Xml>::Generation,
170    >,
171    BasicError<<X as Xml>::Error, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
172>;
173type InputFor<'r, 'p, 'v, 'd, X> =
174    BasicInput<'r, 'p, 'v, 'd, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
175type ProcessingResult<T, X> = Result<
176    T,
177    BasicError<<X as Xml>::Error, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
178>;
179
180/// Combine already checked root fields, every counter, selected key/path and
181/// hash restrictions; check exact original header inclusion and each optional
182/// protected/ordinary/attribute `IssuerSerialV2` DER hint once. No root/counter/path verification, certificate
183/// digest or original URI/dateTime normalization is repeated.
184///
185/// EN319132-1 V1.3.1 §§5.2.2/6.2.1/6.3(a,h–l) keep these conditions distinct;
186/// an issuer hint or `KeyInfo` count cannot replace certificate/key/digest binding.
187/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
188pub fn basic<'r, 'p, 'v, 'd, X: BinaryXml + AttributeXml + ScalarXml>(
189    input: InputFor<'r, 'p, 'v, 'd, X>,
190    xml: &X,
191    operation: &Reader<'_>,
192) -> BasicResult<'r, 'p, 'v, 'd, X> {
193    let prepared = input.fields.prepared();
194    let layout = prepared.bound().containers();
195    let signature = layout.signature();
196    let work = operation.execution();
197    let access = Access::new(xml, layout.snapshot(), &work);
198    require_sources(&input, &access)?;
199    attributes::check_counters(prepared, input.counters, &access)
200        .map_err(BasicError::Attributes)?;
201    let included = header::included(
202        xml,
203        prepared.header(),
204        input.certificate.selected().certificate(),
205        &work,
206    )
207    .map_err(BasicError::Inclusion)?;
208    let hints = hints::check_encodings(prepared, xml, operation)?;
209    access
210        .query(signature, |_, _, _| Ok(()))
211        .map_err(BasicError::Access)?;
212    Ok(Basic {
213        input,
214        included,
215        hints: hints.items,
216        hint_encodings_conform: hints.all_conformant,
217    })
218}
219
220fn require_sources<X: Xml, W: Work>(
221    input: &InputFor<'_, '_, '_, '_, X>,
222    access: &Access<'_, X, W>,
223) -> ProcessingResult<(), X> {
224    let prepared = input.fields.prepared();
225    let layout = prepared.bound().containers();
226    let signature = layout.signature();
227    let foreign = || BasicError::Foreign { signature };
228    if input.hashes.snapshot() != access.snapshot()
229        || input.counters.snapshot() != access.snapshot()
230        || !core::ptr::eq(
231            input.certificate.selected().reference(),
232            prepared.certificates(),
233        )
234    {
235        return Err(foreign());
236    }
237    access
238        .query(signature, |_, _, _| Ok(()))
239        .map_err(BasicError::Access)?;
240    let resource = |cause| BasicError::Resource {
241        node: signature,
242        cause,
243    };
244    if !access
245        .equal_id(signature, input.hashes.signature())
246        .map_err(resource)?
247        || !access
248            .equal_id(signature, input.counters.signature())
249            .map_err(resource)?
250        || input.hashes.counters().len() != input.counters.actors().len()
251    {
252        return Err(foreign());
253    }
254    for (hash, actor) in input.hashes.counters().iter().zip(input.counters.actors()) {
255        if !access
256            .equal_id(
257                hash.signature(),
258                actor.signer().counter().original().child(),
259            )
260            .map_err(resource)?
261        {
262            return Err(foreign());
263        }
264    }
265    Ok(())
266}