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}