wot_network/
search.rs

1//! Data structures and traits for path searches in a Web of Trust
2
3use std::fmt::Display;
4
5use crate::{Binding, Certificate, Certification, Delegation, Edge, Network};
6
7/// Error type for [`ResidualNetworkTrait::reduce`]
8#[derive(Debug)]
9pub struct ReduceError(pub String);
10
11impl Display for ReduceError {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        write!(f, "{}", self.0)
14    }
15}
16
17impl std::error::Error for ReduceError {}
18
19/// The interface of a residual network, which can (optionally) be used in a [`WotSearchTrait`].
20pub trait ResidualNetworkTrait {
21    /// Set up a new residual network based on `network`, and with the given set of `anchors`.
22    ///
23    /// TODO: allow implementations to keep a reference to `&Network`, with appropriate lifetimes?
24    fn new(network: &Network, anchors: &[TrustAnchor]) -> Self;
25
26    /// Find the "best" path from the anchors to `target`, given the remaining capacities in this
27    /// residual network.
28    ///
29    /// Returns a [`Path`] and the effective trust amount for this path, given the flow constraints
30    /// of this residual network.
31    ///
32    /// Note that the effective trust amount (which is limited by the current limitations of the
33    /// residual network state) can be lower than the "standalone" trust amount of that path.
34    /// (This limitation can be caused by a capacity limit of the anchor, an edge or a node along
35    /// the path).
36    fn search(&self, target: &Binding) -> Option<(Path, u8)>;
37
38    /// Reduce the remaining capacities in this residual network by `reduce_amount` for `path`.
39    ///
40    /// Returns a `ReduceError` if the reduce operation was invalid.
41    ///
42    /// In the error case, the residual network's state should not be changed.
43    /// Using flow with this function should be an atomic operation!
44    fn reduce(&mut self, path: &Path, reduce_amount: u8) -> Result<(), ReduceError>;
45
46    /// Get the remaining flow capacity of a trust anchor, specifically in its role as an anchor.
47    ///
48    /// `None`, if `anchor` is not a trust anchor,
49    fn anchor_capacity(&self, anchor: &Certificate) -> Option<u8>;
50
51    /// The amount of remaining available outgoing flow capacity from `cert`,
52    /// in any context (including as an anchor): along a delegation edge, or along certification
53    /// edges.
54    ///
55    /// By default, all certificates start with an outbound capacity of `120`
56    /// (but a certificate may be initialized differently, in a residual network).
57    fn outbound_capacity(&self, cert: &Certificate) -> u8;
58
59    /// The amount of flow that has been used between `issuer` and `target` (along delegation
60    /// edges), in this residual network.
61    ///
62    /// `issuer` and `target` may not be the same certificate in calls to this function.
63    fn used_flow_between(&self, issuer: &Certificate, target: &Certificate) -> u8;
64}
65
66/// An interface for searching paths that authenticate identity bindings in a Web of Trust network.
67pub trait WotSearchTrait {
68    fn new(network: Network, anchors: Vec<TrustAnchor>, target: Binding) -> Self;
69
70    /// Authenticate the link between the certificate and the identity in the `target` Binding
71    /// with an OpenPGP "Web of Trust" lookup.
72    ///
73    /// Searches for enough paths to satisfy `target_trust_amount`, if possible.
74    fn search(&mut self, target_trust_amount: usize) -> Paths;
75}
76
77/// A list of [Path]s and their *effective amount* in this search.
78///
79/// NOTE: The effective amount of a [Path] in the context of a [Paths] may be smaller than the
80/// path's standalone amount, because anchors, edges or nodes shared with other [Path]s may
81/// restrict flow capacity.
82#[derive(Debug)]
83pub struct Paths {
84    paths: Vec<(Path, u8)>,
85}
86
87impl Paths {
88    pub fn new() -> Self {
89        Self {
90            paths: Default::default(),
91        }
92    }
93
94    pub fn new_with_paths(paths: Vec<(Path, u8)>) -> Self {
95        Self { paths }
96    }
97
98    pub fn get(&self) -> &[(Path, u8)] {
99        &self.paths
100    }
101
102    /// Add a [Path] to this list of paths.
103    ///
104    /// The `effective_amount` of the path in this set of paths can be smaller than the path's
105    /// standalone amount: The flow capacity can be limited (by other paths in this `Paths`)
106    /// by the remaining capacity of the anchor, edges or nodes along the path.
107    pub fn add(&mut self, path: Path, effective_amount: u8) {
108        assert!(
109            effective_amount <= path.standalone_amount(),
110            "Effective amount cannot exceed the standalone amount of the path."
111        );
112
113        self.paths.push((path, effective_amount));
114    }
115
116    /// Sum of the effective amounts of each [Path] in this [Paths] object.
117    ///
118    /// This is the total amount of (independent) evidence that the [Network] offers for an
119    /// identity binding, based on the configured [TrustAnchor]s.
120    ///
121    /// Note that the sum of total effective trust amounts of multiple paths can exceed the
122    /// value 120 (which is a limit of trust amount values in all other contexts).
123    pub fn total_effective_amount(&self) -> usize {
124        self.paths
125            .iter()
126            .map(|(_, effective)| *effective as usize)
127            .sum()
128    }
129}
130
131impl Default for Paths {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137/// A Path starts from an anchoring [Certificate] and consists of a list of delegation edges which
138/// lead to a target identity binding.
139#[derive(Debug, Clone, Eq, Hash, PartialEq)]
140pub struct Path {
141    pub start: Certificate,
142    pub delegations: Vec<Delegation>,
143    pub certification: Certification,
144}
145
146impl Path {
147    pub fn new(
148        start: Certificate,
149        delegations: Vec<Delegation>,
150        certification: Certification,
151    ) -> Self {
152        Self {
153            start,
154            delegations,
155            certification,
156        }
157    }
158
159    /// First cert of the path.
160    pub fn first(&self) -> &Certificate {
161        &self.start
162    }
163
164    /// The list of delegation edges that lead from the anchor to the target binding
165    pub fn delegations(&self) -> &[Delegation] {
166        &self.delegations
167    }
168
169    /// The target binding of this [Path]
170    pub fn binding(&self) -> &Certification {
171        &self.certification
172    }
173
174    /// The "standalone trust amount" of this path.
175    ///
176    /// The amount is 120 for paths without a delegation edge,
177    /// or the minimum trust amount of any delegation edge.
178    pub(crate) fn standalone_amount(&self) -> u8 {
179        self.delegations
180            .iter()
181            .map(|d| d.trust_amount)
182            .min()
183            .unwrap_or(120) // paths without delegations have trust amount "120"
184    }
185
186    /// A list of certificates in this [Path], from the starting [Certificate], followed by all
187    /// [Delegation] edge target [Certificate]s, and the target [Certificate] in the final
188    /// [Certification] edge.
189    ///
190    /// This list format is mainly intended for informal uses, such as comparing test results with
191    /// expected paths.
192    pub fn certificates(&self) -> Vec<&Certificate> {
193        let mut certs = vec![self.first()];
194        for edge in self.delegations() {
195            certs.push(edge.target())
196        }
197
198        certs.push(&self.certification.target.cert);
199
200        certs
201    }
202
203    /// The length of this path, counted in nodes:
204    /// A path with a single edge from node A to node B is of length 2.
205    pub fn length(&self) -> usize {
206        // FIXME: do we need to subtract one if the last edge is a redirection to a different
207        // user id in the certificate we're already at?
208        self.delegations.len() + 2
209    }
210
211    /// An iterator over all edges in this path.
212    ///
213    /// This always returns a series of zero or more [`Edge::Delegation`]s,
214    /// followed by exactly one [`Edge::Certification`].
215    pub fn edges(&self) -> impl Iterator<Item = Edge> + '_ {
216        self.delegations()
217            .iter()
218            .map(|d| Edge::Delegation(d.clone()))
219            .chain([Edge::Certification(self.binding().clone())])
220    }
221}
222
223/// A certificate that acts as a root of trust in a Web of Trust search
224#[derive(Debug, Clone, Eq, Hash, PartialEq)]
225pub struct TrustAnchor {
226    pub cert: Certificate,
227    pub trust_amount: u8,
228}
229
230impl TrustAnchor {
231    /// A `trust_amount` over 120 is illegal
232    pub fn new(cert: Certificate, trust_amount: u8) -> Self {
233        assert!(trust_amount <= 120);
234
235        Self { cert, trust_amount }
236    }
237
238    pub fn cert(&self) -> &Certificate {
239        &self.cert
240    }
241
242    pub fn amount(&self) -> u8 {
243        self.trust_amount
244    }
245}