use std::fmt;
use std::ops::Deref;
use sequoia_openpgp as openpgp;
use openpgp::Result;
use crate::Certification;
use crate::Depth;
use crate::CertSynopsis;
#[derive(Clone)]
pub struct Path {
root: CertSynopsis,
edges: Vec<Certification>,
certification_network: bool,
residual_depth: Depth,
}
impl fmt::Debug for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = f.precision().unwrap_or(0);
let indent: String = vec![ ' '; indent ].into_iter().collect();
f.write_fmt(format_args!(
"Path [\n"))?;
if self.certification_network {
f.write_fmt(format_args!(
"{} (certification network)\n", indent))?;
}
f.write_fmt(format_args!(
"{} {} ({})\n",
indent,
self.root.fingerprint(),
self.root.primary_userid().map(|userid| {
String::from_utf8_lossy(userid.value()).into_owned()
}).unwrap_or_else(|| "[no User ID]".into())))?;
for certification in self.edges.iter() {
f.write_fmt(format_args!(
"{} |\n", indent))?;
f.write_fmt(format_args!(
"{} | depth: {}\n", indent, certification.depth()))?;
f.write_fmt(format_args!(
"{} | amount: {}\n", indent, certification.amount()))?;
f.write_fmt(format_args!(
"{} | regexes: {}\n",
indent,
if let Some(re_set) = certification.regular_expressions() {
if re_set.matches_everything() {
String::from("*")
} else {
format!("{:?}", re_set)
}
} else {
"<invalid RE>".into()
}))?;
f.write_fmt(format_args!(
"{} v\n", indent))?;
f.write_fmt(format_args!(
"{} {} ({})\n",
indent, certification.target().fingerprint(),
certification.userid().map(|userid| {
String::from_utf8_lossy(userid.value()).into_owned()
}).unwrap_or_else(|| "[no User ID]".into())))?;
}
f.write_fmt(format_args!("{}]", indent))?;
Ok(())
}
}
impl Path {
pub fn new<C>(root: C) -> Self
where C: Into<CertSynopsis>
{
Self {
root: root.into(),
edges: Vec::with_capacity(2),
certification_network: false,
residual_depth: Depth::new(None),
}
}
pub fn set_certification_network(&mut self, certification_network: bool) {
self.certification_network = certification_network;
}
pub fn certification_network(&self) -> bool {
self.certification_network
}
pub fn root(&self) -> &CertSynopsis {
&self.root
}
pub fn target(&self) -> &CertSynopsis {
if self.edges.len() == 0 {
&self.root
} else {
&self.edges[self.edges.len() - 1].target()
}
}
pub fn certificates(&self)
-> impl Iterator<Item=&CertSynopsis> + DoubleEndedIterator
{
std::iter::once(&self.root)
.chain(self.edges.iter().map(|certification| {
certification.target()
}))
}
pub fn len(&self) -> usize {
1 + self.edges.len()
}
pub fn certifications(&self) -> impl Iterator<Item=&Certification> {
self.edges.iter()
}
pub fn residual_depth(&self) -> Depth {
self.residual_depth
}
pub fn amount(&self) -> usize {
self.edges.iter()
.zip((0..self.edges.len()).rev())
.map(|(e, required_depth)| {
if self.certification_network
|| e.depth() >= required_depth.into()
{
e.amount()
} else {
0
}
}).min().unwrap_or(120) as usize
}
pub fn append(&mut self, certification: Certification)
-> Result<()>
{
if self.target().fingerprint() != certification.issuer().fingerprint() {
return Err(anyhow::format_err!(
"Can't add certification to path: \
the path's tail ({}) is not the certification's issuer ({})",
self.target().fingerprint(), certification.issuer()));
}
let depth = certification.depth();
self.edges.push(certification);
self.residual_depth = self.residual_depth
.max(1.into())
.decrease(1)
.min(depth);
Ok(())
}
pub fn try_append(&mut self, certification: Certification)
-> Result<()>
{
tracer!(false, "Path::try_append");
t!(" path: {:?}", self);
t!(" certification: {:?}", certification);
if ! self.certification_network && self.residual_depth == 0.into() {
return Err(anyhow::format_err!("Not enough depth"));
}
if self.root.fingerprint() == certification.target().fingerprint()
|| self.edges.iter()
.enumerate()
.any(|(i, c)| {
if c.target().fingerprint()
== certification.target().fingerprint()
{
if i == self.edges.len() - 1 {
c.userid() == certification.userid()
} else {
true
}
} else {
false
}
})
{
return Err(anyhow::format_err!(
"Adding {} to the path would create a cycle",
certification.target()));
}
self.append(certification)?;
Ok(())
}
pub fn self_signature(&self) -> bool {
self.edges.is_empty()
}
fn has_suffix(&self, path: &Path) -> bool {
tracer!(false, "Path::has_suffix");
t!("Self: {:?}", self);
t!("Other: {:?}", path);
if self.len() < path.len() {
t!("self is shorter ({}) than path ({}); path can't be a suffix.",
self.len(), path.len());
return false;
}
if self.self_signature() != path.self_signature() {
t!("self is{} a self signature, path is{}",
if self.self_signature() {
""
} else {
" not"
},
if path.self_signature() {
""
} else {
" not"
});
return false;
}
let certs_match = self.certificates().rev()
.zip(path.certificates().rev())
.all(|(this, other)| {
this.fingerprint() == other.fingerprint()
});
if certs_match {
t!("Certificates match");
true
} else {
t!("Certificates don't match");
return false;
}
}
}
#[derive(Clone)]
pub struct Paths {
paths: Vec<(Path, usize)>,
}
impl Paths {
pub fn new() -> Self {
Self {
paths: Vec::new(),
}
}
pub fn iter(&self) -> impl Iterator<Item=&(Path, usize)> {
self.paths.iter()
}
pub fn into_iter(self) -> impl Iterator<Item=(Path, usize)> {
self.paths.into_iter()
}
pub fn amount(&self) -> usize {
self.paths.iter().map(|(_, a)| a).sum()
}
pub fn push(&mut self, path: Path, amount: usize) {
self.paths.push((path, amount));
}
pub fn has_suffix(&self, path: &Path) -> bool {
self.iter().any(|(other, _amount)| other.has_suffix(path))
}
}
impl Deref for Paths {
type Target=[(Path, usize)];
fn deref(&self) -> &Self::Target {
&self.paths[..]
}
}
impl fmt::Debug for Paths {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent = f.precision().unwrap_or(0);
let indent: String = vec![ ' '; indent ].into_iter().collect();
f.write_fmt(format_args!("Paths [\n"))?;
for (i, (p, a)) in self.iter().enumerate() {
f.write_fmt(format_args!(
"{} PATH #{}, trust amount: {}: {:.*?}\n",
indent, i, a, indent.len() + 2, p))?;
}
f.write_fmt(format_args!("{}]", indent))?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::time::SystemTime;
use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;
use sequoia_openpgp::packet::UserID;
use openpgp::types::RevocationStatus;
#[allow(unused)]
#[test]
fn has_suffix() {
let alice_fpr: Fingerprint =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
.parse().expect("valid fingerprint");
let alice_uid = UserID::from("<alice@example.org>");
let alice = CertSynopsis::new(
alice_fpr.clone(), None,
RevocationStatus::NotAsFarAsWeKnow.into(),
std::iter::once((alice_uid.clone(), SystemTime::now())));
let bob_fpr: Fingerprint =
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
.parse().expect("valid fingerprint");
let bob_uid = UserID::from("<bob@example.org>");
let bob = CertSynopsis::new(
bob_fpr.clone(), None,
RevocationStatus::NotAsFarAsWeKnow.into(),
std::iter::once((bob_uid.clone(), SystemTime::now())));
let carol_fpr: Fingerprint =
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
.parse().expect("valid fingerprint");
let carol_uid = UserID::from("<carol@example.org>");
let carol = CertSynopsis::new(
carol_fpr.clone(), None,
RevocationStatus::NotAsFarAsWeKnow.into(),
std::iter::once((carol_uid.clone(), SystemTime::now())));
let t = SystemTime::now();
let alice_certifies_bob
= Certification::new(alice.clone(),
Some(bob_uid.clone()),
bob.clone(),
t);
let alice_certifies_carol
= Certification::new(alice.clone(),
Some(carol_uid.clone()),
carol.clone(),
t);
let bob_certifies_carol
= Certification::new(bob.clone(),
Some(carol_uid.clone()),
carol.clone(),
t);
let mut alice_bob_carol = Path::new(alice.clone());
alice_bob_carol.append(alice_certifies_bob.clone());
alice_bob_carol.append(bob_certifies_carol.clone());
let mut alice_bob = Path::new(alice.clone());
alice_bob.append(alice_certifies_bob.clone());
let mut alice_carol = Path::new(alice.clone());
alice_carol.append(alice_certifies_carol.clone());
let mut bob_carol = Path::new(bob.clone());
bob_carol.append(bob_certifies_carol.clone());
let mut alice = Path::new(alice.clone());
let mut bob = Path::new(bob.clone());
let mut carol = Path::new(carol.clone());
assert!(! alice.has_suffix(&bob));
assert!(alice.has_suffix(&alice));
assert!(! alice_bob.has_suffix(&alice));
assert!(alice_bob.has_suffix(&alice_bob));
assert!(! alice_bob.has_suffix(&alice_carol));
assert!(! alice_bob.has_suffix(&bob));
assert!(! alice_bob_carol.has_suffix(&alice));
assert!(! alice_bob_carol.has_suffix(&bob));
assert!(! alice_bob_carol.has_suffix(&carol));
assert!(! alice_bob_carol.has_suffix(&alice_bob));
assert!(! alice_bob.has_suffix(&alice_carol));
assert!(alice_bob_carol.has_suffix(&alice_bob_carol));
assert!(alice_bob_carol.has_suffix(&bob_carol));
}
}