summaryrefslogtreecommitdiffstats
path: root/openpgp/src/wot/path.rs
diff options
context:
space:
mode:
Diffstat (limited to 'openpgp/src/wot/path.rs')
-rw-r--r--openpgp/src/wot/path.rs395
1 files changed, 395 insertions, 0 deletions
diff --git a/openpgp/src/wot/path.rs b/openpgp/src/wot/path.rs
new file mode 100644
index 00000000..5a3a8616
--- /dev/null
+++ b/openpgp/src/wot/path.rs
@@ -0,0 +1,395 @@
+use std::fmt;
+
+use crate::Result;
+use crate::packet::prelude::*;
+use crate::cert::prelude::*;
+use crate::Fingerprint;
+
+use super::Certification;
+use super::TRACE;
+
+/// A network path.
+#[derive(Clone)]
+pub struct Path<'a> {
+ // The root.
+ root: ValidCert<'a>,
+
+ // Then the transition from the previous node to the next, and the
+ // next node.
+ edges: Vec<Certification<'a>>,
+
+ // Residual depth. To append a certification, this must be >0.
+ // After adding a new certification, the new residual depth is:
+ // min(residual_depth - 1, certification.depth).
+ residual_depth: usize,
+}
+
+impl<'a> fmt::Debug for Path<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Path [\n")?;
+
+ let print_vc = |f: &mut fmt::Formatter<'_>, vc: &ValidCert<'a>|
+ -> fmt::Result
+ {
+ f.write_str(" ")?;
+ if let Ok(ua) = vc.primary_userid() {
+ f.write_str(
+ &String::from_utf8_lossy(
+ ua.userid().value())[..])?;
+ } else {
+ f.write_str("[no User ID]")?;
+ }
+ f.write_str("\n ")?;
+ f.write_str(&vc.keyid().to_string())?;
+ f.write_str("\n")?;
+ Ok(())
+ };
+
+ print_vc(f, &self.root)?;
+
+ for certification in self.edges.iter() {
+ f.write_str(
+ " |\n")?;
+ f.write_str(&format!(
+ " | depth: {}\n", certification.depth)[..])?;
+ f.write_str(&format!(
+ " | amount: {}\n", certification.amount)[..])?;
+ f.write_str(&format!(
+ " | regexes: {}\n",
+ if certification.re_set.matches_everything() {
+ String::from("*")
+ } else {
+ format!("{:?}", &certification.re_set)
+ }))?;
+ f.write_str(
+ " v\n")?;
+
+ print_vc(f, &certification.target_cert)?;
+ }
+ f.write_str("]")?;
+
+ Ok(())
+ }
+}
+
+impl<'a> Path<'a> {
+ /// Instantiates a path starting at the specified root.
+ ///
+ /// We assume that the root is ultimately trusted (its trust depth
+ /// is unlimited and its trust amount is maximal).
+ pub(super) fn new(root: ValidCert<'a>) -> Self
+ {
+ Self {
+ root: root,
+
+ // Most paths will be direct (len: 2) or via one trusted
+ // introducer (len: 3); meta-introducers are really used.
+ edges: Vec::with_capacity(3 - 1),
+
+ residual_depth: usize::MAX,
+ }
+ }
+
+ /// Returns the path's root.
+ pub fn root(&self) -> &ValidCert<'a> {
+ &self.root
+ }
+
+ /// Returns the last node in the path.
+ pub fn tail(&self) -> &ValidCert<'a> {
+ if self.edges.len() == 0 {
+ &self.root
+ } else {
+ &self.edges[self.edges.len() - 1].target_cert
+ }
+ }
+
+ /// Returns an iterator over the path's certificates (the nodes).
+ ///
+ /// The certificates are returned from the root towards the target.
+ pub fn certificates(&'a self) -> impl Iterator<Item=ValidCert<'a>> {
+ std::iter::once(self.root.clone())
+ .chain(self.edges.iter().map(|certification| {
+ certification.target_cert.clone()
+ }))
+ }
+
+ /// Returns the number of nodes in the path.
+ pub fn len(&self) -> usize {
+ 1 + self.edges.len()
+ }
+
+ /// Returns the certifications.
+ ///
+ /// The certifications are returned from the root towards the target.
+ pub fn certifications(&'a self) -> impl Iterator<Item=&Certification<'a>> {
+ self.edges.iter()
+ }
+
+ /// Returns the tail's trust depth.
+ pub fn residual_depth(&self) -> usize {
+ self.residual_depth
+ }
+
+ /// The amount that the tail is trusted.
+ pub fn amount(&self) -> usize {
+ self.edges.iter().map(|e| e.amount).min().unwrap_or(120) as usize
+ }
+
+ /// Appends 'certification' to the path if the path allows it.
+ ///
+ /// A path may not allow it if the trust depth is insufficient.
+ /// Paths are also not allowed to contain cycles.
+ pub(super) fn try_append(&mut self, certification: Certification<'a>)
+ -> Result<()>
+ {
+ tracer!(false, "Path::try_append", 0);
+ t!(" path: {:?}", self);
+ t!(" certification: {:?}", certification);
+
+ if self.tail().fingerprint() != certification.issuer_cert.fingerprint() {
+ return Err(anyhow::format_err!(
+ "Path's tail ({}) did not issuer certification ({})",
+ self.tail().fingerprint(), certification.issuer_cert.fingerprint()));
+ }
+
+ if self.residual_depth == 0 {
+ return Err(anyhow::format_err!("Not enough depth"));
+ }
+
+ // Check for cycles.
+ if self.root.fingerprint()
+ == certification.target_cert.fingerprint()
+ || self.edges.iter().any(|c| {
+ c.target_cert.fingerprint()
+ == certification.target_cert.fingerprint()
+ })
+ {
+ return Err(anyhow::format_err!(
+ "Adding {} would create a cycle",
+ certification.target_cert.fingerprint()));
+ }
+
+ self.residual_depth
+ = std::cmp::min(self.residual_depth - 1,
+ certification.depth as usize);
+ self.edges.push(certification);
+
+ Ok(())
+ }
+
+ /// Appends a Path.
+ pub(super) fn extend(&mut self, suffix: &Path<'a>) -> Result<()> {
+ for certification in suffix.edges.iter() {
+ self.try_append(certification.clone())?;
+ }
+
+ Ok(())
+ }
+
+ /// Appends an RPath.
+ pub(super) fn weld(&mut self, rest: RPath<'a>) -> Result<()> {
+ tracer!(TRACE, "Path::weld", 0);
+ t!("{:?}", self);
+ t!("{:?}", rest);
+ for c in rest.edges.into_iter().rev() {
+ self.try_append(c)?;
+ }
+ Ok(())
+ }
+
+ pub(super) fn replace_tail(&mut self, tail: Certification<'a>) {
+ let l = self.edges.len();
+ self.edges[l - 1] = tail;
+ }
+}
+
+/// A collection of paths.
+///
+/// The amount is the amount while respecting the total capacity of
+/// the edges.
+#[derive(Clone)]
+pub struct Paths<'a> {
+ paths: Vec<(Path<'a>, usize)>,
+}
+
+impl<'a> Paths<'a> {
+ pub(super) fn new() -> Self {
+ Self {
+ paths: Vec::new(),
+ }
+ }
+
+ /// The paths.
+ ///
+ /// Returns an iterator over each path and its trust amount.
+ pub fn iter(&self) -> impl Iterator<Item=&(Path<'a>, usize)> {
+ self.paths.iter()
+ }
+
+ /// The aggregate trust amount.
+ ///
+ /// This respects the network's capacity. Thus, if multiple paths
+ /// use the same edge, the total trust amount may be less than
+ /// simple the trust amount of each individual path.
+ pub fn amount(&self) -> usize {
+ self.paths.iter().map(|(_, a)| a).sum()
+ }
+
+ pub(super) fn push(&mut self, path: Path<'a>, amount: usize) {
+ assert!(amount <= path.amount());
+
+ self.paths.push((path, amount));
+ }
+}
+
+impl<'a> fmt::Debug for Paths<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("Paths [\n")?;
+ for (i, (p, a)) in self.iter().enumerate() {
+ f.write_str(&format!(" PATH {}, trust amount: {}:\n{:?}",
+ i, a, p))?;
+ }
+ f.write_str("]")?;
+ Ok(())
+ }
+}
+
+/// A network path suffix.
+#[derive(Clone)]
+pub(super) struct RPath<'a> {
+ pub target_fpr: Fingerprint,
+ pub target_userid: UserID,
+
+ // The certification.
+ pub edges: Vec<Certification<'a>>,
+}
+
+impl<'a> fmt::Debug for RPath<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("RPath [\n")?;
+
+ f.write_str(" ???\n")?;
+ f.write_str(" |\n")?;
+ for (i, c) in self.edges.iter().rev().enumerate() {
+ if i == 0 {
+ f.write_str(
+ &format!(" {}\n",
+ &c.issuer_cert.primary_userid()
+ .map(|ua| String::from_utf8_lossy(ua.userid().value()).into_owned())
+ .unwrap_or("<missing User ID>".into())))?;
+ f.write_str(
+ &format!(" {}\n", c.issuer_cert.fingerprint()))?;
+ }
+
+ f.write_str(
+ " |\n")?;
+ f.write_str(&format!(
+ " | depth: {}\n", c.depth)[..])?;
+ f.write_str(&format!(
+ " | amount: {}\n", c.amount)[..])?;
+ f.write_str(&format!(
+ " | regexes: {:?}\n", c.re_set)[..])?;
+ f.write_str(
+ " v\n")?;
+ f.write_str(
+ &format!(" {}\n",
+ &String::from_utf8_lossy(c.target_userid.value())))?;
+ f.write_str(
+ &format!(" {}\n", c.target_cert.fingerprint()))?;
+ }
+
+ if self.edges.len() == 0 {
+ f.write_str(
+ &format!(" {}\n",
+ &String::from_utf8_lossy(self.target_userid.value())))?;
+ f.write_str(
+ &format!(" {}", self.target_fpr))?;
+ }
+
+ f.write_str("]")?;
+
+ Ok(())
+ }
+}
+
+impl<'a> RPath<'a> {
+ /// Instantiates a path starting at the specified root.
+ pub fn new(target_userid: UserID, target_fpr: Fingerprint) -> Self
+ {
+ Self {
+ target_userid,
+ target_fpr,
+ edges: vec![],
+ }
+ }
+
+ pub fn from_path(p: Path<'a>, target_fpr: Fingerprint, target_userid: UserID)
+ -> Self
+ {
+ RPath {
+ target_fpr,
+ target_userid,
+ edges: p.edges.into_iter().rev().collect(),
+ }
+ }
+
+ /// Returns the number of nodes in the path.
+ pub fn len(&self) -> usize {
+ 1 + self.edges.len()
+ }
+
+ /// Prepends 'certification' to the path if the path allows it.
+ pub fn try_prepend(&self, certification: Certification<'a>)
+ -> Result<Self>
+ {
+ // XXX: Check trust amount!
+ tracer!(false, "RPath::try_prepend", 0);
+ t!(" path: {:?}", self);
+ t!(" certification: {:?}", certification);
+
+ // XXX: Check target_userid, target_fpr.
+
+ if certification.re_set.matches_everything() {
+ for c in self.edges.iter() {
+ // Check that this introducer can be used to certify the
+ // target.
+ if ! certification.re_set.matches_userid(&c.target_userid) {
+ return Err(anyhow::format_err!(
+ "Certification's REs don't match user id ({})",
+ c.target_userid));
+ }
+ }
+ }
+
+ // Check for cycles.
+ if self.edges.iter().any(|c| {
+ c.target_cert.fingerprint()
+ == certification.target_cert.fingerprint()
+ })
+ {
+ return Err(anyhow::format_err!(
+ "Adding {} would create a cycle",
+ certification.target_cert.fingerprint()));
+ }
+
+
+ // The path remains possible.
+ let mut path = self.clone();
+ path.edges.push(certification);
+
+ Ok(path)
+ }
+
+ pub fn head(&self) -> Fingerprint {
+ if self.edges.len() == 0 {
+ self.target_fpr.clone()
+ } else {
+ self.edges[self.edges.len() - 1].issuer_cert.fingerprint()
+ }
+ }
+
+ pub fn empty(&self) -> bool {
+ self.edges.len() == 0
+ }
+}