Skip to main content

structured_cms/attribute_certificate/validation/
authentication.rs

1//! Authenticated issuance and evaluation-window facts, not AC authorization.
2//!
3//! RFC5755 §5 separates signature/purpose/time from holder authentication,
4//! certification paths, direct AA trust and targeting. The latter obligations,
5//! proxy effects and revocation remain with the caller. A successfully parsed
6//! critical extension is not an enforced effect. Returned profile facts retain
7//! these obligations rather than silently satisfying them.
8//! <https://www.rfc-editor.org/rfc/rfc5755.html#section-5>
9
10use super::{Check, Profile, Rejection as ProfileRejection, charge};
11use crate::attribute_certificate::AttributeCertificate;
12use crate::{Error, Result};
13use structured_asn1::Span;
14use structured_asn1::digest::Resolution;
15use structured_asn1::memory::{Execution, Work};
16use structured_pkix::path::{Interval, Verified, Verify, WorkingKey};
17use structured_pkix::{AlgorithmIdentifier, Certificate};
18
19/// Semantic agreement supplied by the same selected signature verifier.
20///
21/// Resolve both original identifiers, including algorithm-specific defaults
22/// and parameter validity, before comparing. Equal malformed parameters do not
23/// agree; generic absent/NULL equality is not this operation. Unsupported and
24/// malformed inputs remain distinct from a completed different identity.
25/// Original algorithm readers preserve their own source/depth and current work.
26/// No second registry or cryptographic engine is selected by this trait.
27pub trait AlgorithmAgreement {
28    /// Original provider, policy or resource cause.
29    type Error;
30    /// Compare fully resolved identities; no signature arithmetic yet.
31    fn algorithms<W: Work>(
32        &self,
33        signed: AlgorithmIdentifier<'_>,
34        outer: AlgorithmIdentifier<'_>,
35        work: &W,
36    ) -> core::result::Result<Resolution<bool>, Self::Error>;
37}
38
39/// Callback capability or source of a provider failure.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum Stage {
42    /// Original inner/outer algorithm resolution and agreement.
43    Algorithms,
44    /// Exact original signature input and effective key.
45    Signature,
46}
47
48/// Completed semantic refusal, never a resource or callback error.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Failure {
51    /// Static AC profile rejected, retaining its original reason and source.
52    Profile(ProfileRejection),
53    /// Supplied AA identity or purpose rejected by the common authority check.
54    Authority(ProfileRejection),
55    /// Effective key's original SPKI differs from the supplied AA certificate.
56    KeyBinding,
57    /// The entire evaluation interval is not inside the AC's inclusive validity.
58    AcValidity,
59    /// The entire interval is not inside the AA PKC's inclusive validity.
60    AuthorityValidity,
61    /// At least one known algorithm has malformed parameters.
62    AlgorithmSyntax,
63    /// Resolved inner and outer signature identities disagree.
64    AlgorithmMismatch,
65    /// Signature is not a whole number of octets.
66    SignatureEncoding,
67    /// Completed signature verification returned No.
68    Signature,
69}
70
71/// A refusal and an honest original enclosure, not an invented field range.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct Rejection {
74    /// Which fact failed.
75    pub reason: Failure,
76    /// Original AC or authority context.
77    pub source: Span,
78}
79
80/// Completed operation, with unsupported capability separate from rejection.
81#[derive(Debug)]
82pub enum Outcome<T> {
83    /// Issuance, static profile and explicit interval checks completed.
84    Authenticated(T),
85    /// A completed negative check.
86    Rejected(Rejection),
87    /// The selected provider/policy cannot execute this capability.
88    Unsupported(Stage),
89}
90
91/// Original shared processing failure or selected-provider failure.
92#[derive(Debug)]
93pub enum AuthenticationError<E> {
94    /// Original located encoding/resource cause.
95    Processing(Error),
96    /// A newer verifier answer has no interpretation in this implementation.
97    UnknownAnswer,
98    /// Original opaque provider cause; it is not rewritten as a document fault.
99    Callback {
100        /// Callback being executed.
101        stage: Stage,
102        /// Real enclosing AC context, not a replacement for the cause's source.
103        context: Span,
104        /// Original callback cause, including resource or policy failures.
105        source: E,
106    },
107}
108impl<E> From<Error> for AuthenticationError<E> {
109    fn from(value: Error) -> Self {
110        Self::Processing(value)
111    }
112}
113
114/// Original authenticated issuance facts with independently owned profile storage.
115///
116/// This is not full RFC5755 acceptance: holder authentication, both PKIX paths,
117/// direct AA trust, targeting/proxy effects, revocation and product permission
118/// remain separate. AKI is retained as a hint, not a mandatory matching policy
119/// (RFC5755 §4.3.3). UTC correction provenance belongs to the caller, as for the
120/// shared PKIX interval; comparisons retain fractional and inserted-second labels.
121#[derive(Debug)]
122pub struct Authenticated<'r, 'a, 's> {
123    profile: Profile<'r, 'a>,
124    authority: &'r Certificate<'s>,
125    key: WorkingKey<'s>,
126    interval: Interval,
127}
128impl<'r, 'a, 's> Authenticated<'r, 'a, 's> {
129    /// Original AC, without normalization or removal of its attributes.
130    #[must_use]
131    pub fn certificate(&self) -> &AttributeCertificate<'a> {
132        self.profile.certificate()
133    }
134    /// Checked static facts and still-pending targeting/proxy/status obligations.
135    #[must_use]
136    pub fn profile(&self) -> &Profile<'r, 'a> {
137        &self.profile
138    }
139    /// Actual supplied authority; this operation does not establish its path/trust.
140    #[must_use]
141    pub fn authority(&self) -> &'r Certificate<'s> {
142        self.authority
143    }
144    /// Actual supplied effective key, preserving its inherited parameter source.
145    #[must_use]
146    pub fn key(&self) -> WorkingKey<'s> {
147        self.key
148    }
149    /// Complete explicit interval checked against both AC and authority validity.
150    #[must_use]
151    pub fn interval(&self) -> Interval {
152        self.interval
153    }
154}
155
156fn rejected<T>(reason: Failure, source: Span) -> Outcome<T> {
157    Outcome::Rejected(Rejection { reason, source })
158}
159
160fn key_matches<W: Work>(
161    authority: &Certificate<'_>,
162    key: WorkingKey<'_>,
163    work: &W,
164) -> Result<bool> {
165    let original = key.key().algorithm();
166    let key_work = key.key().execution_in(work);
167    let authority_work = authority.execution_in(work);
168    charge(&key_work, 1, original.offset())?;
169    charge(&authority_work, 1, authority.span().start)?;
170    let supplied = key.key().as_bytes();
171    let actual = authority.tbs().public_key().as_bytes();
172    if supplied.len() != actual.len() {
173        return Ok(false);
174    }
175    // The common case is the very same immutable SPKI. Identity needs no scan;
176    // metadata still pays each source/current ceiling before this shortcut.
177    if core::ptr::eq(supplied, actual) {
178        return Ok(true);
179    }
180    charge(&key_work, supplied.len() as u64, original.offset())?;
181    charge(&authority_work, actual.len() as u64, authority.span().start)?;
182    Ok(supplied == actual)
183}
184
185impl<'a> AttributeCertificate<'a> {
186    /// Authenticate the selected authority/key and complete evaluation interval.
187    ///
188    /// All static profile, authority, key, time and signature-padding gates run
189    /// before provider resolution or signature math. The selected verifier also
190    /// implements the existing PKIX signature contract for scoped execution.
191    /// It must account backend work/storage and use original key/signature
192    /// readers for deferred decoding; passing Work does not qualify a backend.
193    /// No PKIX path or direct AA trust is inferred from successful arithmetic.
194    pub fn authenticate<'r, 's, V, W: Work>(
195        &'r self,
196        authority: &'r Certificate<'s>,
197        key: WorkingKey<'s>,
198        interval: Interval,
199        verifier: &V,
200        work: &W,
201    ) -> core::result::Result<
202        Outcome<Authenticated<'r, 'a, 's>>,
203        AuthenticationError<<V as AlgorithmAgreement>::Error>,
204    >
205    where
206        V: AlgorithmAgreement,
207        for<'op> V: Verify<Execution<'op>, Error = <V as AlgorithmAgreement>::Error>,
208    {
209        let execution = work.execution_for(self.parser);
210        let at = self.signed_element().span();
211        let profile = match self.profile(&execution)? {
212            Check::Accepted(profile) => profile,
213            Check::Rejected(cause) => return Ok(rejected(Failure::Profile(cause), at)),
214        };
215        if let Check::Rejected(cause) = self.bind_authority(authority, &execution)? {
216            return Ok(rejected(Failure::Authority(cause), authority.span()));
217        }
218        if !key_matches(authority, key, &execution)? {
219            return Ok(rejected(Failure::KeyBinding, authority.span()));
220        }
221        charge(&execution, 4, at.start)?;
222        let info = self.information();
223        if interval.from_instant() < info.not_before.instant()
224            || interval.to_instant() > info.not_after.instant()
225        {
226            return Ok(rejected(Failure::AcValidity, at));
227        }
228        let authority_work = authority.execution_in(&execution);
229        charge(&authority_work, 4, authority.span().start)?;
230        let validity = authority.tbs().validity();
231        if interval.from_instant() < validity.not_before().instant()
232            || interval.to_instant() > validity.not_after().instant()
233        {
234            return Ok(rejected(Failure::AuthorityValidity, authority.span()));
235        }
236        charge(&execution, 1, at.start)?;
237        if self.signature_value().octets().is_none() {
238            return Ok(rejected(
239                Failure::SignatureEncoding,
240                self.signature_value().span(),
241            ));
242        }
243        // RFC5755 §4.1 carries two original algorithm fields. Their resolved
244        // equality is algorithm-specific, not generic absent/NULL normalization.
245        // https://www.rfc-editor.org/rfc/rfc5755.html#section-4.1
246        match verifier
247            .algorithms(info.signature, self.signature_algorithm(), &execution)
248            .map_err(|source| AuthenticationError::Callback {
249                stage: Stage::Algorithms,
250                context: at,
251                source,
252            })? {
253            Resolution::Supported(true) => {}
254            Resolution::Supported(false) => return Ok(rejected(Failure::AlgorithmMismatch, at)),
255            Resolution::Malformed => return Ok(rejected(Failure::AlgorithmSyntax, at)),
256            Resolution::Unsupported => return Ok(Outcome::Unsupported(Stage::Algorithms)),
257        }
258        charge(&execution, 1, at.start)?;
259        match verifier
260            .verify(
261                &info.signature,
262                key,
263                self.signed_bytes(),
264                self.signature_value(),
265                &execution,
266            )
267            .map_err(|source| AuthenticationError::Callback {
268                stage: Stage::Signature,
269                context: at,
270                source,
271            })? {
272            Verified::Yes => Ok(Outcome::Authenticated(Authenticated {
273                profile,
274                authority,
275                key,
276                interval,
277            })),
278            Verified::No => Ok(rejected(Failure::Signature, self.signature_value().span())),
279            Verified::Unsupported => Ok(Outcome::Unsupported(Stage::Signature)),
280            _ => Err(AuthenticationError::UnknownAnswer),
281        }
282    }
283}