wot_network/search.rs
1//! Data structures and traits for path searches in a Web of Trust
2
3use crate::{Binding, Certificate, Certification, Delegation, Edge, Network};
4
5/// An interface for searching paths that authenticate identity bindings in a Web of Trust network.
6pub trait WotSearch<'a> {
7 fn new(network: &'a Network, roots: &'a [TrustAnchor]) -> Self;
8
9 /// Authenticate the link between `target_id` and `target_userid` with an OpenPGP "Web of Trust" query.
10 ///
11 /// Searches for enough paths to satisfy `target_trust_amount`, if possible.
12 fn search(&self, binding: &Binding, target_trust_amount: usize) -> Paths;
13}
14
15/// A list of [Path]s and their *effective amount* in this search.
16///
17/// NOTE: The effective amount of a [Path] in the context of a [Paths] may be smaller than the
18/// path's standalone amount, because edges shared with other [Path]s may restrict flow capacity.
19#[derive(Debug)]
20pub struct Paths {
21 paths: Vec<(Path, u8)>,
22}
23
24impl Paths {
25 pub fn new() -> Self {
26 Self {
27 paths: Default::default(),
28 }
29 }
30
31 pub fn get(&self) -> &[(Path, u8)] {
32 &self.paths
33 }
34
35 /// Add a [Path] to this list of paths.
36 ///
37 /// The `effective_amount` of the path in this set of paths can be smaller than the paths
38 /// actual amount (it can be limited by the available remaining flow along one of its edges,
39 /// caused by other paths in this `Paths`)
40 pub fn add(&mut self, path: Path, effective_amount: u8) {
41 assert!(
42 effective_amount <= path.amount(),
43 "Effective amount cannot exceed actual amount of the path."
44 );
45
46 self.paths.push((path, effective_amount));
47 }
48
49 /// Sum of the effective amounts of each [Path] in this [Paths] object.
50 ///
51 /// This is the total amount of evidence that the [Network] offers for an identity binding,
52 /// based on the configured [TrustAnchor]s
53 pub fn total_amount(&self) -> usize {
54 self.paths.iter().map(|(_, amount)| *amount as usize).sum()
55 }
56}
57
58impl Default for Paths {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64/// A Path starts from an anchoring [Certificate] and consists of a list of delegation edges which lead
65/// to a target identity binding.
66#[derive(Debug, Clone, Eq, Hash, PartialEq)]
67pub struct Path {
68 start: Certificate,
69 delegations: Vec<Delegation>,
70 binding: Option<Certification>,
71}
72
73impl Path {
74 pub fn new(start: Certificate) -> Self {
75 Self {
76 start,
77 delegations: vec![],
78 binding: None,
79 }
80 }
81
82 /// First cert of the path.
83 pub fn first(&self) -> &Certificate {
84 &self.start
85 }
86
87 /// The list of delegation edges that lead from the anchor to the target binding
88 pub fn delegations(&self) -> &[Delegation] {
89 &self.delegations
90 }
91
92 /// The target binding of this [Path]
93 pub fn binding(&self) -> Option<&Certification> {
94 self.binding.as_ref()
95 }
96
97 /// The mathematical "amount" of this path.
98 ///
99 /// This corresponds to the minimum "amount" of any edge in the path.
100 pub(crate) fn amount(&self) -> u8 {
101 // We start with the trust amount of the final binding edge (always 120) as the ceiling
102 let mut min = 120;
103
104 // ... and lower it to the minimal amount on any delegation edge.
105 if self.delegations().is_empty() {
106 min
107 } else {
108 for edge in &self.delegations {
109 min = u8::min(min, edge.trust_amount());
110 }
111 min
112 }
113 }
114
115 /// A list of certificates in this [Path], from the starting [Certificate], followed by all
116 /// [Delegation] edge target [Certificate]s, and the target [Certificate] in the final [Certification] edge.
117 ///
118 /// This list format is mainly mostly for informal uses, such as comparing test results with
119 /// expected paths.
120 pub fn certificates(&self) -> Vec<Certificate> {
121 let mut certs = vec![self.first().clone()];
122 for edge in self.delegations() {
123 certs.push(edge.target().clone())
124 }
125
126 if let Some(b) = &self.binding {
127 certs.push(b.target.clone())
128 } else {
129 panic!("Incomplete path")
130 }
131
132 certs
133 }
134
135 /// The length of this path, counted in nodes: A path with a single edge from node A to node B
136 /// is of length 2.
137 pub fn length(&self) -> usize {
138 // FIXME: do we need to subtract one if the last edge is a redirection to a different
139 // user id in the certificate we're already at?
140 self.delegations.len() + 2
141 }
142
143 pub fn edges(&self) -> impl Iterator<Item = Edge> + '_ {
144 self.delegations()
145 .iter()
146 .map(|d| Edge::Delegation(d.clone()))
147 .chain(self.binding().map(|b| Edge::Certification(b.clone())))
148 }
149
150 /// Add a delegation edge
151 pub fn push(&mut self, edge: Delegation) {
152 assert!(
153 self.binding.is_none(),
154 "Once the binding is set, no more delegations should be added"
155 );
156
157 self.delegations.push(edge)
158 }
159
160 pub fn set_binding(&mut self, bind: Certification) {
161 assert!(
162 self.binding.is_none(),
163 "A binding should not normally be replaced"
164 );
165
166 self.binding = Some(bind)
167 }
168}
169
170/// A certificate that acts as a root of trust in a Web of Trust search
171#[derive(Debug, Clone, Eq, Hash, PartialEq)]
172pub struct TrustAnchor {
173 id: Certificate,
174 trust_amount: u8,
175}
176
177impl TrustAnchor {
178 /// FIXME: is a `trust_amount` over 120 illegal?
179 pub fn new(cert_id: Certificate, trust_amount: u8) -> Self {
180 Self {
181 id: cert_id,
182 trust_amount,
183 }
184 }
185
186 pub fn id(&self) -> &Certificate {
187 &self.id
188 }
189
190 pub fn amount(&self) -> u8 {
191 self.trust_amount
192 }
193}