Skip to main content

structured_xades/validation/
ref_roles.rs

1//! Ordinary certificate references versus actual exclusive credential CA use.
2
3use super::{ReferenceSearch, ref_catalogue::Memo};
4use crate::{
5    Xades,
6    binary::{self, BinaryXml, Location},
7    certificate::{Agreement, DigestProvider},
8    digest,
9    material::{
10        ExclusiveFindingKind,
11        exclusivity::{self, CaError, CaFact, ReferenceCa},
12    },
13    prepare::{Preparation, Prepared},
14    unsigned,
15    xml::{Access, Xml},
16};
17use core::ops::Range;
18use structured_asn1::digest::Digest;
19use structured_asn1::memory::{MemoryError, OwnedVec, Work};
20use structured_ltv::material::ArtifactUses;
21use structured_ltv::timestamp::TimestampScope;
22use structured_pkix::Certificate;
23
24/// One original ordinary Cert reference and all exclusive-credential CA comparisons.
25pub struct CertificateRoleRow<N> {
26    property: usize,
27    entry: usize,
28    node: N,
29    search: ReferenceSearch,
30    range: Range<usize>,
31}
32impl<N: Copy> CertificateRoleRow<N> {
33    /// Original property ordinal in the same preparation.
34    #[must_use]
35    pub fn property(&self) -> usize {
36        self.property
37    }
38    /// Original Cert ordinal within the property.
39    #[must_use]
40    pub fn entry(&self) -> usize {
41        self.entry
42    }
43    /// Original Cert node, preserving source identity.
44    #[must_use]
45    pub fn node(&self) -> N {
46        self.node
47    }
48    /// Completed search or an explicit unavailable method/value outcome.
49    #[must_use]
50    pub fn search(&self) -> ReferenceSearch {
51        self.search
52    }
53}
54
55/// Actual exclusive credential certificate and its current reference agreement.
56pub struct CertificateRoleComparison<'certificate, 'document> {
57    certificate: &'certificate Certificate<'document>,
58    kind: ExclusiveFindingKind,
59    agreement: Agreement,
60}
61impl<'certificate, 'document> CertificateRoleComparison<'certificate, 'document> {
62    /// Actual original certificate from the complete participant/status census.
63    #[must_use]
64    pub fn certificate(&self) -> &'certificate Certificate<'document> {
65        self.certificate
66    }
67    /// Asserted forbidden CA versus insufficient old-certificate CA evidence.
68    #[must_use]
69    pub fn kind(&self) -> ExclusiveFindingKind {
70        self.kind
71    }
72    /// A match with `ForbiddenCa` violates Annex A.1.1; an unstated CA match
73    /// remains indeterminate. Neither unsupported nor malformed means Differs.
74    #[must_use]
75    pub fn agreement(&self) -> Agreement {
76        self.agreement
77    }
78}
79
80/// Only the exclusive credential CA prohibition of Annex A.1.1. Root coverage,
81/// signer exclusion, credential reference completeness, status and incorporation
82/// remain separate. TSA-only or shared credential/TSA CAs are not prohibited.
83pub struct CertificateRoles<'prepared, 'certificate, 'document, N, D, G> {
84    prepared: &'prepared Preparation<N, D, G>,
85    rows: OwnedVec<CertificateRoleRow<N>>,
86    comparisons: OwnedVec<CertificateRoleComparison<'certificate, 'document>>,
87}
88impl<'p, 'c, 'd, N: Copy, D, G> CertificateRoles<'p, 'c, 'd, N, D, G> {
89    /// Same complete original preparation.
90    #[must_use]
91    pub fn prepared(&self) -> &'p Preparation<N, D, G> {
92        self.prepared
93    }
94    /// All ordinary certificate references, without dropping unsupported entries.
95    #[must_use]
96    pub fn rows(&self) -> &[CertificateRoleRow<N>] {
97        &self.rows
98    }
99    /// All exclusive actual CA candidate comparisons for this original row.
100    #[must_use]
101    pub fn comparisons(&self, row: usize) -> Option<&[CertificateRoleComparison<'c, 'd>]> {
102        self.comparisons.get(self.rows.get(row)?.range.clone())
103    }
104}
105
106/// Original source, classification and provider failures are terminal causes.
107pub enum CertificateRoleError<'certificate, 'document, PE> {
108    /// A subset/archive/foreign participant census cannot prove exclusivity.
109    Scope,
110    /// Current metadata/catalogue work or capacity refusal.
111    Resource(MemoryError),
112    /// Lazy CA query failed in this original certificate's binary source.
113    Certificate {
114        /// Actual original certificate whose CA query failed.
115        certificate: &'certificate Certificate<'document>,
116        /// Unchanged PKIX cause and original binary offset/field.
117        cause: structured_pkix::Error,
118    },
119    /// Existing selected-provider digest driver refused.
120    Digest(digest::Error<structured_cms::stream::RangesError, PE>),
121}
122impl<PE: core::fmt::Debug> core::fmt::Debug for CertificateRoleError<'_, '_, PE> {
123    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124        match self {
125            Self::Scope => f.write_str("Scope"),
126            Self::Resource(cause) => f.debug_tuple("Resource").field(cause).finish(),
127            Self::Certificate { cause, .. } => f.debug_tuple("Certificate").field(cause).finish(),
128            Self::Digest(cause) => f.debug_tuple("Digest").field(cause).finish(),
129        }
130    }
131}
132
133/// Complete certificate-role comparisons or an unchanged terminal cause.
134pub type CertificateRoleResult<'p, 'c, 'd, X, PE> = binary::BinaryResult<
135    CertificateRoles<'p, 'c, 'd, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
136    X,
137    CertificateRoleError<'c, 'd, PE>,
138>;
139
140fn compare_role_candidates<'c, 'd, 'q, X: Xml, P: Digest<'q>, W: Work>(
141    memo: &mut Memo<'q, 'd, P, W>,
142    candidates: &mut Option<OwnedVec<ReferenceCa<'c, 'd>, W>>,
143    artifacts: &ArtifactUses<'_, '_, '_, 'c, 'd, X::Id, X::Document, X::Generation, Xades>,
144    comparisons: &mut OwnedVec<CertificateRoleComparison<'c, 'd>, W>,
145    identity: crate::certificate::FormatDigest<P::Identity>,
146    expected: &[u8],
147    work: &W,
148) -> Result<(), digest::Error<CertificateRoleError<'c, 'd, P::Error>, P::Error>> {
149    let method = memo
150        .method(identity, work)
151        .map_err(|cause| digest::Error::Source(CertificateRoleError::Digest(cause)))?;
152    if candidates.is_none() {
153        *candidates = Some(
154            exclusivity::reference_certificate_candidates(artifacts, work).map_err(|cause| {
155                digest::Error::Source(match cause {
156                    CaError::Resource(cause) => CertificateRoleError::Resource(cause),
157                    CaError::Certificate(certificate, cause) => {
158                        CertificateRoleError::Certificate { certificate, cause }
159                    }
160                })
161            })?,
162        );
163    }
164    for (ordinal, candidate) in candidates
165        .as_ref()
166        .expect("initialized candidates")
167        .iter()
168        .enumerate()
169    {
170        work.charge_work(1).map_err(digest::Error::Resource)?;
171        let agreement = memo
172            .compare(
173                ordinal,
174                candidate.certificate.as_bytes(),
175                method,
176                expected,
177                work,
178            )
179            .map_err(|cause| digest::Error::Source(CertificateRoleError::Digest(cause)))?;
180        let kind = match candidate.fact {
181            CaFact::Asserted => ExclusiveFindingKind::ForbiddenCa,
182            CaFact::Unstated => ExclusiveFindingKind::UnstatedCa,
183            CaFact::NotAsserted => unreachable!("non-CA is not an exclusion candidate"),
184        };
185        comparisons
186            .try_push(CertificateRoleComparison {
187                certificate: candidate.certificate,
188                kind,
189                agreement,
190            })
191            .map_err(digest::Error::Resource)?;
192    }
193    Ok(())
194}
195
196pub(super) fn role_error<'c, 'd, PE>(
197    cause: digest::Error<CertificateRoleError<'c, 'd, PE>, PE>,
198) -> CertificateRoleError<'c, 'd, PE> {
199    match cause {
200        digest::Error::ForeignDigest => CertificateRoleError::Digest(digest::Error::ForeignDigest),
201        digest::Error::Unstreamable => CertificateRoleError::Digest(digest::Error::Unstreamable),
202        digest::Error::Source(cause) => cause,
203        digest::Error::Provider(cause) => {
204            CertificateRoleError::Digest(digest::Error::Provider(cause))
205        }
206        digest::Error::Resource(cause) => CertificateRoleError::Resource(cause),
207        digest::Error::Stream(cause) => CertificateRoleError::Digest(digest::Error::Stream(cause)),
208        digest::Error::OutputWidth { expected, actual } => {
209            CertificateRoleError::Digest(digest::Error::OutputWidth { expected, actual })
210        }
211    }
212}
213
214/// Compare ordinary certificate references with exclusively credential CA
215/// certificates from the COMPLETE actual actor/status closure. Annex A.1.1
216/// permits TSA references; this deliberately differs from the ordinary values
217/// prohibition in §§5.4.2–5.4.3. One existing lazy BC kernel classifies each
218/// distinct actual candidate once; one full-identity digest memo serves all refs.
219/// Method/declared-value validation precedes lazy certificate scans/allocation.
220/// No parse, credential/signature/path authentication or XML acquisition repeats.
221/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
222///
223/// # Panics
224/// Only for a violated private mandatory-reference or CA-candidate invariant.
225pub fn check_certificate_reference_roles<
226    'p,
227    'c,
228    'd,
229    'q,
230    X: BinaryXml,
231    P: DigestProvider<'q, X>,
232    W: Work,
233>(
234    xml: &X,
235    prepared: &'p Prepared<X>,
236    artifacts: &ArtifactUses<'_, '_, '_, 'c, 'd, X::Id, X::Document, X::Generation, Xades>,
237    provider: &'q P,
238    work: &W,
239) -> CertificateRoleResult<'p, 'c, 'd, X, P::Error> {
240    let source = prepared.bound().containers();
241    let location = Location {
242        snapshot: source.snapshot(),
243        node: source.signature(),
244    };
245    let access = Access::new(xml, location.snapshot, work);
246    access
247        .query(location.node, |_, _, _| Ok(()))
248        .map_err(|cause| binary::Error::Access { location, cause })?;
249    let failed = |cause| binary::Error::Callback { location, cause };
250    let required = artifacts.inputs().requirements();
251    if required.scope().location().snapshot != location.snapshot
252        || !matches!(required.scope(), TimestampScope::All(_))
253        || !access
254            .equal_id(required.signature(), location.node)
255            .map_err(|cause| failed(CertificateRoleError::Resource(cause)))?
256    {
257        return Err(failed(CertificateRoleError::Scope));
258    }
259    let mut candidates: Option<OwnedVec<ReferenceCa<'c, 'd>, W>> = None;
260    let mut memo = Memo::<P, W>::new(provider, work);
261    let mut rows = work.vector();
262    let mut comparisons = work.vector();
263    for (property, refs) in prepared.validation_references().iter().enumerate() {
264        work.charge_work(1)
265            .map_err(|cause| failed(CertificateRoleError::Resource(cause)))?;
266        if refs.role() != unsigned::Role::CertificateReferences {
267            continue;
268        }
269        for (entry, reference) in refs.entries().iter().enumerate() {
270            work.charge_work(1)
271                .map_err(|cause| failed(CertificateRoleError::Resource(cause)))?;
272            let (method, value) = reference.digest().expect("mandatory CertDigest");
273            let start = comparisons.len();
274            let checked = digest::check_with(
275                xml,
276                digest::Reference {
277                    snapshot: refs.snapshot(),
278                    method,
279                    value,
280                },
281                provider,
282                work,
283                |identity, expected, work| {
284                    compare_role_candidates::<X, P, W>(
285                        &mut memo,
286                        &mut candidates,
287                        artifacts,
288                        &mut comparisons,
289                        identity,
290                        expected,
291                        work,
292                    )
293                },
294            )
295            .map_err(|cause| cause.map_callback(role_error))?;
296            let search = match checked {
297                digest::Checked::Value(()) => ReferenceSearch::Compared,
298                digest::Checked::Answer(answer) => ReferenceSearch::Unavailable(answer),
299            };
300            rows.try_push(CertificateRoleRow {
301                property,
302                entry,
303                node: reference.node(),
304                search,
305                range: start..comparisons.len(),
306            })
307            .map_err(|cause| failed(CertificateRoleError::Resource(cause)))?;
308        }
309    }
310    access
311        .query(location.node, |_, _, _| Ok(()))
312        .map_err(|cause| binary::Error::Access { location, cause })?;
313    Ok(CertificateRoles {
314        prepared,
315        rows: rows.into_owned(),
316        comparisons: comparisons.into_owned(),
317    })
318}