Skip to main content

structured_ltv/pki/
purpose.rs

1//! Actual actor uses propagated through the common status-authority graph.
2
3use super::closure::{Key, follow};
4use super::{
5    AuthorityClosureError, AuthorityNode, AuthorityOrigin, Participant, ParticipantAuthority,
6    ParticipantClosure,
7};
8use crate::source::{Query, ScopeError};
9use structured_asn1::memory::{OwnedMap, OwnedVec, Work};
10
11/// Actual validation purposes, not configurable policy or an authorization flag.
12/// Several purposes can apply to the same path or artifact. In particular a
13/// shared CA cannot be called exclusive to TSA/credentials merely because it
14/// was first encountered through one of those actors.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct Uses(u8);
17
18impl Uses {
19    /// The one purpose an actor's own validation material serves.
20    #[must_use]
21    pub fn participant<N: Copy>(participant: Participant<N>) -> Self {
22        match participant {
23            Participant::Signer(_) | Participant::Counter { .. } => Self(1),
24            Participant::Credential { .. } => Self(2),
25            Participant::Timestamp { .. } => Self(4),
26        }
27    }
28
29    /// Adds every purpose of `other`; purposes are never removed.
30    pub fn merge(&mut self, other: Self) {
31        self.0 |= other.0;
32    }
33
34    /// Used for the actual root signer or an incorporated countersignature.
35    #[must_use]
36    pub fn signature(self) -> bool {
37        self.0 & 1 != 0
38    }
39    /// Used by an actual protected credential and its validation authorities.
40    #[must_use]
41    pub fn credential(self) -> bool {
42        self.0 & 2 != 0
43    }
44    /// Used by an actual TSA in the selected scope and its validation authorities.
45    #[must_use]
46    pub fn timestamp(self) -> bool {
47        self.0 & 4 != 0
48    }
49}
50
51/// One reached original authority with all its actual transitive actor uses.
52pub struct AuthorityUse<'view, 'certificate, 'document> {
53    node: &'view AuthorityNode<'view, 'certificate, 'document>,
54    uses: Uses,
55    pending: u8,
56}
57
58impl<'view, 'certificate, 'document> AuthorityUse<'view, 'certificate, 'document> {
59    /// Same original status inputs and responder associations, never re-evaluated.
60    #[must_use]
61    pub fn node(&self) -> &'view AuthorityNode<'view, 'certificate, 'document> {
62        self.node
63    }
64    /// All actor roles reaching this exact path/evaluation-label pair.
65    #[must_use]
66    pub fn uses(&self) -> Uses {
67        self.uses
68    }
69}
70
71/// Complete role propagation for the original participant closure. It establishes
72/// neither status usability nor material placement, and never normalizes bytes.
73pub struct AuthorityUses<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, I> {
74    inputs: &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>,
75    entries: OwnedVec<AuthorityUse<'view, 'certificate, 'document>>,
76}
77
78impl<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, I>
79    AuthorityUses<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, I>
80{
81    /// Same complete source-bound actor and status-authority input set.
82    #[must_use]
83    pub fn inputs(
84        &self,
85    ) -> &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>
86    {
87        self.inputs
88    }
89    /// Each reached node once, preserving original closure order.
90    #[must_use]
91    pub fn entries(&self) -> &[AuthorityUse<'view, 'certificate, 'document>] {
92        &self.entries
93    }
94}
95
96/// Source/refusal errors cannot be interpreted as exclusive material use.
97#[derive(Debug)]
98pub enum UsesError<N> {
99    /// Current original source refused.
100    Scope(ScopeError<N>),
101    /// Participant source/generation differs from this operation.
102    Foreign,
103    /// Actual graph, indexing or admitted storage/work refused.
104    Closure(AuthorityClosureError),
105}
106
107/// Purpose propagation result in the query's original identity domain.
108pub type UsesResult<'closure, 'requirements, 'view, 'certificate, 'document, Q, I> = Result<
109    AuthorityUses<
110        'closure,
111        'requirements,
112        'view,
113        'certificate,
114        'document,
115        <Q as Query>::Id,
116        <Q as Query>::Document,
117        <Q as Query>::Generation,
118        I,
119    >,
120    UsesError<<Q as Query>::Id>,
121>;
122
123struct Flow<'view, 'certificate, 'document, W: Work> {
124    index: OwnedMap<Key<'view, 'document>, usize, W>,
125    entries: OwnedVec<AuthorityUse<'view, 'certificate, 'document>, W>,
126    queue: OwnedVec<usize, W>,
127}
128
129impl<'view, 'document, W: Work> Flow<'view, '_, 'document, W> {
130    fn need(
131        &mut self,
132        key: Key<'view, 'document>,
133        uses: u8,
134        origin: AuthorityOrigin,
135        work: &W,
136    ) -> Result<(), AuthorityClosureError> {
137        let index = *self
138            .index
139            .get(&key, work)?
140            .ok_or(AuthorityClosureError::MissingPath { origin })?;
141        work.charge_work(1)?;
142        let entry = &mut self.entries[index];
143        let added = uses & !entry.uses.0;
144        if added == 0 {
145            return Ok(());
146        }
147        if entry.pending == 0 {
148            self.queue.try_push(index)?;
149        }
150        entry.uses.0 |= added;
151        entry.pending |= added;
152        Ok(())
153    }
154}
155
156/// Propagate actual root/counter/credential/TSA uses through the SAME dependency
157/// kernel as `close_authorities`. §5.4.2/§5.4.3's exclusive-use rules concern all
158/// uses, including indirect CRL/OCSP signers, not the first path that found a CA.
159/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
160///
161/// One indexed graph, an iterative queue and three fixed purpose bits bound
162/// propagation: each bit is admitted at each node at most once, including cycles.
163/// Only newly added bits propagate. No parser/provider/status check is repeated;
164/// direct anchors and certificate-free credentials remain separate requirements.
165/// Scratch drops before return; failure cannot return a successful role subset.
166///
167/// # Panics
168/// Only if a private queue invariant is violated: a queue index was admitted
169/// after its node's insertion and nodes are never removed during propagation.
170pub fn authority_uses<'closure, 'requirements, 'view, 'certificate, 'document, Q: Query, I>(
171    inputs: &'closure ParticipantClosure<
172        'requirements,
173        'view,
174        'certificate,
175        'document,
176        Q::Id,
177        Q::Document,
178        Q::Generation,
179        I,
180    >,
181    query: &Q,
182) -> UsesResult<'closure, 'requirements, 'view, 'certificate, 'document, Q, I> {
183    let requirements = inputs.requirements();
184    if requirements.scope().location().snapshot != query.snapshot() {
185        return Err(UsesError::Foreign);
186    }
187    query
188        .admit(requirements.signature(), 1)
189        .map_err(UsesError::Scope)?;
190    derive(inputs, query.execution()).map_err(UsesError::Closure)
191}
192
193fn derive<
194    'closure,
195    'requirements,
196    'view,
197    'certificate,
198    'document,
199    N: Copy,
200    D: Copy,
201    G: Copy,
202    W: Work,
203    I,
204>(
205    inputs: &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G, I>,
206    work: &W,
207) -> Result<
208    AuthorityUses<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G, I>,
209    AuthorityClosureError,
210> {
211    let mut flow = Flow {
212        index: work.map(),
213        entries: work.vector(),
214        queue: work.vector(),
215    };
216    let mut responders = work.map();
217    flow.entries
218        .try_reserve_exact(inputs.authorities().nodes().len())?;
219    flow.queue
220        .try_reserve_exact(inputs.authorities().nodes().len())?;
221    for (index, node) in inputs.authorities().nodes().iter().enumerate() {
222        work.charge_work(1)?;
223        let key = Key {
224            path: node.statuses.path(),
225            at: node.statuses.evaluated_at(),
226        };
227        if flow.index.try_insert(key, index)?.is_some() {
228            return Err(AuthorityClosureError::DuplicateNode { index });
229        }
230        flow.entries.try_push(AuthorityUse {
231            node,
232            uses: Uses(0),
233            pending: 0,
234        })?;
235        for responder in node.responders {
236            work.charge_work(1)?;
237            let key = core::ptr::from_ref(responder.target()).addr();
238            if responders.try_insert(key, *responder)?.is_some() {
239                return Err(AuthorityClosureError::DuplicateResponder { node: index });
240            }
241        }
242    }
243    let mut root = 0;
244    for entry in inputs.requirements().entries() {
245        work.charge_work(1)?;
246        let ParticipantAuthority::Path(_) = entry.authority() else {
247            continue;
248        };
249        let statuses = inputs.authorities().roots()[root];
250        let uses = Uses::participant(entry.participant()).0;
251        flow.need(
252            Key {
253                path: statuses.path(),
254                at: statuses.evaluated_at(),
255            },
256            uses,
257            AuthorityOrigin::Root { index: root },
258            work,
259        )?;
260        root += 1;
261    }
262    debug_assert_eq!(root, inputs.authorities().roots().len());
263    while !flow.queue.is_empty() {
264        work.charge_work(1)?;
265        let index = flow.queue.pop().expect("nonempty queue");
266        let node = flow.entries[index].node;
267        let added = flow.entries[index].pending;
268        flow.entries[index].pending = 0;
269        follow(
270            node.statuses,
271            index,
272            &responders,
273            &mut |path, at, origin, work| flow.need(Key { path, at }, added, origin, work),
274            work,
275        )?;
276    }
277    Ok(AuthorityUses {
278        inputs,
279        entries: flow.entries.into_owned(),
280    })
281}