diff --git a/build.rs b/build.rs index 097a75d..add9861 100644 --- a/build.rs +++ b/build.rs @@ -4,6 +4,5 @@ use vergen::{generate_cargo_keys, ConstantsFlags}; fn main() { // Generate the 'cargo:' key output - generate_cargo_keys(ConstantsFlags::all()) - .expect("Unable to generate the cargo keys!"); + generate_cargo_keys(ConstantsFlags::all()).expect("Unable to generate the cargo keys!"); } diff --git a/database/src/fs.rs b/database/src/fs.rs index d45bf23..d87685c 100644 --- a/database/src/fs.rs +++ b/database/src/fs.rs @@ -1,19 +1,21 @@ use std::collections::HashMap; use std::convert::TryFrom; -use std::fs::{OpenOptions, File, create_dir_all, read_link, remove_file, rename, set_permissions, Permissions}; +use std::fs::{ + create_dir_all, read_link, remove_file, rename, set_permissions, File, OpenOptions, Permissions, +}; use std::io::Write; -use std::path::{Path, PathBuf}; use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; -use tempfile; -use url::form_urlencoded; use pathdiff::diff_paths; use std::time::SystemTime; +use tempfile; +use url::form_urlencoded; -use {Database, Query}; -use types::{Email, Fingerprint, KeyID}; use sync::FlockMutexGuard; +use types::{Email, Fingerprint, KeyID}; use Result; +use {Database, Query}; use wkd; @@ -51,7 +53,6 @@ fn ensure_parent(path: &Path) -> Result<&Path> { Ok(path) } - impl Filesystem { pub fn new_from_base(base_dir: impl Into) -> Result { let base_dir: PathBuf = base_dir.into(); @@ -164,22 +165,23 @@ impl Filesystem { /// Returns the path to the given Email. fn link_by_email(&self, email: &Email) -> PathBuf { - let email = form_urlencoded::byte_serialize(email.as_str().as_bytes()) - .collect::(); + let email = form_urlencoded::byte_serialize(email.as_str().as_bytes()).collect::(); self.links_dir_by_email.join(path_split(&email)) } /// Returns the WKD path to the given Email. fn link_wkd_by_email(&self, email: &Email) -> PathBuf { let (encoded_local_part, domain) = wkd::encode_wkd(email.as_str()).unwrap(); - let encoded_domain = form_urlencoded::byte_serialize(domain.as_bytes()) - .collect::(); + let encoded_domain = + form_urlencoded::byte_serialize(domain.as_bytes()).collect::(); [ &self.links_dir_wkd_by_email, &encoded_domain, - &path_split(&encoded_local_part) - ].iter().collect() + &path_split(&encoded_local_part), + ] + .iter() + .collect() } /// Returns the WKD path to the given url-encoded domain and wkd-encoded local part. @@ -187,16 +189,19 @@ impl Filesystem { [ &self.links_dir_wkd_by_email, Path::new(&domain), - &path_split(hash) - ].iter().collect() + &path_split(hash), + ] + .iter() + .collect() } #[allow(clippy::nonminimal_bool)] fn read_from_path(&self, path: &Path, allow_internal: bool) -> Option { use std::fs; - if !path.starts_with(&self.keys_external_dir) && - !(allow_internal && path.starts_with(&self.keys_internal_dir)) { + if !path.starts_with(&self.keys_external_dir) + && !(allow_internal && path.starts_with(&self.keys_internal_dir)) + { panic!("Attempted to access file outside expected dirs!"); } @@ -211,8 +216,9 @@ impl Filesystem { fn read_from_path_bytes(&self, path: &Path, allow_internal: bool) -> Option> { use std::fs; - if !path.starts_with(&self.keys_external_dir) && - !(allow_internal && path.starts_with(&self.keys_internal_dir)) { + if !path.starts_with(&self.keys_external_dir) + && !(allow_internal && path.starts_with(&self.keys_internal_dir)) + { panic!("Attempted to access file outside expected dirs!"); } @@ -286,8 +292,9 @@ impl Filesystem { let expected = diff_paths( &self.fingerprint_to_path_published(fpr), - link.parent().unwrap() - ).unwrap(); + link.parent().unwrap(), + ) + .unwrap(); symlink_unlink_with_check(&link, &expected) } @@ -297,8 +304,9 @@ impl Filesystem { let expected = diff_paths( &self.fingerprint_to_path_published_wkd(fpr), - link.parent().unwrap() - ).unwrap(); + link.parent().unwrap(), + ) + .unwrap(); symlink_unlink_with_check(&link, &expected) } @@ -317,8 +325,8 @@ impl Filesystem { tpks: &mut HashMap, check: impl Fn(&Path, &Cert, &Fingerprint) -> Result<()>, ) -> Result<()> { - use walkdir::WalkDir; use std::fs; + use walkdir::WalkDir; for entry in WalkDir::new(checks_dir) { let entry = entry?; @@ -331,23 +339,19 @@ impl Filesystem { // Compute the corresponding primary fingerprint just // by looking at the paths. let primary_fp = Filesystem::path_to_primary(path) - .ok_or_else( - || format_err!("Malformed path: {:?}", - path.read_link().unwrap()))?; + .ok_or_else(|| format_err!("Malformed path: {:?}", path.read_link().unwrap()))?; // Load into cache. - if ! tpks.contains_key(&primary_fp) { + if !tpks.contains_key(&primary_fp) { tpks.insert( primary_fp.clone(), - self.lookup(&Query::ByFingerprint(primary_fp.clone())) - ?.ok_or_else( - || format_err!("No Cert with fingerprint {:?}", - primary_fp))?); + self.lookup(&Query::ByFingerprint(primary_fp.clone()))? + .ok_or_else(|| format_err!("No Cert with fingerprint {:?}", primary_fp))?, + ); } - let tpk = tpks.get(&primary_fp) - .ok_or_else( - || format_err!("Broken symlink {:?}: No such Key {}", - path, primary_fp))?; + let tpk = tpks.get(&primary_fp).ok_or_else(|| { + format_err!("Broken symlink {:?}: No such Key {}", path, primary_fp) + })?; check(path, tpk, &primary_fp)?; } @@ -433,7 +437,11 @@ impl Database for Filesystem { Ok(()) } - fn move_tmp_to_published_wkd(&self, file: Option, fpr: &Fingerprint) -> Result<()> { + fn move_tmp_to_published_wkd( + &self, + file: Option, + fpr: &Fingerprint, + ) -> Result<()> { if self.dry_run { return Ok(()); } @@ -461,24 +469,30 @@ impl Database for Filesystem { Ok(()) } - fn check_link_fpr(&self, fpr: &Fingerprint, fpr_target: &Fingerprint) -> Result> { + fn check_link_fpr( + &self, + fpr: &Fingerprint, + fpr_target: &Fingerprint, + ) -> Result> { let link_keyid = self.link_by_keyid(&fpr.into()); let link_fpr = self.link_by_fingerprint(fpr); let path_published = self.fingerprint_to_path_published(fpr_target); if let Ok(link_fpr_target) = link_fpr.canonicalize() { - if !link_fpr_target.ends_with(&path_published) { + if !link_fpr_target.ends_with(&path_published) { info!("Fingerprint points to different key for {} (expected {:?} to be suffix of {:?})", fpr, &path_published, &link_fpr_target); return Err(anyhow!(format!("Fingerprint collision for key {}", fpr))); - } + } } if let Ok(link_keyid_target) = link_keyid.canonicalize() { if !link_keyid_target.ends_with(&path_published) { - info!("KeyID points to different key for {} (expected {:?} to be suffix of {:?})", - fpr, &path_published, &link_keyid_target); + info!( + "KeyID points to different key for {} (expected {:?} to be suffix of {:?})", + fpr, &path_published, &link_keyid_target + ); return Err(anyhow!(format!("KeyID collision for key {}", fpr))); } } @@ -496,7 +510,7 @@ impl Database for Filesystem { ByFingerprint(ref fp) => self.link_by_fingerprint(fp), ByKeyID(ref keyid) => self.link_by_keyid(keyid), ByEmail(ref email) => self.link_by_email(email), - _ => return None + _ => return None, }; path.read_link() .ok() @@ -527,8 +541,11 @@ impl Database for Filesystem { let link_fpr = self.link_by_fingerprint(from); let link_keyid = self.link_by_keyid(&from.into()); - let target = diff_paths(&self.fingerprint_to_path_published(primary_fpr), - link_fpr.parent().unwrap()).unwrap(); + let target = diff_paths( + &self.fingerprint_to_path_published(primary_fpr), + link_fpr.parent().unwrap(), + ) + .unwrap(); symlink(&target, ensure_parent(&link_fpr)?)?; symlink(&target, ensure_parent(&link_keyid)?) @@ -537,10 +554,13 @@ impl Database for Filesystem { fn unlink_fpr(&self, from: &Fingerprint, primary_fpr: &Fingerprint) -> Result<()> { let link_fpr = self.link_by_fingerprint(from); let link_keyid = self.link_by_keyid(&from.into()); - let expected = diff_paths(&self.fingerprint_to_path_published(primary_fpr), - link_fpr.parent().unwrap()).unwrap(); + let expected = diff_paths( + &self.fingerprint_to_path_published(primary_fpr), + link_fpr.parent().unwrap(), + ) + .unwrap(); - if let Ok(target) = read_link(&link_fpr) { + if let Ok(target) = read_link(&link_fpr) { if target == expected { remove_file(&link_fpr)?; } @@ -604,7 +624,9 @@ impl Database for Filesystem { // A cache of all Certs, for quick lookups. let mut tpks = HashMap::new(); - self.perform_checks(&self.keys_dir_published, &mut tpks, + self.perform_checks( + &self.keys_dir_published, + &mut tpks, |path, _, primary_fp| { // The KeyID corresponding with this path. let fp = Filesystem::path_to_fingerprint(path) @@ -614,131 +636,141 @@ impl Database for Filesystem { return Err(format_err!( "{:?} points to the wrong Cert, expected {} \ but found {}", - path, fp, primary_fp)); + path, + fp, + primary_fp + )); } Ok(()) - } + }, )?; - self.perform_checks(&self.keys_dir_published, &mut tpks, - |_, tpk, primary_fp| { - // check that certificate exists in published wkd path - let path_wkd = self.fingerprint_to_path_published_wkd(primary_fp); - let should_wkd_exist = tpk.userids().next().is_some(); + self.perform_checks(&self.keys_dir_published, &mut tpks, |_, tpk, primary_fp| { + // check that certificate exists in published wkd path + let path_wkd = self.fingerprint_to_path_published_wkd(primary_fp); + let should_wkd_exist = tpk.userids().next().is_some(); - if should_wkd_exist && !path_wkd.exists() { - return Err(format_err!("Missing wkd for fp {}", primary_fp)); - }; - if !should_wkd_exist && path_wkd.exists() { - return Err(format_err!("Incorrectly present wkd for fp {}", primary_fp)); - }; - Ok(()) - } - )?; + if should_wkd_exist && !path_wkd.exists() { + return Err(format_err!("Missing wkd for fp {}", primary_fp)); + }; + if !should_wkd_exist && path_wkd.exists() { + return Err(format_err!("Incorrectly present wkd for fp {}", primary_fp)); + }; + Ok(()) + })?; // check that all subkeys are linked - self.perform_checks(&self.keys_dir_published, &mut tpks, - |_, tpk, primary_fp| { - let policy = &POLICY; - let fingerprints = tpk - .keys() - .with_policy(policy, None) - .for_certification() - .for_signing() - .map(|amalgamation| amalgamation.key().fingerprint()) - .map(Fingerprint::try_from) - .flatten(); + self.perform_checks(&self.keys_dir_published, &mut tpks, |_, tpk, primary_fp| { + let policy = &POLICY; + let fingerprints = tpk + .keys() + .with_policy(policy, None) + .for_certification() + .for_signing() + .map(|amalgamation| amalgamation.key().fingerprint()) + .map(Fingerprint::try_from) + .flatten(); - for fpr in fingerprints { - if let Some(missing_fpr) = self.check_link_fpr(&fpr, primary_fp)? { - return Err(format_err!( - "Missing link to key {} for sub {}", primary_fp, missing_fpr)); - } + for fpr in fingerprints { + if let Some(missing_fpr) = self.check_link_fpr(&fpr, primary_fp)? { + return Err(format_err!( + "Missing link to key {} for sub {}", + primary_fp, + missing_fpr + )); } - Ok(()) } - )?; + Ok(()) + })?; // check that all published uids are linked - self.perform_checks(&self.keys_dir_published, &mut tpks, - |_, tpk, primary_fp| { - let emails = tpk - .userids() - .map(|binding| binding.userid().clone()) - .map(|userid| Email::try_from(&userid).unwrap()); + self.perform_checks(&self.keys_dir_published, &mut tpks, |_, tpk, primary_fp| { + let emails = tpk + .userids() + .map(|binding| binding.userid().clone()) + .map(|userid| Email::try_from(&userid).unwrap()); - for email in emails { - let email_path = self.link_by_email(&email); - if !email_path.exists() { - return Err(format_err!( - "Missing link to key {} for email {}", primary_fp, email)); - } - let email_wkd_path = self.link_wkd_by_email(&email); - if !email_wkd_path.exists() { - return Err(format_err!( - "Missing wkd link to key {} for email {}", primary_fp, email)); - } - } - Ok(()) - } - )?; - - - self.perform_checks(&self.links_dir_by_fingerprint, &mut tpks, - |path, tpk, _| { - // The KeyID corresponding with this path. - let id = Filesystem::path_to_keyid(path) - .ok_or_else(|| format_err!("Malformed path: {:?}", path))?; - - let found = tpk.keys() - .map(|amalgamation| KeyID::try_from(amalgamation.key().fingerprint()).unwrap()) - .any(|key_fp| key_fp == id); - if ! found { + for email in emails { + let email_path = self.link_by_email(&email); + if !email_path.exists() { return Err(format_err!( - "{:?} points to the wrong Cert, the Cert does not \ - contain the (sub)key {}", path, id)); + "Missing link to key {} for email {}", + primary_fp, + email + )); } - Ok(()) - } - )?; - - self.perform_checks(&self.links_dir_by_keyid, &mut tpks, - |path, tpk, _| { - // The KeyID corresponding with this path. - let id = Filesystem::path_to_keyid(path) - .ok_or_else(|| format_err!("Malformed path: {:?}", path))?; - - let found = tpk.keys() - .map(|amalgamation| KeyID::try_from(amalgamation.key().fingerprint()).unwrap()) - .any(|key_fp| key_fp == id); - if ! found { + let email_wkd_path = self.link_wkd_by_email(&email); + if !email_wkd_path.exists() { return Err(format_err!( - "{:?} points to the wrong Cert, the Cert does not \ - contain the (sub)key {}", path, id)); + "Missing wkd link to key {} for email {}", + primary_fp, + email + )); } - Ok(()) } - )?; + Ok(()) + })?; - self.perform_checks(&self.links_dir_by_email, &mut tpks, - |path, tpk, _| { - // The Email corresponding with this path. - let email = Filesystem::path_to_email(path) - .ok_or_else(|| format_err!("Malformed path: {:?}", path))?; - let mut found = false; - for uidb in tpk.userids() { - if Email::try_from(uidb.userid()).unwrap() == email - { - found = true; - break; - } + self.perform_checks(&self.links_dir_by_fingerprint, &mut tpks, |path, tpk, _| { + // The KeyID corresponding with this path. + let id = Filesystem::path_to_keyid(path) + .ok_or_else(|| format_err!("Malformed path: {:?}", path))?; + + let found = tpk + .keys() + .map(|amalgamation| KeyID::try_from(amalgamation.key().fingerprint()).unwrap()) + .any(|key_fp| key_fp == id); + if !found { + return Err(format_err!( + "{:?} points to the wrong Cert, the Cert does not \ + contain the (sub)key {}", + path, + id + )); + } + Ok(()) + })?; + + self.perform_checks(&self.links_dir_by_keyid, &mut tpks, |path, tpk, _| { + // The KeyID corresponding with this path. + let id = Filesystem::path_to_keyid(path) + .ok_or_else(|| format_err!("Malformed path: {:?}", path))?; + + let found = tpk + .keys() + .map(|amalgamation| KeyID::try_from(amalgamation.key().fingerprint()).unwrap()) + .any(|key_fp| key_fp == id); + if !found { + return Err(format_err!( + "{:?} points to the wrong Cert, the Cert does not \ + contain the (sub)key {}", + path, + id + )); + } + Ok(()) + })?; + + self.perform_checks(&self.links_dir_by_email, &mut tpks, |path, tpk, _| { + // The Email corresponding with this path. + let email = Filesystem::path_to_email(path) + .ok_or_else(|| format_err!("Malformed path: {:?}", path))?; + let mut found = false; + for uidb in tpk.userids() { + if Email::try_from(uidb.userid()).unwrap() == email { + found = true; + break; } - if ! found { - return Err(format_err!( - "{:?} points to the wrong Cert, the Cert does not \ - contain the email {}", path, email)); - } - Ok(()) + } + if !found { + return Err(format_err!( + "{:?} points to the wrong Cert, the Cert does not \ + contain the email {}", + path, + email + )); + } + Ok(()) })?; Ok(()) @@ -754,7 +786,13 @@ fn path_split(path: &str) -> PathBuf { } fn path_merge(path: &Path) -> String { - let comps = path.iter().rev().take(3).collect::>().into_iter().rev(); + let comps = path + .iter() + .rev() + .take(3) + .collect::>() + .into_iter() + .rev(); let comps: Vec<_> = comps.map(|os| os.to_string_lossy()).collect(); comps.join("") } @@ -762,9 +800,9 @@ fn path_merge(path: &Path) -> String { #[cfg(test)] mod tests { use super::*; - use test; use openpgp::cert::CertBuilder; use tempfile::TempDir; + use test; #[test] fn init() { @@ -783,18 +821,48 @@ mod tests { #[test] fn new() { let (_tmp_dir, db, _log_path) = open_db(); - let k1 = CertBuilder::new().add_userid("a@invalid.example.org") - .generate().unwrap().0; - let k2 = CertBuilder::new().add_userid("b@invalid.example.org") - .generate().unwrap().0; - let k3 = CertBuilder::new().add_userid("c@invalid.example.org") - .generate().unwrap().0; + let k1 = CertBuilder::new() + .add_userid("a@invalid.example.org") + .generate() + .unwrap() + .0; + let k2 = CertBuilder::new() + .add_userid("b@invalid.example.org") + .generate() + .unwrap() + .0; + let k3 = CertBuilder::new() + .add_userid("c@invalid.example.org") + .generate() + .unwrap() + .0; assert!(db.merge(k1).unwrap().into_tpk_status().email_status.len() > 0); - assert!(db.merge(k2.clone()).unwrap().into_tpk_status().email_status.len() > 0); + assert!( + db.merge(k2.clone()) + .unwrap() + .into_tpk_status() + .email_status + .len() + > 0 + ); assert!(!db.merge(k2).unwrap().into_tpk_status().email_status.len() > 0); - assert!(db.merge(k3.clone()).unwrap().into_tpk_status().email_status.len() > 0); - assert!(!db.merge(k3.clone()).unwrap().into_tpk_status().email_status.len() > 0); + assert!( + db.merge(k3.clone()) + .unwrap() + .into_tpk_status() + .email_status + .len() + > 0 + ); + assert!( + !db.merge(k3.clone()) + .unwrap() + .into_tpk_status() + .email_status + .len() + > 0 + ); assert!(!db.merge(k3).unwrap().into_tpk_status().email_status.len() > 0); } @@ -915,11 +983,12 @@ mod tests { let tmpdir = TempDir::new().unwrap(); let db = Filesystem::new_from_base(tmpdir.path()).unwrap(); - let fp: Fingerprint = - "CBCD8F030588653EEDD7E2659B7DD433F254904A".parse().unwrap(); + let fp: Fingerprint = "CBCD8F030588653EEDD7E2659B7DD433F254904A".parse().unwrap(); - assert_eq!(Filesystem::path_to_fingerprint(&db.link_by_fingerprint(&fp)), - Some(fp.clone())); + assert_eq!( + Filesystem::path_to_fingerprint(&db.link_by_fingerprint(&fp)), + Some(fp.clone()) + ); db.check_consistency().expect("inconsistent database"); } diff --git a/database/src/lib.rs b/database/src/lib.rs index bca1514..212a204 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -14,6 +14,8 @@ extern crate fs2; extern crate idna; #[macro_use] extern crate log; +extern crate chrono; +extern crate hex; extern crate pathdiff; extern crate rand; extern crate serde; @@ -21,24 +23,17 @@ extern crate serde_json; extern crate tempfile; extern crate time; extern crate url; -extern crate hex; extern crate walkdir; -extern crate chrono; extern crate zbase32; extern crate sequoia_openpgp as openpgp; -use openpgp::{ - Cert, - packet::UserID, - parse::Parse, - types::KeyFlags, -}; +use openpgp::{packet::UserID, parse::Parse, types::KeyFlags, Cert}; pub mod types; use types::{Email, Fingerprint, KeyID}; -pub mod wkd; pub mod sync; +pub mod wkd; mod fs; pub use self::fs::Filesystem as KeyDatabase; @@ -47,7 +42,7 @@ mod stateful_tokens; pub use stateful_tokens::StatefulTokens; mod openpgp_utils; -use openpgp_utils::{tpk_filter_alive_emails, tpk_to_string, tpk_clean, is_status_revoked, POLICY}; +use openpgp_utils::{is_status_revoked, tpk_clean, tpk_filter_alive_emails, tpk_to_string, POLICY}; #[cfg(test)] mod test; @@ -74,8 +69,8 @@ impl FromStr for Query { fn from_str(term: &str) -> Result { use self::Query::*; - let looks_like_short_key_id = !term.contains('@') && - (term.starts_with("0x") && term.len() < 16 || term.len() == 8); + let looks_like_short_key_id = + !term.contains('@') && (term.starts_with("0x") && term.len() < 16 || term.len() == 8); if looks_like_short_key_id { Ok(InvalidShort()) } else if let Ok(fp) = Fingerprint::from_str(term) { @@ -90,7 +85,7 @@ impl FromStr for Query { } } -#[derive(Debug,PartialEq,Eq,PartialOrd,Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum EmailAddressStatus { Published, NotPublished, @@ -113,10 +108,10 @@ impl ImportResult { } } -#[derive(Debug,PartialEq)] +#[derive(Debug, PartialEq)] pub struct TpkStatus { pub is_revoked: bool, - pub email_status: Vec<(Email,EmailAddressStatus)>, + pub email_status: Vec<(Email, EmailAddressStatus)>, pub unparsed_uids: usize, } @@ -151,7 +146,11 @@ pub trait Database: Sync + Send { fn by_email_wkd(&self, email: &Email) -> Option>; fn by_domain_and_hash_wkd(&self, domain: &str, hash: &str) -> Option>; - fn check_link_fpr(&self, fpr: &Fingerprint, target: &Fingerprint) -> Result>; + fn check_link_fpr( + &self, + fpr: &Fingerprint, + target: &Fingerprint, + ) -> Result>; fn by_fpr_full(&self, fpr: &Fingerprint) -> Option; fn by_primary_fpr(&self, fpr: &Fingerprint) -> Option; @@ -159,7 +158,11 @@ pub trait Database: Sync + Send { fn write_to_temp(&self, content: &[u8]) -> Result; fn move_tmp_to_full(&self, content: Self::TempCert, fpr: &Fingerprint) -> Result<()>; fn move_tmp_to_published(&self, content: Self::TempCert, fpr: &Fingerprint) -> Result<()>; - fn move_tmp_to_published_wkd(&self, content: Option, fpr: &Fingerprint) -> Result<()>; + fn move_tmp_to_published_wkd( + &self, + content: Option, + fpr: &Fingerprint, + ) -> Result<()>; fn write_to_quarantine(&self, fpr: &Fingerprint, content: &[u8]) -> Result<()>; fn write_log_append(&self, filename: &str, fpr_primary: &Fingerprint) -> Result<()>; @@ -204,7 +207,8 @@ pub trait Database: Sync + Send { .map(|binding| binding.userid().clone()) .collect(); - let full_tpk_old = self.by_fpr_full(&fpr_primary) + let full_tpk_old = self + .by_fpr_full(&fpr_primary) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok()); let is_update = full_tpk_old.is_some(); let (full_tpk_new, full_tpk_unchanged) = if let Some(full_tpk_old) = full_tpk_old { @@ -217,9 +221,9 @@ pub trait Database: Sync + Send { let is_revoked = is_status_revoked(full_tpk_new.revocation_status(&POLICY, None)); - let is_ok = is_revoked || - full_tpk_new.keys().subkeys().next().is_some() || - full_tpk_new.userids().next().is_some(); + let is_ok = is_revoked + || full_tpk_new.keys().subkeys().next().is_some() + || full_tpk_new.userids().next().is_some(); if !is_ok { // self.write_to_quarantine(&fpr_primary, &tpk_to_string(&full_tpk_new)?)?; return Err(anyhow!("Not a well-formed key!")); @@ -228,7 +232,10 @@ pub trait Database: Sync + Send { let published_tpk_old = self .by_fpr(&fpr_primary) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok()); - let published_emails = published_tpk_old.as_ref().map(tpk_get_emails).unwrap_or_default(); + let published_emails = published_tpk_old + .as_ref() + .map(tpk_get_emails) + .unwrap_or_default(); let unparsed_uids = full_tpk_new .userids() @@ -246,7 +253,9 @@ pub trait Database: Sync + Send { } }) .flatten() - .filter(|(binding, email)| known_uids.contains(binding.userid()) || published_emails.contains(email)) + .filter(|(binding, email)| { + known_uids.contains(binding.userid()) || published_emails.contains(email) + }) .flat_map(|(binding, email)| { if is_status_revoked(binding.revocation_status(&POLICY, None)) { Some((email, EmailAddressStatus::Revoked)) @@ -264,7 +273,11 @@ pub trait Database: Sync + Send { // Abort if no changes were made if full_tpk_unchanged { - return Ok(ImportResult::Unchanged(TpkStatus { is_revoked, email_status, unparsed_uids })); + return Ok(ImportResult::Unchanged(TpkStatus { + is_revoked, + email_status, + unparsed_uids, + })); } let published_tpk_new = if is_revoked { @@ -284,7 +297,8 @@ pub trait Database: Sync + Send { .flatten() .any(|unrevoked_email| &unrevoked_email == *email); !has_unrevoked_userid - }).collect(); + }) + .collect(); let fingerprints = tpk_get_linkable_fprs(&published_tpk_new); @@ -321,22 +335,31 @@ pub trait Database: Sync + Send { for fpr in fpr_not_linked { if let Err(e) = self.link_fpr(&fpr, &fpr_primary) { - info!("Error ensuring symlink! {} {} {:?}", - &fpr, &fpr_primary, e); + info!("Error ensuring symlink! {} {} {:?}", &fpr, &fpr_primary, e); } } for revoked_email in newly_revoked_emails { if let Err(e) = self.unlink_email(revoked_email, &fpr_primary) { - info!("Error ensuring symlink! {} {} {:?}", - &fpr_primary, &revoked_email, e); + info!( + "Error ensuring symlink! {} {} {:?}", + &fpr_primary, &revoked_email, e + ); } } if is_update { - Ok(ImportResult::Updated(TpkStatus { is_revoked, email_status, unparsed_uids })) + Ok(ImportResult::Updated(TpkStatus { + is_revoked, + email_status, + unparsed_uids, + })) } else { - Ok(ImportResult::New(TpkStatus { is_revoked, email_status, unparsed_uids })) + Ok(ImportResult::New(TpkStatus { + is_revoked, + email_status, + unparsed_uids, + })) } } @@ -352,8 +375,13 @@ pub trait Database: Sync + Send { Utc::now().format("%Y-%m-%d").to_string() } - fn get_tpk_status(&self, fpr_primary: &Fingerprint, known_addresses: &[Email]) -> Result { - let tpk_full = self.by_fpr_full(fpr_primary) + fn get_tpk_status( + &self, + fpr_primary: &Fingerprint, + known_addresses: &[Email], + ) -> Result { + let tpk_full = self + .by_fpr_full(fpr_primary) .ok_or_else(|| anyhow!("Key not in database!")) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()))?; @@ -368,10 +396,12 @@ pub trait Database: Sync + Send { let published_uids: Vec = self .by_fpr(fpr_primary) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok()) - .map(|tpk| tpk.userids() - .map(|binding| binding.userid().clone()) - .collect() - ).unwrap_or_default(); + .map(|tpk| { + tpk.userids() + .map(|binding| binding.userid().clone()) + .collect() + }) + .unwrap_or_default(); let mut email_status: Vec<_> = tpk_full .userids() @@ -397,7 +427,11 @@ pub trait Database: Sync + Send { // the same address, we keep the first. email_status.dedup_by(|(e1, _), (e2, _)| e1 == e2); - Ok(TpkStatus { is_revoked, email_status, unparsed_uids }) + Ok(TpkStatus { + is_revoked, + email_status, + unparsed_uids, + }) } /// Complex operation that publishes some user id for a Cert already in the database. @@ -418,18 +452,22 @@ pub trait Database: Sync + Send { self.nolock_unlink_email_if_other(fpr_primary, email_new)?; - let full_tpk = self.by_fpr_full(fpr_primary) + let full_tpk = self + .by_fpr_full(fpr_primary) .ok_or_else(|| anyhow!("Key not in database!")) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()))?; let published_uids_old: Vec = self .by_fpr(fpr_primary) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok()) - .map(|tpk| tpk.userids() - .map(|binding| binding.userid().clone()) - .collect() - ).unwrap_or_default(); - let published_emails_old: Vec = published_uids_old.iter() + .map(|tpk| { + tpk.userids() + .map(|binding| binding.userid().clone()) + .collect() + }) + .unwrap_or_default(); + let published_emails_old: Vec = published_uids_old + .iter() .map(|uid| Email::try_from(uid).ok()) .flatten() .collect(); @@ -449,8 +487,9 @@ pub trait Database: Sync + Send { .userids() .map(|binding| Email::try_from(binding.userid())) .flatten() - .any(|email| email == *email_new) { - return Err(anyhow!("Requested UserID not found!")); + .any(|email| email == *email_new) + { + return Err(anyhow!("Requested UserID not found!")); } let published_tpk_clean = tpk_clean(&published_tpk_new)?; @@ -462,8 +501,10 @@ pub trait Database: Sync + Send { self.update_write_log(fpr_primary); if let Err(e) = self.link_email(email_new, fpr_primary) { - info!("Error ensuring email symlink! {} -> {} {:?}", - &email_new, &fpr_primary, e); + info!( + "Error ensuring email symlink! {} -> {} {:?}", + &email_new, &fpr_primary, e + ); } Ok(()) @@ -474,13 +515,15 @@ pub trait Database: Sync + Send { fpr_primary: &Fingerprint, unlink_email: &Email, ) -> Result<()> { - let current_link_fpr = self.lookup_primary_fingerprint( - &Query::ByEmail(unlink_email.clone())); + let current_link_fpr = + self.lookup_primary_fingerprint(&Query::ByEmail(unlink_email.clone())); if let Some(current_fpr) = current_link_fpr { if current_fpr != *fpr_primary { - self.nolock_set_email_unpublished_filter(¤t_fpr, - |uid| Email::try_from(uid).map(|email| email != *unlink_email) - .unwrap_or(false))?; + self.nolock_set_email_unpublished_filter(¤t_fpr, |uid| { + Email::try_from(uid) + .map(|email| email != *unlink_email) + .unwrap_or(false) + })?; } } Ok(()) @@ -513,7 +556,8 @@ pub trait Database: Sync + Send { fpr_primary: &Fingerprint, email_remove: impl Fn(&UserID) -> bool, ) -> Result<()> { - let published_tpk_old = self.by_fpr(fpr_primary) + let published_tpk_old = self + .by_fpr(fpr_primary) .ok_or_else(|| anyhow!("Key not in database!")) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()))?; @@ -523,8 +567,7 @@ pub trait Database: Sync + Send { .flatten() .collect(); - let published_tpk_new = published_tpk_old.retain_userids( - |uid| email_remove(uid.userid())); + let published_tpk_new = published_tpk_old.retain_userids(|uid| email_remove(uid.userid())); let published_emails_new: Vec = published_tpk_new .userids() @@ -546,37 +589,31 @@ pub trait Database: Sync + Send { for unpublished_email in unpublished_emails { if let Err(e) = self.unlink_email(unpublished_email, fpr_primary) { - info!("Error deleting email symlink! {} -> {} {:?}", - &unpublished_email, &fpr_primary, e); + info!( + "Error deleting email symlink! {} -> {} {:?}", + &unpublished_email, &fpr_primary, e + ); } } Ok(()) } - fn set_email_unpublished( - &self, - fpr_primary: &Fingerprint, - email_remove: &Email, - ) -> Result<()> { - self.set_email_unpublished_filter(fpr_primary, |uid| + fn set_email_unpublished(&self, fpr_primary: &Fingerprint, email_remove: &Email) -> Result<()> { + self.set_email_unpublished_filter(fpr_primary, |uid| { Email::try_from(uid) .map(|email| email != *email_remove) - .unwrap_or(false)) + .unwrap_or(false) + }) } - fn set_email_unpublished_all( - &self, - fpr_primary: &Fingerprint, - ) -> Result<()> { + fn set_email_unpublished_all(&self, fpr_primary: &Fingerprint) -> Result<()> { self.set_email_unpublished_filter(fpr_primary, |_| false) } - fn regenerate_links( - &self, - fpr_primary: &Fingerprint, - ) -> Result { - let tpk = self.by_primary_fpr(fpr_primary) + fn regenerate_links(&self, fpr_primary: &Fingerprint) -> Result { + let tpk = self + .by_primary_fpr(fpr_primary) .and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok()) .ok_or_else(|| anyhow!("Key not in database!"))?; @@ -619,11 +656,7 @@ pub trait Database: Sync + Send { } } - fn regenerate_wkd( - &self, - fpr_primary: &Fingerprint, - published_tpk: &Cert - ) -> Result<()> { + fn regenerate_wkd(&self, fpr_primary: &Fingerprint, published_tpk: &Cert) -> Result<()> { let published_wkd_tpk_tmp = if published_tpk.userids().next().is_some() { Some(self.write_to_temp(&published_tpk.export_to_vec()?)?) } else { @@ -636,30 +669,33 @@ pub trait Database: Sync + Send { } fn tpk_get_emails(cert: &Cert) -> Vec { - cert - .userids() + cert.userids() .map(|binding| Email::try_from(binding.userid())) .flatten() .collect() } pub fn tpk_get_linkable_fprs(tpk: &Cert) -> Vec { - let signing_capable = &KeyFlags::empty() - .set_signing() - .set_certification(); + let signing_capable = &KeyFlags::empty().set_signing().set_certification(); let fpr_primary = &Fingerprint::try_from(tpk.fingerprint()).unwrap(); - tpk - .keys() - .into_iter() - .flat_map(|bundle| { - Fingerprint::try_from(bundle.key().fingerprint()) - .map(|fpr| (fpr, bundle.binding_signature(&POLICY, None).ok().and_then(|sig| sig.key_flags()))) + tpk.keys() + .into_iter() + .flat_map(|bundle| { + Fingerprint::try_from(bundle.key().fingerprint()).map(|fpr| { + ( + fpr, + bundle + .binding_signature(&POLICY, None) + .ok() + .and_then(|sig| sig.key_flags()), + ) }) - .filter(|(fpr, flags)| { - fpr == fpr_primary || - flags.is_none() || - !(signing_capable & flags.as_ref().unwrap()).is_empty() - }) - .map(|(fpr,_)| fpr) - .collect() + }) + .filter(|(fpr, flags)| { + fpr == fpr_primary + || flags.is_none() + || !(signing_capable & flags.as_ref().unwrap()).is_empty() + }) + .map(|(fpr, _)| fpr) + .collect() } diff --git a/database/src/openpgp_utils.rs b/database/src/openpgp_utils.rs index cbaba90..0a2bf83 100644 --- a/database/src/openpgp_utils.rs +++ b/database/src/openpgp_utils.rs @@ -2,11 +2,8 @@ use openpgp::Result; use std::convert::TryFrom; use openpgp::{ - Cert, - types::RevocationStatus, - cert::prelude::*, - serialize::SerializeInto as _, - policy::StandardPolicy, + cert::prelude::*, policy::StandardPolicy, serialize::SerializeInto as _, + types::RevocationStatus, Cert, }; use Email; @@ -33,24 +30,42 @@ pub fn tpk_clean(tpk: &Cert) -> Result { // The primary key and related signatures. let pk_bundle = tpk.primary_key().bundle(); acc.push(pk_bundle.key().clone().into()); - for s in pk_bundle.self_signatures() { acc.push(s.clone().into()) } - for s in pk_bundle.self_revocations() { acc.push(s.clone().into()) } - for s in pk_bundle.other_revocations() { acc.push(s.clone().into()) } + for s in pk_bundle.self_signatures() { + acc.push(s.clone().into()) + } + for s in pk_bundle.self_revocations() { + acc.push(s.clone().into()) + } + for s in pk_bundle.other_revocations() { + acc.push(s.clone().into()) + } // The subkeys and related signatures. for skb in tpk.keys().subkeys() { acc.push(skb.key().clone().into()); - for s in skb.self_signatures() { acc.push(s.clone().into()) } - for s in skb.self_revocations() { acc.push(s.clone().into()) } - for s in skb.other_revocations() { acc.push(s.clone().into()) } + for s in skb.self_signatures() { + acc.push(s.clone().into()) + } + for s in skb.self_revocations() { + acc.push(s.clone().into()) + } + for s in skb.other_revocations() { + acc.push(s.clone().into()) + } } // The UserIDs. for uidb in tpk.userids() { acc.push(uidb.userid().clone().into()); - for s in uidb.self_signatures() { acc.push(s.clone().into()) } - for s in uidb.self_revocations() { acc.push(s.clone().into()) } - for s in uidb.other_revocations() { acc.push(s.clone().into()) } + for s in uidb.self_signatures() { + acc.push(s.clone().into()) + } + for s in uidb.self_revocations() { + acc.push(s.clone().into()) + } + for s in uidb.other_revocations() { + acc.push(s.clone().into()) + } // Reasoning about the currently attested certifications // requires a policy. diff --git a/database/src/stateful_tokens.rs b/database/src/stateful_tokens.rs index 15c27a5..cb372d9 100644 --- a/database/src/stateful_tokens.rs +++ b/database/src/stateful_tokens.rs @@ -1,6 +1,6 @@ -use std::io::{Read,Write}; -use std::path::PathBuf; use std::fs::{create_dir_all, remove_file, File}; +use std::io::{Read, Write}; +use std::path::PathBuf; use std::str; diff --git a/database/src/test.rs b/database/src/test.rs index e337860..c54c5eb 100644 --- a/database/src/test.rs +++ b/database/src/test.rs @@ -14,29 +14,28 @@ // confirm again // fetch by uid & fpr +use anyhow::Result; use std::convert::{TryFrom, TryInto}; use std::str::FromStr; -use anyhow::Result; +use openpgp::cert::{CertBuilder, UserIDRevocationBuilder}; +use openpgp::types::{KeyFlags, ReasonForRevocation, SignatureType}; +use openpgp::{ + packet::{signature::*, UserID}, + parse::Parse, + types::RevocationStatus, + Cert, Packet, +}; +use std::fs; +use std::path::Path; +use types::{Email, Fingerprint, KeyID}; use Database; use Query; -use openpgp::cert::{CertBuilder, UserIDRevocationBuilder}; -use openpgp::{ - packet::{ - UserID, - signature::*, - }, - parse::Parse, Packet, types::RevocationStatus, Cert, -}; -use openpgp::types::{ReasonForRevocation, SignatureType, KeyFlags}; -use types::{Email, Fingerprint, KeyID}; -use std::path::Path; -use std::fs; use openpgp_utils::POLICY; -use TpkStatus; use EmailAddressStatus; +use TpkStatus; fn check_mail_none(db: &impl Database, email: &Email) { assert!(db.by_email(email).is_none()); @@ -67,14 +66,17 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) { let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::NotPublished), - (email2.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1.clone(), EmailAddressStatus::NotPublished), + (email2.clone(), EmailAddressStatus::NotPublished), + ), + unparsed_uids: 0, + }, + tpk_status + ); { // fetch by fpr @@ -105,9 +107,7 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) { let uid = key.userids().next().unwrap().userid().clone(); assert!((uid == uid1) ^ (uid == uid2)); - let email = - Email::from_str(&String::from_utf8(uid.value().to_vec()).unwrap()) - .unwrap(); + let email = Email::from_str(&String::from_utf8(uid.value().to_vec()).unwrap()).unwrap(); assert_eq!(db.by_email(&email).unwrap(), raw); if email1 == email { @@ -120,7 +120,8 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) { } // this operation is idempotent - let's try again! - db.set_email_published(&fpr, &tpk_status.email_status[0].0).unwrap(); + db.set_email_published(&fpr, &tpk_status.email_status[0].0) + .unwrap(); { // fetch by fpr @@ -134,9 +135,7 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) { let uid = key.userids().next().unwrap().userid().clone(); assert!((uid == uid1) ^ (uid == uid2)); - let email = - Email::from_str(&String::from_utf8(uid.value().to_vec()).unwrap()) - .unwrap(); + let email = Email::from_str(&String::from_utf8(uid.value().to_vec()).unwrap()).unwrap(); assert_eq!(db.by_email(&email).unwrap(), raw); if email1 == email { @@ -149,7 +148,8 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) { } // verify 2nd uid - db.set_email_published(&fpr, &tpk_status.email_status[1].0).unwrap(); + db.set_email_published(&fpr, &tpk_status.email_status[1].0) + .unwrap(); { // fetch by fpr @@ -165,36 +165,39 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) { assert_eq!(db.by_email(&email1).unwrap(), raw); assert_eq!(db.by_email(&email2).unwrap(), raw); - assert!( - ((myuid1 == uid1) & (myuid2 == uid2)) - ^ ((myuid1 == uid2) & (myuid2 == uid1)) - ); + assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1))); } let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::Published), - (email2.clone(), EmailAddressStatus::Published), - ), - unparsed_uids: 0, - }, tpk_status); - - // publish w/ one uid less - { - let short_tpk = cert_without_uid(tpk, &uid1); - - let tpk_status = db.merge(short_tpk).unwrap().into_tpk_status(); - assert_eq!(TpkStatus { + assert_eq!( + TpkStatus { is_revoked: false, email_status: vec!( (email1.clone(), EmailAddressStatus::Published), (email2.clone(), EmailAddressStatus::Published), ), unparsed_uids: 0, - }, tpk_status); + }, + tpk_status + ); + + // publish w/ one uid less + { + let short_tpk = cert_without_uid(tpk, &uid1); + + let tpk_status = db.merge(short_tpk).unwrap().into_tpk_status(); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1.clone(), EmailAddressStatus::Published), + (email2.clone(), EmailAddressStatus::Published), + ), + unparsed_uids: 0, + }, + tpk_status + ); // fetch by fpr let raw = db.by_fpr(&fpr).unwrap(); @@ -209,10 +212,7 @@ pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) { assert_eq!(db.by_email(&email1).unwrap(), raw); assert_eq!(db.by_email(&email2).unwrap(), raw); - assert!( - ((myuid1 == uid1) & (myuid2 == uid2)) - ^ ((myuid1 == uid2) & (myuid2 == uid1)) - ); + assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1))); } // publish w/one uid more @@ -286,16 +286,20 @@ pub fn test_regenerate(db: &mut impl Database, log_path: &Path) { .0; let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap(); let email1 = Email::from_str(str_uid1).unwrap(); - let fpr_sign: Fingerprint = tpk.keys() + let fpr_sign: Fingerprint = tpk + .keys() .with_policy(&POLICY, None) .for_signing() .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap()) - .next().unwrap(); - let fpr_encrypt: Fingerprint = tpk.keys() + .next() + .unwrap(); + let fpr_encrypt: Fingerprint = tpk + .keys() .with_policy(&POLICY, None) .key_flags(KeyFlags::empty().set_transport_encryption()) .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap()) - .next().unwrap(); + .next() + .unwrap(); // upload key db.merge(tpk).unwrap().into_tpk_status(); @@ -349,23 +353,34 @@ pub fn test_reupload(db: &mut impl Database, log_path: &Path) { // reupload let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::Published), - (email2.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1.clone(), EmailAddressStatus::Published), + (email2.clone(), EmailAddressStatus::NotPublished), + ), + unparsed_uids: 0, + }, + tpk_status + ); assert!(db.by_email(&email2).is_none() ^ db.by_email(&email1).is_none()); } pub fn test_uid_replacement(db: &mut impl Database, log_path: &Path) { let str_uid1 = "Test A "; - let tpk1 = CertBuilder::new().add_userid(str_uid1).generate().unwrap().0; + let tpk1 = CertBuilder::new() + .add_userid(str_uid1) + .generate() + .unwrap() + .0; let fpr1 = Fingerprint::try_from(tpk1.fingerprint()).unwrap(); - let tpk2 = CertBuilder::new().add_userid(str_uid1).generate().unwrap().0; + let tpk2 = CertBuilder::new() + .add_userid(str_uid1) + .generate() + .unwrap() + .0; let fpr2 = Fingerprint::try_from(tpk2.fingerprint()).unwrap(); let pgp_fpr1 = tpk1.fingerprint(); @@ -382,24 +397,52 @@ pub fn test_uid_replacement(db: &mut impl Database, log_path: &Path) { // verify 1st uid db.set_email_published(&fpr1, &email1).unwrap(); check_mail_some(db, &email1); - assert_eq!(Cert::from_bytes(db.by_email(&email1).unwrap().as_bytes()).unwrap() - .fingerprint(), pgp_fpr1); + assert_eq!( + Cert::from_bytes(db.by_email(&email1).unwrap().as_bytes()) + .unwrap() + .fingerprint(), + pgp_fpr1 + ); - assert_eq!(Cert::from_bytes(db.by_fpr(&fpr1).unwrap().as_bytes()).unwrap() - .userids().len(), 1); - assert_eq!(Cert::from_bytes(db.by_fpr(&fpr2).unwrap().as_bytes()).unwrap() - .userids().len(), 0); + assert_eq!( + Cert::from_bytes(db.by_fpr(&fpr1).unwrap().as_bytes()) + .unwrap() + .userids() + .len(), + 1 + ); + assert_eq!( + Cert::from_bytes(db.by_fpr(&fpr2).unwrap().as_bytes()) + .unwrap() + .userids() + .len(), + 0 + ); // verify uid on other key db.set_email_published(&fpr2, &email1).unwrap(); check_mail_some(db, &email1); - assert_eq!(Cert::from_bytes(db.by_email(&email1).unwrap().as_bytes()).unwrap() - .fingerprint(), pgp_fpr2); + assert_eq!( + Cert::from_bytes(db.by_email(&email1).unwrap().as_bytes()) + .unwrap() + .fingerprint(), + pgp_fpr2 + ); - assert_eq!(Cert::from_bytes(db.by_fpr(&fpr1).unwrap().as_bytes()).unwrap() - .userids().len(), 0); - assert_eq!(Cert::from_bytes(db.by_fpr(&fpr2).unwrap().as_bytes()).unwrap() - .userids().len(), 1); + assert_eq!( + Cert::from_bytes(db.by_fpr(&fpr1).unwrap().as_bytes()) + .unwrap() + .userids() + .len(), + 0 + ); + assert_eq!( + Cert::from_bytes(db.by_fpr(&fpr2).unwrap().as_bytes()) + .unwrap() + .userids() + .len(), + 1 + ); } pub fn test_uid_deletion(db: &mut impl Database, log_path: &Path) { @@ -421,14 +464,17 @@ pub fn test_uid_deletion(db: &mut impl Database, log_path: &Path) { // upload key and verify uids let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::NotPublished), - (email2.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1.clone(), EmailAddressStatus::NotPublished), + (email2.clone(), EmailAddressStatus::NotPublished), + ), + unparsed_uids: 0, + }, + tpk_status + ); db.set_email_published(&fpr, &email1).unwrap(); db.set_email_published(&fpr, &email2).unwrap(); @@ -455,9 +501,10 @@ pub fn test_uid_deletion(db: &mut impl Database, log_path: &Path) { // Check that the second is still there, and that the Cert is // otherwise intact. - let tpk = - db.lookup(&Query::ByFingerprint(tpk.fingerprint().try_into().unwrap())) - .unwrap().unwrap(); + let tpk = db + .lookup(&Query::ByFingerprint(tpk.fingerprint().try_into().unwrap())) + .unwrap() + .unwrap(); assert_eq!(tpk.userids().count(), 0); assert_eq!(tpk.keys().subkeys().count(), n_subkeys); } @@ -475,19 +522,27 @@ pub fn test_subkey_lookup(db: &mut impl Database, _log_path: &Path) { let _ = db.merge(tpk.clone()).unwrap().into_tpk_status(); let fpr_primray = Fingerprint::try_from(tpk.fingerprint()).unwrap(); - let fpr_sign: Fingerprint = tpk.keys() + let fpr_sign: Fingerprint = tpk + .keys() .with_policy(&POLICY, None) .for_signing() .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap()) - .next().unwrap(); - let fpr_encrypt: Fingerprint = tpk.keys() + .next() + .unwrap(); + let fpr_encrypt: Fingerprint = tpk + .keys() .with_policy(&POLICY, None) .key_flags(KeyFlags::empty().set_transport_encryption()) .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap()) - .next().unwrap(); + .next() + .unwrap(); - let raw1 = db.by_fpr(&fpr_primray).expect("primary fpr must be linked!"); - let raw2 = db.by_fpr(&fpr_sign).expect("signing subkey fpr must be linked!"); + let raw1 = db + .by_fpr(&fpr_primray) + .expect("primary fpr must be linked!"); + let raw2 = db + .by_fpr(&fpr_sign) + .expect("signing subkey fpr must be linked!"); // encryption subkey key id must not be linked! assert!(db.by_fpr(&fpr_encrypt).is_none()); @@ -506,19 +561,27 @@ pub fn test_kid_lookup(db: &mut impl Database, _log_path: &Path) { // upload key let _ = db.merge(tpk.clone()).unwrap().into_tpk_status(); let kid_primray = KeyID::try_from(tpk.fingerprint()).unwrap(); - let kid_sign: KeyID = tpk.keys() + let kid_sign: KeyID = tpk + .keys() .with_policy(&POLICY, None) .for_signing() .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap()) - .next().unwrap(); - let kid_encrypt: KeyID = tpk.keys() + .next() + .unwrap(); + let kid_encrypt: KeyID = tpk + .keys() .with_policy(&POLICY, None) .key_flags(KeyFlags::empty().set_transport_encryption()) .map(|amalgamation| amalgamation.key().fingerprint().try_into().unwrap()) - .next().unwrap(); + .next() + .unwrap(); - let raw1 = db.by_kid(&kid_primray).expect("primary key id must be linked!"); - let raw2 = db.by_kid(&kid_sign).expect("signing subkey key id must be linked!"); + let raw1 = db + .by_kid(&kid_primray) + .expect("primary key id must be linked!"); + let raw2 = db + .by_kid(&kid_sign) + .expect("signing subkey key id must be linked!"); // encryption subkey key id must not be linked! assert!(db.by_kid(&kid_encrypt).is_none()); @@ -554,14 +617,17 @@ pub fn test_upload_revoked_tpk(db: &mut impl Database, log_path: &Path) { // upload key let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: true, - email_status: vec!( - (email1.clone(), EmailAddressStatus::NotPublished), - (email2.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: true, + email_status: vec!( + (email1.clone(), EmailAddressStatus::NotPublished), + (email2.clone(), EmailAddressStatus::NotPublished), + ), + unparsed_uids: 0, + }, + tpk_status + ); check_mail_none(db, &email1); check_mail_none(db, &email2); @@ -586,18 +652,23 @@ pub fn test_uid_revocation(db: &mut impl Database, log_path: &Path) { // upload key let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::NotPublished), - (email2.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1.clone(), EmailAddressStatus::NotPublished), + (email2.clone(), EmailAddressStatus::NotPublished), + ), + unparsed_uids: 0, + }, + tpk_status + ); // verify uid - db.set_email_published(&fpr, &tpk_status.email_status[0].0).unwrap(); - db.set_email_published(&fpr, &tpk_status.email_status[1].0).unwrap(); + db.set_email_published(&fpr, &tpk_status.email_status[0].0) + .unwrap(); + db.set_email_published(&fpr, &tpk_status.email_status[1].0) + .unwrap(); // fetch both uids check_mail_some(db, &email1); @@ -608,30 +679,45 @@ pub fn test_uid_revocation(db: &mut impl Database, log_path: &Path) { // revoke one uid let sig = { let policy = &POLICY; - let uid = tpk.userids() + let uid = tpk + .userids() .with_policy(policy, None) - .find(|b| *b.userid() == uid2).unwrap(); - assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revocation_status(&POLICY, None)); + .find(|b| *b.userid() == uid2) + .unwrap(); + assert_eq!( + RevocationStatus::NotAsFarAsWeKnow, + uid.revocation_status(&POLICY, None) + ); - let mut keypair = tpk.primary_key().bundle().key().clone() - .parts_into_secret().unwrap() - .into_keypair().unwrap(); + let mut keypair = tpk + .primary_key() + .bundle() + .key() + .clone() + .parts_into_secret() + .unwrap() + .into_keypair() + .unwrap(); UserIDRevocationBuilder::new() - .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/").unwrap() + .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/") + .unwrap() .build(&mut keypair, &tpk, uid.userid(), None) .unwrap() }; assert_eq!(sig.typ(), SignatureType::CertificationRevocation); let tpk = tpk.insert_packets(sig).unwrap(); let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::Published), - (email2.clone(), EmailAddressStatus::Revoked), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1.clone(), EmailAddressStatus::Published), + (email2.clone(), EmailAddressStatus::Revoked), + ), + unparsed_uids: 0, + }, + tpk_status + ); // Fail to fetch by the revoked uid, ok by the non-revoked one. check_mail_some(db, &email1); @@ -735,17 +821,32 @@ pub fn test_unlink_uid(db: &mut impl Database, log_path: &Path) { let fpr_evil = Fingerprint::try_from(tpk_evil.fingerprint()).unwrap(); let sig = { let policy = &POLICY; - let uid = tpk_evil.userids() + let uid = tpk_evil + .userids() .with_policy(policy, None) - .find(|b| b.userid().value() == uid.as_bytes()).unwrap(); - assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revocation_status(&POLICY, None)); + .find(|b| b.userid().value() == uid.as_bytes()) + .unwrap(); + assert_eq!( + RevocationStatus::NotAsFarAsWeKnow, + uid.revocation_status(&POLICY, None) + ); - let mut keypair = tpk_evil.primary_key().bundle().key().clone() - .parts_into_secret().unwrap() - .into_keypair().unwrap(); + let mut keypair = tpk_evil + .primary_key() + .bundle() + .key() + .clone() + .parts_into_secret() + .unwrap() + .into_keypair() + .unwrap(); UserIDRevocationBuilder::new() - .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"I just had to quit, I couldn't bear it any longer").unwrap() + .set_reason_for_revocation( + ReasonForRevocation::UIDRetired, + b"I just had to quit, I couldn't bear it any longer", + ) + .unwrap() .build(&mut keypair, &tpk_evil, uid.userid(), None) .unwrap() }; @@ -753,24 +854,31 @@ pub fn test_unlink_uid(db: &mut impl Database, log_path: &Path) { let tpk_evil = tpk_evil.insert_packets(sig).unwrap(); let tpk_status = db.merge(tpk_evil).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr_evil); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email.clone(), EmailAddressStatus::Revoked), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email.clone(), EmailAddressStatus::Revoked),), + unparsed_uids: 0, + }, + tpk_status + ); // Check that when looking up by email, we still get the former // Cert. assert_eq!( - db.lookup(&Query::ByEmail(email)).unwrap().unwrap().fingerprint(), - tpk.fingerprint()); + db.lookup(&Query::ByEmail(email)) + .unwrap() + .unwrap() + .fingerprint(), + tpk.fingerprint() + ); } pub fn get_userids(armored: &str) -> Vec { let tpk = Cert::from_bytes(armored.as_bytes()).unwrap(); - tpk.userids().map(|binding| binding.userid().clone()).collect() + tpk.userids() + .map(|binding| binding.userid().clone()) + .collect() } // If multiple keys have the same email address, make sure things work @@ -799,56 +907,77 @@ pub fn test_same_email_1(db: &mut impl Database, log_path: &Path) { // upload keys. let tpk_status1 = db.merge(tpk1).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr1); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status1); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email1.clone(), EmailAddressStatus::NotPublished),), + unparsed_uids: 0, + }, + tpk_status1 + ); let tpk_status2 = db.merge(tpk2.clone()).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr2); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email2.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status2); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email2.clone(), EmailAddressStatus::NotPublished),), + unparsed_uids: 0, + }, + tpk_status2 + ); // verify tpk1 - db.set_email_published(&fpr1, &tpk_status1.email_status[0].0).unwrap(); + db.set_email_published(&fpr1, &tpk_status1.email_status[0].0) + .unwrap(); // fetch by both user ids. Even though we didn't verify uid2, the // email is the same, and both should return tpk1. - assert_eq!(get_userids(&db.by_email(&email1).unwrap()[..]), - vec![ uid1.clone() ]); - assert_eq!(get_userids(&db.by_email(&email2).unwrap()[..]), - vec![ uid1 ]); + assert_eq!( + get_userids(&db.by_email(&email1).unwrap()[..]), + vec![uid1.clone()] + ); + assert_eq!(get_userids(&db.by_email(&email2).unwrap()[..]), vec![uid1]); // verify tpk2 - db.set_email_published(&fpr2, &tpk_status2.email_status[0].0).unwrap(); + db.set_email_published(&fpr2, &tpk_status2.email_status[0].0) + .unwrap(); // fetch by both user ids. We should now get tpk2. - assert_eq!(get_userids(&db.by_email(&email1).unwrap()[..]), - vec![ uid2.clone() ]); - assert_eq!(get_userids(&db.by_email(&email2).unwrap()[..]), - vec![ uid2.clone() ]); + assert_eq!( + get_userids(&db.by_email(&email1).unwrap()[..]), + vec![uid2.clone()] + ); + assert_eq!( + get_userids(&db.by_email(&email2).unwrap()[..]), + vec![uid2.clone()] + ); // revoke tpk2's uid let sig = { let policy = &POLICY; - let uid = tpk2.userids() + let uid = tpk2 + .userids() .with_policy(policy, None) - .find(|b| *b.userid() == uid2).unwrap(); - assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revocation_status(&POLICY, None)); + .find(|b| *b.userid() == uid2) + .unwrap(); + assert_eq!( + RevocationStatus::NotAsFarAsWeKnow, + uid.revocation_status(&POLICY, None) + ); - let mut keypair = tpk2.primary_key().bundle().key().clone() - .parts_into_secret().unwrap() - .into_keypair().unwrap(); + let mut keypair = tpk2 + .primary_key() + .bundle() + .key() + .clone() + .parts_into_secret() + .unwrap() + .into_keypair() + .unwrap(); UserIDRevocationBuilder::new() - .set_reason_for_revocation(ReasonForRevocation::KeyRetired, b"It was the maid :/").unwrap() + .set_reason_for_revocation(ReasonForRevocation::KeyRetired, b"It was the maid :/") + .unwrap() .build(&mut keypair, &tpk2, uid.userid(), None) .unwrap() }; @@ -856,13 +985,14 @@ pub fn test_same_email_1(db: &mut impl Database, log_path: &Path) { let tpk2 = tpk2.insert_packets(sig).unwrap(); let tpk_status2 = db.merge(tpk2).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr2); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email2.clone(), EmailAddressStatus::Revoked), - ), - unparsed_uids: 0, - }, tpk_status2); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email2.clone(), EmailAddressStatus::Revoked),), + unparsed_uids: 0, + }, + tpk_status2 + ); // fetch by both user ids. We should get nothing. check_mail_none(db, &email1); @@ -894,34 +1024,50 @@ pub fn test_same_email_2(db: &mut impl Database, log_path: &Path) { check_log_entry(log_path, &fpr); // verify uid1 - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status); - db.set_email_published(&fpr, &tpk_status.email_status[0].0).unwrap(); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email.clone(), EmailAddressStatus::NotPublished),), + unparsed_uids: 0, + }, + tpk_status + ); + db.set_email_published(&fpr, &tpk_status.email_status[0].0) + .unwrap(); // fetch by both user ids. - assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), - vec![ uid1.clone(), uid2.clone() ]); + assert_eq!( + get_userids(&db.by_email(&email).unwrap()[..]), + vec![uid1.clone(), uid2.clone()] + ); thread::sleep(time::Duration::from_secs(2)); // revoke one uid let sig = { let policy = &POLICY; - let uid = tpk.userids() + let uid = tpk + .userids() .with_policy(policy, None) - .find(|b| *b.userid() == uid2).unwrap(); - assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revocation_status(&POLICY, None)); + .find(|b| *b.userid() == uid2) + .unwrap(); + assert_eq!( + RevocationStatus::NotAsFarAsWeKnow, + uid.revocation_status(&POLICY, None) + ); - let mut keypair = tpk.primary_key().bundle().key().clone() - .parts_into_secret().unwrap() - .into_keypair().unwrap(); + let mut keypair = tpk + .primary_key() + .bundle() + .key() + .clone() + .parts_into_secret() + .unwrap() + .into_keypair() + .unwrap(); UserIDRevocationBuilder::new() - .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/").unwrap() + .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/") + .unwrap() .build(&mut keypair, &tpk, uid.userid(), None) .unwrap() }; @@ -929,17 +1075,17 @@ pub fn test_same_email_2(db: &mut impl Database, log_path: &Path) { let tpk = tpk.insert_packets(sig).unwrap(); let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email.clone(), EmailAddressStatus::Published), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email.clone(), EmailAddressStatus::Published),), + unparsed_uids: 0, + }, + tpk_status + ); // fetch by both user ids. We should still get both user ids. - assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), - vec![ uid1 ]); + assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), vec![uid1]); } // If a key has multiple user ids with the same email address, make @@ -967,34 +1113,50 @@ pub fn test_same_email_3(db: &mut impl Database, log_path: &Path) { check_log_entry(log_path, &fpr); // verify uid1 - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 0, - }, tpk_status); - db.set_email_published(&fpr, &tpk_status.email_status[0].0).unwrap(); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email.clone(), EmailAddressStatus::NotPublished),), + unparsed_uids: 0, + }, + tpk_status + ); + db.set_email_published(&fpr, &tpk_status.email_status[0].0) + .unwrap(); // fetch by both user ids. - assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), - vec![ uid1.clone(), uid2.clone() ]); + assert_eq!( + get_userids(&db.by_email(&email).unwrap()[..]), + vec![uid1.clone(), uid2.clone()] + ); thread::sleep(time::Duration::from_secs(2)); // revoke one uid let sig = { let policy = &POLICY; - let uid = tpk.userids() + let uid = tpk + .userids() .with_policy(policy, None) - .find(|b| *b.userid() == uid1).unwrap(); - assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revocation_status(&POLICY, None)); + .find(|b| *b.userid() == uid1) + .unwrap(); + assert_eq!( + RevocationStatus::NotAsFarAsWeKnow, + uid.revocation_status(&POLICY, None) + ); - let mut keypair = tpk.primary_key().bundle().key().clone() - .parts_into_secret().unwrap() - .into_keypair().unwrap(); + let mut keypair = tpk + .primary_key() + .bundle() + .key() + .clone() + .parts_into_secret() + .unwrap() + .into_keypair() + .unwrap(); UserIDRevocationBuilder::new() - .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/").unwrap() + .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"It was the maid :/") + .unwrap() .build(&mut keypair, &tpk, uid.userid(), None) .unwrap() }; @@ -1002,22 +1164,24 @@ pub fn test_same_email_3(db: &mut impl Database, log_path: &Path) { let tpk = tpk.insert_packets(sig).unwrap(); let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email.clone(), EmailAddressStatus::Published), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email.clone(), EmailAddressStatus::Published),), + unparsed_uids: 0, + }, + tpk_status + ); - assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), - vec![ uid2.clone() ]); + assert_eq!( + get_userids(&db.by_email(&email).unwrap()[..]), + vec![uid2.clone()] + ); // make sure this survives depulication and publication of that same email address db.set_email_unpublished(&fpr, &email).unwrap(); db.set_email_published(&fpr, &email).unwrap(); - assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), - vec![ uid2 ]); + assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), vec![uid2]); } // If a key has a verified email address, make sure newly uploaded user @@ -1042,26 +1206,31 @@ pub fn test_same_email_4(db: &mut impl Database, log_path: &Path) { // upload key let tpk_status = db.merge(cert_uid_1).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - db.set_email_published(&fpr, &tpk_status.email_status[0].0).unwrap(); - assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), - vec![ uid1.clone() ]); + db.set_email_published(&fpr, &tpk_status.email_status[0].0) + .unwrap(); + assert_eq!( + get_userids(&db.by_email(&email).unwrap()[..]), + vec![uid1.clone()] + ); let tpk_status = db.merge(cert_uid_2).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email.clone(), EmailAddressStatus::Published), - ), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!((email.clone(), EmailAddressStatus::Published),), + unparsed_uids: 0, + }, + tpk_status + ); // fetch by both user ids. We should still get both user ids. - assert_eq!(get_userids(&db.by_email(&email).unwrap()[..]), - vec![ uid1, uid2 ]); + assert_eq!( + get_userids(&db.by_email(&email).unwrap()[..]), + vec![uid1, uid2] + ); } - pub fn test_bad_uids(db: &mut impl Database, log_path: &Path) { let str_uid1 = "foo@bar.example "; let str_uid2 = "A "; @@ -1079,33 +1248,38 @@ pub fn test_bad_uids(db: &mut impl Database, log_path: &Path) { let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1.clone(), EmailAddressStatus::NotPublished), - (email2.clone(), EmailAddressStatus::NotPublished), - ), - unparsed_uids: 1, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1.clone(), EmailAddressStatus::NotPublished), + (email2.clone(), EmailAddressStatus::NotPublished), + ), + unparsed_uids: 1, + }, + tpk_status + ); db.set_email_published(&fpr, &email2).unwrap(); - let tpk_status = db.get_tpk_status(&fpr, &[email1.clone(), - email2.clone()]).unwrap(); - assert_eq!(TpkStatus { - is_revoked: false, - email_status: vec!( - (email1, EmailAddressStatus::NotPublished), - (email2, EmailAddressStatus::Published), - ), - unparsed_uids: 1, - }, tpk_status); + let tpk_status = db + .get_tpk_status(&fpr, &[email1.clone(), email2.clone()]) + .unwrap(); + assert_eq!( + TpkStatus { + is_revoked: false, + email_status: vec!( + (email1, EmailAddressStatus::NotPublished), + (email2, EmailAddressStatus::Published), + ), + unparsed_uids: 1, + }, + tpk_status + ); } pub fn test_no_selfsig(db: &mut impl Database, log_path: &Path) { - let (mut tpk, revocation) = CertBuilder::new() - .generate() - .unwrap(); + let (mut tpk, revocation) = CertBuilder::new().generate().unwrap(); let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap(); // don't allow upload of naked key @@ -1115,20 +1289,20 @@ pub fn test_no_selfsig(db: &mut impl Database, log_path: &Path) { tpk = tpk.insert_packets(revocation).unwrap(); let tpk_status = db.merge(tpk).unwrap().into_tpk_status(); check_log_entry(log_path, &fpr); - assert_eq!(TpkStatus { - is_revoked: true, - email_status: vec!(), - unparsed_uids: 0, - }, tpk_status); + assert_eq!( + TpkStatus { + is_revoked: true, + email_status: vec!(), + unparsed_uids: 0 + }, + tpk_status + ); } /// Makes sure that attested key signatures are correctly handled. -pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path) - -> Result<()> { - use std::time::{SystemTime, Duration}; - use openpgp::{ - types::*, - }; +pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path) -> Result<()> { + use openpgp::types::*; + use std::time::{Duration, SystemTime}; let t0 = SystemTime::now() - Duration::new(5 * 60, 0); let t1 = SystemTime::now() - Duration::new(4 * 60, 0); @@ -1136,8 +1310,11 @@ pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path) .set_creation_time(t0) .add_userid("alice@foo.com") .generate()?; - let mut alice_signer = - alice.primary_key().key().clone().parts_into_secret()? + let mut alice_signer = alice + .primary_key() + .key() + .clone() + .parts_into_secret()? .into_keypair()?; let (bob, _) = CertBuilder::new() @@ -1145,24 +1322,28 @@ pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path) .add_userid("bob@bar.com") .generate()?; let bobs_fp = Fingerprint::try_from(bob.fingerprint())?; - let mut bob_signer = - bob.primary_key().key().clone().parts_into_secret()? + let mut bob_signer = bob + .primary_key() + .key() + .clone() + .parts_into_secret()? .into_keypair()?; // Have Alice certify the binding between "bob@bar.com" and // Bob's key. - let alice_certifies_bob - = bob.userids().nth(0).unwrap().userid().bind( - &mut alice_signer, &bob, - SignatureBuilder::new(SignatureType::GenericCertification) - .set_signature_creation_time(t1)?)?; + let alice_certifies_bob = bob.userids().nth(0).unwrap().userid().bind( + &mut alice_signer, + &bob, + SignatureBuilder::new(SignatureType::GenericCertification) + .set_signature_creation_time(t1)?, + )?; // Have Bob attest that certification. - let attestations = - bob.userids().next().unwrap().attest_certifications( - &POLICY, - &mut bob_signer, - vec![&alice_certifies_bob])?; + let attestations = bob.userids().next().unwrap().attest_certifications( + &POLICY, + &mut bob_signer, + vec![&alice_certifies_bob], + )?; assert_eq!(attestations.len(), 1); let attestation = attestations[0].clone(); @@ -1175,9 +1356,7 @@ pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path) // Then, add the certification, merge into the db, check that the // certification is stripped. - let bob = bob.insert_packets(vec![ - alice_certifies_bob.clone(), - ])?; + let bob = bob.insert_packets(vec![alice_certifies_bob.clone()])?; db.merge(bob.clone())?; check_log_entry(log_path, &bobs_fp); let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?; @@ -1186,18 +1365,30 @@ pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path) // Add the attestation, merge into the db, check that the // certification is now included. - let bob_attested = bob.clone().insert_packets(vec![ - attestation, - ])?; + let bob_attested = bob.clone().insert_packets(vec![attestation])?; db.merge(bob_attested.clone())?; check_log_entry(log_path, &bobs_fp); let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?; assert_eq!(bob_.bad_signatures().count(), 0); assert_eq!(bob_.userids().nth(0).unwrap().certifications().count(), 1); - assert_eq!(bob_.with_policy(&POLICY, None)? - .userids().nth(0).unwrap().attestation_key_signatures().count(), 1); - assert_eq!(bob_.with_policy(&POLICY, None)? - .userids().nth(0).unwrap().attested_certifications().count(), 1); + assert_eq!( + bob_.with_policy(&POLICY, None)? + .userids() + .nth(0) + .unwrap() + .attestation_key_signatures() + .count(), + 1 + ); + assert_eq!( + bob_.with_policy(&POLICY, None)? + .userids() + .nth(0) + .unwrap() + .attested_certifications() + .count(), + 1 + ); // Make a random merge with Bob's unattested cert, demonstrating // that the attestation still works. @@ -1209,60 +1400,80 @@ pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path) // Finally, withdraw consent by overriding the attestation, merge // into the db, check that the certification is now gone. - let attestations = - bob_attested.userids().next().unwrap().attest_certifications( - &POLICY, - &mut bob_signer, - &[])?; + let attestations = bob_attested + .userids() + .next() + .unwrap() + .attest_certifications(&POLICY, &mut bob_signer, &[])?; assert_eq!(attestations.len(), 1); let clear_attestation = attestations[0].clone(); - let bob = bob.insert_packets(vec![ - clear_attestation, - ])?; + let bob = bob.insert_packets(vec![clear_attestation])?; assert_eq!(bob.userids().nth(0).unwrap().certifications().count(), 1); - assert_eq!(bob.with_policy(&POLICY, None)? - .userids().nth(0).unwrap().attestation_key_signatures().count(), 1); - assert_eq!(bob.with_policy(&POLICY, None)? - .userids().nth(0).unwrap().attested_certifications().count(), 0); + assert_eq!( + bob.with_policy(&POLICY, None)? + .userids() + .nth(0) + .unwrap() + .attestation_key_signatures() + .count(), + 1 + ); + assert_eq!( + bob.with_policy(&POLICY, None)? + .userids() + .nth(0) + .unwrap() + .attested_certifications() + .count(), + 0 + ); db.merge(bob)?; check_log_entry(log_path, &bobs_fp); let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?; assert_eq!(bob_.bad_signatures().count(), 0); assert_eq!(bob_.userids().nth(0).unwrap().certifications().count(), 0); - assert_eq!(bob_.with_policy(&POLICY, None)? - .userids().nth(0).unwrap().attestation_key_signatures().count(), 1); - assert_eq!(bob_.with_policy(&POLICY, None)? - .userids().nth(0).unwrap().attested_certifications().count(), 0); + assert_eq!( + bob_.with_policy(&POLICY, None)? + .userids() + .nth(0) + .unwrap() + .attestation_key_signatures() + .count(), + 1 + ); + assert_eq!( + bob_.with_policy(&POLICY, None)? + .userids() + .nth(0) + .unwrap() + .attested_certifications() + .count(), + 0 + ); Ok(()) } fn check_log_entry(log_path: &Path, fpr: &Fingerprint) { let log_data = fs::read_to_string(log_path).unwrap(); - let last_entry = log_data - .lines() - .last().unwrap() - .split(' ') - .last().unwrap(); + let last_entry = log_data.lines().last().unwrap().split(' ').last().unwrap(); assert_eq!(last_entry, fpr.to_string()); - } fn cert_without_uid(cert: Cert, removed_uid: &UserID) -> Cert { - let packets = - cert.into_packet_pile().into_children().filter(|pkt| { - match pkt { - Packet::UserID(ref uid) => uid != removed_uid, - _ => true, - } + let packets = cert + .into_packet_pile() + .into_children() + .filter(|pkt| match pkt { + Packet::UserID(ref uid) => uid != removed_uid, + _ => true, }); Cert::from_packets(packets).unwrap() } -pub fn nonexportable_sigs(db: &mut impl Database, _log_path: &Path) - -> Result<()> { +pub fn nonexportable_sigs(db: &mut impl Database, _log_path: &Path) -> Result<()> { let str_uid1 = "Test A "; let str_uid2 = "Test B "; @@ -1273,7 +1484,8 @@ pub fn nonexportable_sigs(db: &mut impl Database, _log_path: &Path) .add_userid_with( str_uid2, SignatureBuilder::new(SignatureType::PositiveCertification) - .set_exportable_certification(false)?)? + .set_exportable_certification(false)?, + )? .generate() .unwrap(); let email1 = Email::from_str(str_uid1).unwrap(); diff --git a/database/src/types.rs b/database/src/types.rs index 441a887..c708872 100644 --- a/database/src/types.rs +++ b/database/src/types.rs @@ -3,10 +3,10 @@ use std::fmt; use std::result; use std::str::FromStr; +use anyhow::Error; use openpgp::packet::UserID; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use anyhow::Error; -use {Result}; +use Result; /// Holds a normalized email address. /// @@ -86,8 +86,7 @@ impl TryFrom for Fingerprint { fn try_from(fpr: sequoia_openpgp::Fingerprint) -> Result { match fpr { sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a)), - sequoia_openpgp::Fingerprint::Invalid(_) => - Err(anyhow!("invalid fingerprint")), + sequoia_openpgp::Fingerprint::Invalid(_) => Err(anyhow!("invalid fingerprint")), _ => Err(anyhow!("unknown fingerprint type")), } } @@ -95,7 +94,7 @@ impl TryFrom for Fingerprint { impl fmt::Display for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ::hex::ToHex; + use hex::ToHex; self.0.write_hex_upper(f) } } @@ -116,8 +115,7 @@ impl<'de> Deserialize<'de> for Fingerprint { { use serde::de::Error; String::deserialize(deserializer).and_then(|string| { - Self::from_str(&string) - .map_err(|err| Error::custom(err.to_string())) + Self::from_str(&string).map_err(|err| Error::custom(err.to_string())) }) } } @@ -128,8 +126,9 @@ impl FromStr for Fingerprint { fn from_str(s: &str) -> Result { match sequoia_openpgp::Fingerprint::from_hex(s)? { sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a)), - sequoia_openpgp::Fingerprint::Invalid(_) => - Err(anyhow!("'{}' is not a valid fingerprint", s)), + sequoia_openpgp::Fingerprint::Invalid(_) => { + Err(anyhow!("'{}' is not a valid fingerprint", s)) + } _ => Err(anyhow!("unknown fingerprint type")), } } @@ -144,9 +143,7 @@ impl TryFrom for KeyID { fn try_from(fpr: sequoia_openpgp::Fingerprint) -> Result { match fpr { sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a).into()), - sequoia_openpgp::Fingerprint::Invalid(_) => { - Err(anyhow!("invalid fingerprint")) - }, + sequoia_openpgp::Fingerprint::Invalid(_) => Err(anyhow!("invalid fingerprint")), _ => Err(anyhow!("unknown fingerprint type")), } } @@ -172,7 +169,7 @@ impl From for KeyID { impl fmt::Display for KeyID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ::hex::ToHex; + use hex::ToHex; self.0.write_hex_upper(f) } } @@ -183,8 +180,9 @@ impl FromStr for KeyID { fn from_str(s: &str) -> Result { match sequoia_openpgp::KeyID::from_hex(s)? { sequoia_openpgp::KeyID::V4(a) => Ok(KeyID(a)), - sequoia_openpgp::KeyID::Invalid(_) => - Err(anyhow!("'{}' is not a valid long key ID", s)), + sequoia_openpgp::KeyID::Invalid(_) => { + Err(anyhow!("'{}' is not a valid long key ID", s)) + } _ => Err(anyhow!("unknown keyid type")), } } @@ -203,10 +201,11 @@ mod tests { assert_eq!(c("Foo Bar ").as_str(), "foo@example.org"); // FIXME gotta fix this // assert_eq!(c("foo@example.org ").as_str(), "foo@example.org"); - assert_eq!(c("\"Foo Bar\" ").as_str(), - "foo@example.org"); - assert_eq!(c("foo@👍.example.org").as_str(), - "foo@xn--yp8h.example.org"); + assert_eq!( + c("\"Foo Bar\" ").as_str(), + "foo@example.org" + ); + assert_eq!(c("foo@👍.example.org").as_str(), "foo@xn--yp8h.example.org"); assert_eq!(c("Foo@example.org").as_str(), "foo@example.org"); assert_eq!(c("foo@EXAMPLE.ORG").as_str(), "foo@example.org"); } diff --git a/database/src/wkd.rs b/database/src/wkd.rs index 0463eca..75d202d 100644 --- a/database/src/wkd.rs +++ b/database/src/wkd.rs @@ -1,11 +1,11 @@ +use super::Result; use crate::openpgp::types::HashAlgorithm; use zbase32; -use super::Result; // cannibalized from // https://gitlab.com/sequoia-pgp/sequoia/blob/master/net/src/wkd.rs -pub fn encode_wkd(address: impl AsRef) -> Result<(String,String)> { +pub fn encode_wkd(address: impl AsRef) -> Result<(String, String)> { let (local_part, domain) = split_address(address)?; let local_part_encoded = encode_local_part(local_part); @@ -13,7 +13,7 @@ pub fn encode_wkd(address: impl AsRef) -> Result<(String,String)> { Ok((local_part_encoded, domain)) } -fn split_address(email_address: impl AsRef) -> Result<(String,String)> { +fn split_address(email_address: impl AsRef) -> Result<(String, String)> { let email_address = email_address.as_ref(); let v: Vec<&str> = email_address.split('@').collect(); if v.len() != 2 { diff --git a/hagridctl/src/import.rs b/hagridctl/src/import.rs index dc4f73e..9d0de3f 100644 --- a/hagridctl/src/import.rs +++ b/hagridctl/src/import.rs @@ -1,22 +1,22 @@ -use std::path::{Path,PathBuf}; +use std::cmp::min; use std::fs::File; use std::io::Read; -use std::thread; -use std::cmp::min; +use std::path::{Path, PathBuf}; use std::sync::Arc; +use std::thread; use anyhow::Result; extern crate tempfile; extern crate sequoia_openpgp as openpgp; -use openpgp::Packet; use openpgp::parse::{PacketParser, PacketParserResult, Parse}; +use openpgp::Packet; extern crate hagrid_database as database; -use database::{Database, KeyDatabase, ImportResult}; +use database::{Database, ImportResult, KeyDatabase}; -use indicatif::{MultiProgress,ProgressBar,ProgressStyle}; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use HagridConfig; @@ -38,8 +38,7 @@ pub fn do_import(config: &HagridConfig, dry_run: bool, input_files: Vec let config = config.clone(); let multi_progress = multi_progress.clone(); thread::spawn(move || { - import_from_files( - &config, dry_run, input_file_chunk, multi_progress).unwrap(); + import_from_files(&config, dry_run, input_file_chunk, multi_progress).unwrap(); }) }) .collect(); @@ -53,15 +52,12 @@ pub fn do_import(config: &HagridConfig, dry_run: bool, input_files: Vec Ok(()) } -fn setup_chunks( - mut input_files: Vec, - num_threads: usize, -) -> Vec> { +fn setup_chunks(mut input_files: Vec, num_threads: usize) -> Vec> { let chunk_size = (input_files.len() + (num_threads - 1)) / num_threads; (0..num_threads) .map(|_| { let len = input_files.len(); - input_files.drain(0..min(chunk_size,len)).collect() + input_files.drain(0..min(chunk_size, len)).collect() }) .collect() } @@ -76,7 +72,7 @@ struct ImportStats<'a> { count_unchanged: u64, } -impl <'a> ImportStats<'a> { +impl<'a> ImportStats<'a> { fn new(progress: &'a ProgressBar, filename: String) -> Self { ImportStats { progress, @@ -106,9 +102,14 @@ impl <'a> ImportStats<'a> { return; } self.progress.set_message(&format!( - "{}, imported {:5} keys, {:5} New {:5} Updated {:5} Unchanged {:5} Errors", - &self.filename, self.count_total, self.count_new, self.count_updated, self.count_unchanged, self.count_err)); - + "{}, imported {:5} keys, {:5} New {:5} Updated {:5} Unchanged {:5} Errors", + &self.filename, + self.count_total, + self.count_new, + self.count_updated, + self.count_unchanged, + self.count_err + )); } } @@ -137,10 +138,11 @@ fn import_from_file(db: &KeyDatabase, input: &Path, multi_progress: &MultiProgre let bytes_total = input_file.metadata()?.len(); let progress_bar = multi_progress.add(ProgressBar::new(bytes_total)); - progress_bar - .set_style(ProgressStyle::default_bar() + progress_bar.set_style( + ProgressStyle::default_bar() .template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}") - .progress_chars("##-")); + .progress_chars("##-"), + ); progress_bar.set_message("Starting…"); let input_reader = &mut progress_bar.wrap_read(input_file); @@ -156,8 +158,13 @@ fn import_from_file(db: &KeyDatabase, input: &Path, multi_progress: &MultiProgre Packet::SecretKey(key) => key.fingerprint().to_hex(), _ => "Unknown".to_owned(), }; - let error = format!("{}:{:05}:{}: {}", filename, stats.count_total, - key_fpr, e.to_string()); + let error = format!( + "{}:{:05}:{}: {}", + filename, + stats.count_total, + key_fpr, + e.to_string() + ); progress_bar.println(error); } stats.update(result); @@ -169,7 +176,7 @@ fn import_from_file(db: &KeyDatabase, input: &Path, multi_progress: &MultiProgre fn read_file_to_tpks( reader: impl Read + Send + Sync, - callback: &mut impl FnMut(Vec) -> () + callback: &mut impl FnMut(Vec) -> (), ) -> Result<()> { let mut ppr = PacketParser::from_reader(reader)?; let mut acc = Vec::new(); @@ -183,7 +190,7 @@ fn read_file_to_tpks( if !acc.is_empty() { if let Packet::PublicKey(_) | Packet::SecretKey(_) = packet { callback(acc); - acc = vec!(); + acc = vec![]; } } @@ -194,10 +201,7 @@ fn read_file_to_tpks( } fn import_key(db: &KeyDatabase, packets: Vec) -> Result { - openpgp::Cert::from_packets(packets.into_iter()) - .and_then(|tpk| { - db.merge(tpk) - }) + openpgp::Cert::from_packets(packets.into_iter()).and_then(|tpk| db.merge(tpk)) } /* diff --git a/hagridctl/src/main.rs b/hagridctl/src/main.rs index 16d495b..b91d580 100644 --- a/hagridctl/src/main.rs +++ b/hagridctl/src/main.rs @@ -1,12 +1,12 @@ extern crate anyhow; extern crate clap; -extern crate tempfile; -extern crate sequoia_openpgp as openpgp; extern crate hagrid_database as database; +extern crate sequoia_openpgp as openpgp; +extern crate tempfile; #[macro_use] extern crate serde_derive; -extern crate toml; extern crate indicatif; +extern crate toml; extern crate walkdir; use std::fs; @@ -15,7 +15,7 @@ use std::str::FromStr; use anyhow::Result; -use clap::{Arg, App, SubCommand}; +use clap::{App, Arg, SubCommand}; mod import; mod regenerate; @@ -30,7 +30,7 @@ pub struct HagridConfigs { // this is not an exact match - Rocket config has more complicated semantics // than a plain toml file. // see also https://github.com/SergioBenitez/Rocket/issues/228 -#[derive(Deserialize,Clone)] +#[derive(Deserialize, Clone)] pub struct HagridConfig { _template_dir: Option, keys_internal_dir: Option, @@ -43,34 +43,42 @@ pub struct HagridConfig { fn main() -> Result<()> { let matches = App::new("Hagrid Control") - .version("0.1") - .about("Control hagrid database externally") - .arg(Arg::with_name("config") - .short("c") - .long("config") - .value_name("FILE") - .help("Sets a custom config file") - .takes_value(true)) - .arg(Arg::with_name("env") - .short("e") - .long("env") - .value_name("ENVIRONMENT") - .takes_value(true) - .default_value("prod") - .possible_values(&["dev","stage","prod"])) - .subcommand(SubCommand::with_name("regenerate") - .about("Regenerate symlink directory")) - .subcommand(SubCommand::with_name("import") - .about("Import keys into Hagrid") - .arg(Arg::with_name("dry run") - .short("n") - .long("dry-run") - .help("don't actually keep imported keys") - ) - .arg(Arg::with_name("keyring files") - .required(true) - .multiple(true))) - .get_matches(); + .version("0.1") + .about("Control hagrid database externally") + .arg( + Arg::with_name("config") + .short("c") + .long("config") + .value_name("FILE") + .help("Sets a custom config file") + .takes_value(true), + ) + .arg( + Arg::with_name("env") + .short("e") + .long("env") + .value_name("ENVIRONMENT") + .takes_value(true) + .default_value("prod") + .possible_values(&["dev", "stage", "prod"]), + ) + .subcommand(SubCommand::with_name("regenerate").about("Regenerate symlink directory")) + .subcommand( + SubCommand::with_name("import") + .about("Import keys into Hagrid") + .arg( + Arg::with_name("dry run") + .short("n") + .long("dry-run") + .help("don't actually keep imported keys"), + ) + .arg( + Arg::with_name("keyring files") + .required(true) + .multiple(true), + ), + ) + .get_matches(); let config_file = matches.value_of("config").unwrap_or("Rocket.toml"); let config_data = fs::read_to_string(config_file).unwrap(); diff --git a/hagridctl/src/regenerate.rs b/hagridctl/src/regenerate.rs index a6a398a..e995a99 100644 --- a/hagridctl/src/regenerate.rs +++ b/hagridctl/src/regenerate.rs @@ -3,12 +3,12 @@ use anyhow::Result; use std::path::Path; use std::time::Instant; +use indicatif::{ProgressBar, ProgressStyle}; use walkdir::WalkDir; -use indicatif::{ProgressBar,ProgressStyle}; -use HagridConfig; -use database::{Database,KeyDatabase,RegenerateResult}; use database::types::Fingerprint; +use database::{Database, KeyDatabase, RegenerateResult}; +use HagridConfig; struct RegenerateStats<'a> { progress: &'a ProgressBar, @@ -22,7 +22,7 @@ struct RegenerateStats<'a> { kps_partial: u64, } -impl <'a> RegenerateStats<'a> { +impl<'a> RegenerateStats<'a> { fn new(progress: &'a ProgressBar) -> Self { Self { progress, @@ -48,7 +48,7 @@ impl <'a> RegenerateStats<'a> { Err(e) => { self.progress.println(format!("{}: {}", fpr, e.to_string())); self.count_err += 1; - }, + } Ok(RegenerateResult::Updated) => self.count_updated += 1, Ok(RegenerateResult::Unchanged) => self.count_unchanged += 1, } @@ -79,21 +79,27 @@ pub fn do_regenerate(config: &HagridConfig) -> Result<()> { false, )?; - let published_dir = config.keys_external_dir.as_ref().unwrap().join("links").join("by-email"); + let published_dir = config + .keys_external_dir + .as_ref() + .unwrap() + .join("links") + .join("by-email"); let dirs: Vec<_> = WalkDir::new(published_dir) .min_depth(1) .max_depth(1) - .sort_by(|a,b| a.file_name().cmp(b.file_name())) + .sort_by(|a, b| a.file_name().cmp(b.file_name())) .into_iter() .flatten() .map(|entry| entry.into_path()) .collect(); let progress_bar = ProgressBar::new(dirs.len() as u64); - progress_bar - .set_style(ProgressStyle::default_bar() + progress_bar.set_style( + ProgressStyle::default_bar() .template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}") - .progress_chars("##-")); + .progress_chars("##-"), + ); let mut stats = RegenerateStats::new(&progress_bar); @@ -106,14 +112,18 @@ pub fn do_regenerate(config: &HagridConfig) -> Result<()> { Ok(()) } -fn regenerate_dir_recursively(db: &KeyDatabase, stats: &mut RegenerateStats, dir: &Path) -> Result<()> { +fn regenerate_dir_recursively( + db: &KeyDatabase, + stats: &mut RegenerateStats, + dir: &Path, +) -> Result<()> { for path in WalkDir::new(dir) .follow_links(true) .into_iter() .flatten() .filter(|e| e.file_type().is_file()) - .map(|entry| entry.into_path()) { - + .map(|entry| entry.into_path()) + { let fpr = KeyDatabase::path_to_primary(&path).unwrap(); let result = db.regenerate_links(&fpr); stats.update(result, fpr); diff --git a/src/anonymize_utils.rs b/src/anonymize_utils.rs index fe2b674..a08c15f 100644 --- a/src/anonymize_utils.rs +++ b/src/anonymize_utils.rs @@ -55,17 +55,18 @@ lazy_static! { } pub fn anonymize_address(email: &Email) -> Option { - email.as_str() - .rsplit('@') - .next() - .map(|domain| domain.to_lowercase()) - .and_then(|domain| { - if POPULAR_DOMAINS.contains(&domain.as_str()) { - Some(domain) - } else { - domain.rsplit('.').next().map(|tld| tld.to_owned()) - } - }) + email + .as_str() + .rsplit('@') + .next() + .map(|domain| domain.to_lowercase()) + .and_then(|domain| { + if POPULAR_DOMAINS.contains(&domain.as_str()) { + Some(domain) + } else { + domain.rsplit('.').next().map(|tld| tld.to_owned()) + } + }) } pub fn anonymize_address_fallback(email: &Email) -> String { diff --git a/src/counters.rs b/src/counters.rs index 01803ba..876ec71 100644 --- a/src/counters.rs +++ b/src/counters.rs @@ -8,14 +8,21 @@ use crate::database::types::Email; lazy_static! { static ref KEY_UPLOAD: LabelCounter = LabelCounter::new("hagrid_key_upload", "Uploaded keys", &["result"]); - - static ref MAIL_SENT: LabelCounter = - LabelCounter::new("hagrid_mail_sent", "Sent verification mails", &["type", "domain"]); - - static ref KEY_ADDRESS_PUBLISHED: LabelCounter = - LabelCounter::new("hagrid_key_address_published", "Verified email addresses", &["domain"]); - static ref KEY_ADDRESS_UNPUBLISHED: LabelCounter = - LabelCounter::new("hagrid_key_address_unpublished", "Unpublished email addresses", &["domain"]); + static ref MAIL_SENT: LabelCounter = LabelCounter::new( + "hagrid_mail_sent", + "Sent verification mails", + &["type", "domain"] + ); + static ref KEY_ADDRESS_PUBLISHED: LabelCounter = LabelCounter::new( + "hagrid_key_address_published", + "Verified email addresses", + &["domain"] + ); + static ref KEY_ADDRESS_UNPUBLISHED: LabelCounter = LabelCounter::new( + "hagrid_key_address_unpublished", + "Unpublished email addresses", + &["domain"] + ); } pub fn register_counters(registry: &prometheus::Registry) { @@ -58,7 +65,9 @@ impl LabelCounter { } fn register(&self, registry: &prometheus::Registry) { - registry.register(Box::new(self.prometheus_counter.clone())).unwrap(); + registry + .register(Box::new(self.prometheus_counter.clone())) + .unwrap(); } fn inc(&self, values: &[&str]) { diff --git a/src/delete.rs b/src/delete.rs index 64a93fe..7c1c81f 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -4,13 +4,13 @@ use std::convert::TryInto; use std::path::PathBuf; extern crate anyhow; -use anyhow::Result as Result; +use anyhow::Result; extern crate structopt; use structopt::StructOpt; extern crate hagrid_database as database; -use crate::database::{Query, Database, KeyDatabase}; +use crate::database::{Database, KeyDatabase, Query}; #[derive(Debug, StructOpt)] #[structopt( @@ -54,32 +54,30 @@ fn real_main() -> Result<()> { delete(&db, &opt.query.parse()?, opt.all_bindings, opt.all) } -fn delete(db: &KeyDatabase, query: &Query, all_bindings: bool, mut all: bool) - -> Result<()> { +fn delete(db: &KeyDatabase, query: &Query, all_bindings: bool, mut all: bool) -> Result<()> { match query { Query::ByFingerprint(_) | Query::ByKeyID(_) => { - eprintln!("Fingerprint or KeyID given, deleting key and all \ - bindings."); + eprintln!( + "Fingerprint or KeyID given, deleting key and all \ + bindings." + ); all = true; - }, + } _ => (), } - let tpk = db.lookup(query)?.ok_or_else( - || anyhow::format_err!("No TPK matching {:?}", query))?; + let tpk = db + .lookup(query)? + .ok_or_else(|| anyhow::format_err!("No TPK matching {:?}", query))?; let fp: database::types::Fingerprint = tpk.fingerprint().try_into()?; let mut results = Vec::new(); // First, delete the bindings. if all_bindings || all { - results.push( - ("all bindings".into(), - db.set_email_unpublished_all(&fp))); + results.push(("all bindings".into(), db.set_email_unpublished_all(&fp))); } else if let Query::ByEmail(ref email) = query { - results.push( - (email.to_string(), - db.set_email_unpublished(&fp, email))); + results.push((email.to_string(), db.set_email_unpublished(&fp, email))); } else { unreachable!() } @@ -110,12 +108,15 @@ fn delete(db: &KeyDatabase, query: &Query, all_bindings: bool, mut all: bool) let mut err = Ok(()); for (slug, result) in results { - eprintln!("{}: {}", slug, - if let Err(ref e) = result { - e.to_string() - } else { - "Deleted".into() - }); + eprintln!( + "{}: {}", + slug, + if let Err(ref e) = result { + e.to_string() + } else { + "Deleted".into() + } + ); if err.is_ok() { if let Err(e) = result { err = Err(e); diff --git a/src/dump.rs b/src/dump.rs index 340949b..2fc4078 100644 --- a/src/dump.rs +++ b/src/dump.rs @@ -1,22 +1,20 @@ use std::io::{self, Read}; -use sequoia_openpgp as openpgp; -use self::openpgp::types::{Duration, Timestamp, SymmetricAlgorithm}; -use self::openpgp::fmt::hex; use self::openpgp::crypto::mpi; -use self::openpgp::{Packet, Result}; -use self::openpgp::packet::prelude::*; -use self::openpgp::packet::header::CTB; -use self::openpgp::packet::{Header, header::BodyLength, Signature}; -use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; use self::openpgp::crypto::{SessionKey, S2K}; -use self::openpgp::parse::{map::Map, Parse, PacketParserResult}; +use self::openpgp::fmt::hex; +use self::openpgp::packet::header::CTB; +use self::openpgp::packet::prelude::*; +use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; +use self::openpgp::packet::{header::BodyLength, Header, Signature}; +use self::openpgp::parse::{map::Map, PacketParserResult, Parse}; +use self::openpgp::types::{Duration, SymmetricAlgorithm, Timestamp}; +use self::openpgp::{Packet, Result}; +use sequoia_openpgp as openpgp; #[derive(Debug)] pub enum Kind { - Message { - encrypted: bool, - }, + Message { encrypted: bool }, Keyring, Cert, Unknown, @@ -53,16 +51,20 @@ impl Convert> for Timestamp { } #[allow(clippy::redundant_pattern_matching)] -pub fn dump(input: &mut (dyn io::Read + Sync + Send), - output: &mut dyn io::Write, - mpis: bool, hex: bool, sk: Option<&SessionKey>, - width: W) - -> Result - where W: Into> +pub fn dump( + input: &mut (dyn io::Read + Sync + Send), + output: &mut dyn io::Write, + mpis: bool, + hex: bool, + sk: Option<&SessionKey>, + width: W, +) -> Result +where + W: Into>, { - let mut ppr - = self::openpgp::parse::PacketParserBuilder::from_reader(input)? - .map(hex).build()?; + let mut ppr = self::openpgp::parse::PacketParserBuilder::from_reader(input)? + .map(hex) + .build()?; let mut message_encrypted = false; let width = width.into().unwrap_or(80); let mut dumper = PacketDumper::new(width, mpis); @@ -72,12 +74,12 @@ pub fn dump(input: &mut (dyn io::Read + Sync + Send), Packet::Literal(_) => { let mut prefix = vec![0; 40]; let n = pp.read(&mut prefix)?; - Some(vec![ - format!("Content: {:?}{}", - String::from_utf8_lossy(&prefix[..n]), - if n == prefix.len() { "..." } else { "" }), - ]) - }, + Some(vec![format!( + "Content: {:?}{}", + String::from_utf8_lossy(&prefix[..n]), + if n == prefix.len() { "..." } else { "" } + )]) + } Packet::SEIP(_) if sk.is_none() => { message_encrypted = true; Some(vec!["No session key supplied".into()]) @@ -89,7 +91,9 @@ pub fn dump(input: &mut (dyn io::Read + Sync + Send), for algo in 1..20 { let algo = SymmetricAlgorithm::from(algo); if let Ok(size) = algo.key_size() { - if size != sk.len() { continue; } + if size != sk.len() { + continue; + } } else { continue; } @@ -108,7 +112,7 @@ pub fn dump(input: &mut (dyn io::Read + Sync + Send), fields.push("Decryption failed".into()); } Some(fields) - }, + } Packet::AED(_) if sk.is_none() => { message_encrypted = true; Some(vec!["No session key supplied".into()]) @@ -132,7 +136,7 @@ pub fn dump(input: &mut (dyn io::Read + Sync + Send), fields.push("Decryption successful".into()); } Some(fields) - }, + } _ => None, }; @@ -142,15 +146,21 @@ pub fn dump(input: &mut (dyn io::Read + Sync + Send), let recursion_depth = pp.recursion_depth(); let packet = pp.packet.clone(); - dumper.packet(output, recursion_depth as usize, - header, packet, map, additional_fields)?; + dumper.packet( + output, + recursion_depth as usize, + header, + packet, + map, + additional_fields, + )?; let (_, ppr_) = match pp.recurse() { Ok(v) => Ok(v), Err(e) => { let _ = dumper.flush(output); Err(e) - }, + } }?; ppr = ppr_; } @@ -183,8 +193,12 @@ struct Node { } impl Node { - fn new(header: Header, packet: Packet, map: Option, - additional_fields: Option>) -> Self { + fn new( + header: Header, + packet: Packet, + map: Option, + additional_fields: Option>, + ) -> Self { Node { header, packet, @@ -198,7 +212,11 @@ impl Node { if depth == 0 { self.children.push(node); } else { - self.children.iter_mut().last().unwrap().append(depth - 1, node); + self.children + .iter_mut() + .last() + .unwrap() + .append(depth - 1, node); } } } @@ -218,10 +236,15 @@ impl PacketDumper { } } - pub fn packet(&mut self, output: &mut dyn io::Write, depth: usize, - header: Header, p: Packet, map: Option, - additional_fields: Option>) - -> Result<()> { + pub fn packet( + &mut self, + output: &mut dyn io::Write, + depth: usize, + header: Header, + p: Packet, + map: Option, + additional_fields: Option>, + ) -> Result<()> { let node = Node::new(header, p, map, additional_fields); if self.root.is_none() { assert_eq!(depth, 0); @@ -243,13 +266,20 @@ impl PacketDumper { Ok(()) } - fn dump_tree(&self, output: &mut dyn io::Write, indent: &str, node: &Node) - -> Result<()> { - let indent_node = - format!("{}{} ", indent, - if node.children.is_empty() { " " } else { "│" }); - self.dump_packet(output, &indent_node, Some(&node.header), &node.packet, - node.map.as_ref(), node.additional_fields.as_ref())?; + fn dump_tree(&self, output: &mut dyn io::Write, indent: &str, node: &Node) -> Result<()> { + let indent_node = format!( + "{}{} ", + indent, + if node.children.is_empty() { " " } else { "│" } + ); + self.dump_packet( + output, + &indent_node, + Some(&node.header), + &node.packet, + node.map.as_ref(), + node.additional_fields.as_ref(), + )?; if node.children.is_empty() { return Ok(()); } @@ -257,20 +287,22 @@ impl PacketDumper { let last = node.children.len() - 1; for (i, child) in node.children.iter().enumerate() { let is_last = i == last; - write!(output, "{}{}── ", indent, - if is_last { "└" } else { "├" })?; - let indent_child = - format!("{}{} ", indent, - if is_last { " " } else { "│" }); + write!(output, "{}{}── ", indent, if is_last { "└" } else { "├" })?; + let indent_child = format!("{}{} ", indent, if is_last { " " } else { "│" }); self.dump_tree(output, &indent_child, child)?; } Ok(()) } - fn dump_packet(&self, mut output: &mut dyn io::Write, i: &str, - header: Option<&Header>, p: &Packet, map: Option<&Map>, - additional_fields: Option<&Vec>) - -> Result<()> { + fn dump_packet( + &self, + mut output: &mut dyn io::Write, + i: &str, + header: Option<&Header>, + p: &Packet, + map: Option<&Map>, + additional_fields: Option<&Vec>, + ) -> Result<()> { use self::openpgp::Packet::*; if let Some(tag) = p.kind() { @@ -280,40 +312,55 @@ impl PacketDumper { } if let Some(h) = header { - write!(output, ", {} CTB, {}{}", - if let CTB::Old(_) = h.ctb() { "old" } else { "new" }, - if let Some(map) = map { - format!("{} header bytes + ", - map.iter().take(2).map(|f| f.as_bytes().len()) - .sum::()) - } else { - // XXX: Mapping is disabled. No can do for - // now. Once we save the header in - // packet::Common, we can use this instead of - // relying on the map. - "".into() - }, - match h.length() { - BodyLength::Full(n) => - format!("{} bytes", n), - BodyLength::Partial(n) => - format!("partial length, {} bytes in first chunk", n), - BodyLength::Indeterminate => - "indeterminate length".into(), - })?; + write!( + output, + ", {} CTB, {}{}", + if let CTB::Old(_) = h.ctb() { + "old" + } else { + "new" + }, + if let Some(map) = map { + format!( + "{} header bytes + ", + map.iter() + .take(2) + .map(|f| f.as_bytes().len()) + .sum::() + ) + } else { + // XXX: Mapping is disabled. No can do for + // now. Once we save the header in + // packet::Common, we can use this instead of + // relying on the map. + "".into() + }, + match h.length() { + BodyLength::Full(n) => format!("{} bytes", n), + BodyLength::Partial(n) => format!("partial length, {} bytes in first chunk", n), + BodyLength::Indeterminate => "indeterminate length".into(), + } + )?; } writeln!(output)?; - fn dump_key(pd: &PacketDumper, - output: &mut dyn io::Write, i: &str, - k: &Key) - -> Result<()> - where P: key::KeyParts, - R: key::KeyRole, + fn dump_key( + pd: &PacketDumper, + output: &mut dyn io::Write, + i: &str, + k: &Key, + ) -> Result<()> + where + P: key::KeyParts, + R: key::KeyRole, { writeln!(output, "{} Version: {}", i, k.version())?; - writeln!(output, "{} Creation time: {}", i, - k.creation_time().convert())?; + writeln!( + output, + "{} Creation time: {}", + i, + k.creation_time().convert() + )?; writeln!(output, "{} Pk algo: {}", i, k.pk_algo())?; if let Some(bits) = k.mpis().bits() { writeln!(output, "{} Pk size: {} bits", i, bits)?; @@ -326,49 +373,55 @@ impl PacketDumper { let ii = format!("{} ", i); match k.mpis() { - mpi::PublicKey::RSA { e, n } => - pd.dump_mpis(output, &ii, - &[e.value(), n.value()], - &["e", "n"])?, - mpi::PublicKey::DSA { p, q, g, y } => - pd.dump_mpis(output, &ii, - &[p.value(), q.value(), g.value(), - y.value()], - &["p", "q", "g", "y"])?, - mpi::PublicKey::ElGamal { p, g, y } => - pd.dump_mpis(output, &ii, - &[p.value(), g.value(), y.value()], - &["p", "g", "y"])?, + mpi::PublicKey::RSA { e, n } => { + pd.dump_mpis(output, &ii, &[e.value(), n.value()], &["e", "n"])? + } + mpi::PublicKey::DSA { p, q, g, y } => pd.dump_mpis( + output, + &ii, + &[p.value(), q.value(), g.value(), y.value()], + &["p", "q", "g", "y"], + )?, + mpi::PublicKey::ElGamal { p, g, y } => pd.dump_mpis( + output, + &ii, + &[p.value(), g.value(), y.value()], + &["p", "g", "y"], + )?, mpi::PublicKey::EdDSA { curve, q } => { writeln!(output, "{} Curve: {}", ii, curve)?; pd.dump_mpis(output, &ii, &[q.value()], &["q"])?; - }, + } mpi::PublicKey::ECDSA { curve, q } => { writeln!(output, "{} Curve: {}", ii, curve)?; pd.dump_mpis(output, &ii, &[q.value()], &["q"])?; - }, - mpi::PublicKey::ECDH { curve, q, hash, sym } => { + } + mpi::PublicKey::ECDH { + curve, + q, + hash, + sym, + } => { writeln!(output, "{} Curve: {}", ii, curve)?; writeln!(output, "{} Hash algo: {}", ii, hash)?; - writeln!(output, "{} Symmetric algo: {}", ii, - sym)?; + writeln!(output, "{} Symmetric algo: {}", ii, sym)?; pd.dump_mpis(output, &ii, &[q.value()], &["q"])?; - }, + } mpi::PublicKey::Unknown { mpis, rest } => { let keys: Vec = - (0..mpis.len()).map( - |i| format!("mpi{}", i)).collect(); + (0..mpis.len()).map(|i| format!("mpi{}", i)).collect(); pd.dump_mpis( - output, &ii, - &mpis.iter().map(|m| { - m.value().iter().as_slice() - }).collect::>()[..], - &keys.iter().map(|k| k.as_str()) + output, + &ii, + &mpis + .iter() + .map(|m| m.value().iter().as_slice()) .collect::>()[..], + &keys.iter().map(|k| k.as_str()).collect::>()[..], )?; pd.dump_mpis(output, &ii, &[&rest[..]], &["rest"])?; - }, + } // crypto::mpi:Publickey is non-exhaustive u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, @@ -386,47 +439,48 @@ impl PacketDumper { writeln!(output, "{} Unencrypted", ii)?; if pd.mpis { u.map(|mpis| -> Result<()> { - match mpis - { - mpi::SecretKeyMaterial::RSA { d, p, q, u } => - pd.dump_mpis(output, &ii, - &[d.value(), p.value(), - q.value(), u.value()], - &["d", "p", "q", "u"])?, - mpi::SecretKeyMaterial::DSA { x } => - pd.dump_mpis(output, &ii, &[x.value()], - &["x"])?, - mpi::SecretKeyMaterial::ElGamal { x } => - pd.dump_mpis(output, &ii, &[x.value()], - &["x"])?, - mpi::SecretKeyMaterial::EdDSA { scalar } => - pd.dump_mpis(output, &ii, - &[scalar.value()], - &["scalar"])?, - mpi::SecretKeyMaterial::ECDSA { scalar } => - pd.dump_mpis(output, &ii, - &[scalar.value()], - &["scalar"])?, - mpi::SecretKeyMaterial::ECDH { scalar } => - pd.dump_mpis(output, &ii, - &[scalar.value()], - &["scalar"])?, + match mpis { + mpi::SecretKeyMaterial::RSA { d, p, q, u } => pd.dump_mpis( + output, + &ii, + &[d.value(), p.value(), q.value(), u.value()], + &["d", "p", "q", "u"], + )?, + mpi::SecretKeyMaterial::DSA { x } => { + pd.dump_mpis(output, &ii, &[x.value()], &["x"])? + } + mpi::SecretKeyMaterial::ElGamal { x } => { + pd.dump_mpis(output, &ii, &[x.value()], &["x"])? + } + mpi::SecretKeyMaterial::EdDSA { scalar } => { + pd.dump_mpis(output, &ii, &[scalar.value()], &["scalar"])? + } + mpi::SecretKeyMaterial::ECDSA { scalar } => { + pd.dump_mpis(output, &ii, &[scalar.value()], &["scalar"])? + } + mpi::SecretKeyMaterial::ECDH { scalar } => { + pd.dump_mpis(output, &ii, &[scalar.value()], &["scalar"])? + } mpi::SecretKeyMaterial::Unknown { mpis, rest } => { let keys: Vec = - (0..mpis.len()).map( - |i| format!("mpi{}", i)).collect(); + (0..mpis.len()).map(|i| format!("mpi{}", i)).collect(); pd.dump_mpis( - output, &ii, - &mpis.iter().map(|m| { - m.value().iter().as_slice() - }).collect::>()[..], - &keys.iter().map(|k| k.as_str()) + output, + &ii, + &mpis + .iter() + .map(|m| { + m.value().iter().as_slice() + }) + .collect::>()[..], + &keys + .iter() + .map(|k| k.as_str()) .collect::>()[..], )?; - pd.dump_mpis(output, &ii, &[rest], - &["rest"])?; - }, + pd.dump_mpis(output, &ii, &[rest], &["rest"])?; + } // crypto::mpi::SecretKeyMaterial is non-exhaustive. u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, @@ -440,15 +494,13 @@ impl PacketDumper { writeln!(output, "{} Encrypted", ii)?; write!(output, "{} S2K: ", ii)?; pd.dump_s2k(output, &ii, e.s2k())?; - writeln!(output, "{} Sym. algo: {}", ii, - e.algo())?; + writeln!(output, "{} Sym. algo: {}", ii, e.algo())?; if pd.mpis { if let Ok(ciphertext) = e.ciphertext() { - pd.dump_mpis(output, &ii, &[ciphertext], - &["ciphertext"])?; + pd.dump_mpis(output, &ii, &[ciphertext], &["ciphertext"])?; } } - }, + } } } @@ -459,7 +511,7 @@ impl PacketDumper { Unknown(ref u) => { writeln!(output, "{} Tag: {}", i, u.tag())?; writeln!(output, "{} Error: {}", i, u.error())?; - }, + } PublicKey(ref k) => dump_key(self, output, i, k)?, PublicSubkey(ref k) => dump_key(self, output, i, k)?, @@ -483,15 +535,26 @@ impl PacketDumper { self.dump_subpacket(output, i, pkt, s)?; } } - writeln!(output, "{} Digest prefix: {}", i, - hex::encode(s.digest_prefix()))?; + writeln!( + output, + "{} Digest prefix: {}", + i, + hex::encode(s.digest_prefix()) + )?; write!(output, "{} Level: {} ", i, s.level())?; match s.level() { 0 => writeln!(output, "(signature over data)")?, - 1 => writeln!(output, "(notarization over signatures \ - level 0 and data)")?, - n => writeln!(output, "(notarization over signatures \ - level <= {} and data)", n - 1)?, + 1 => writeln!( + output, + "(notarization over signatures \ + level 0 and data)" + )?, + n => writeln!( + output, + "(notarization over signatures \ + level <= {} and data)", + n - 1 + )?, } if self.mpis { writeln!(output, "{}", i)?; @@ -499,47 +562,42 @@ impl PacketDumper { let ii = format!("{} ", i); match s.mpis() { - mpi::Signature::RSA { s } => - self.dump_mpis(output, &ii, - &[s.value()], - &["s"])?, - mpi::Signature::DSA { r, s } => - self.dump_mpis(output, &ii, - &[r.value(), s.value()], - &["r", "s"])?, - mpi::Signature::ElGamal { r, s } => - self.dump_mpis(output, &ii, - &[r.value(), s.value()], - &["r", "s"])?, - mpi::Signature::EdDSA { r, s } => - self.dump_mpis(output, &ii, - &[r.value(), s.value()], - &["r", "s"])?, - mpi::Signature::ECDSA { r, s } => - self.dump_mpis(output, &ii, - &[r.value(), s.value()], - &["r", "s"])?, + mpi::Signature::RSA { s } => { + self.dump_mpis(output, &ii, &[s.value()], &["s"])? + } + mpi::Signature::DSA { r, s } => { + self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])? + } + mpi::Signature::ElGamal { r, s } => { + self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])? + } + mpi::Signature::EdDSA { r, s } => { + self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])? + } + mpi::Signature::ECDSA { r, s } => { + self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])? + } mpi::Signature::Unknown { mpis, rest } => { let keys: Vec = - (0..mpis.len()).map( - |i| format!("mpi{}", i)).collect(); + (0..mpis.len()).map(|i| format!("mpi{}", i)).collect(); self.dump_mpis( - output, &ii, - &mpis.iter().map(|m| { - m.value().iter().as_slice() - }).collect::>()[..], - &keys.iter().map(|k| k.as_str()) + output, + &ii, + &mpis + .iter() + .map(|m| m.value().iter().as_slice()) .collect::>()[..], + &keys.iter().map(|k| k.as_str()).collect::>()[..], )?; self.dump_mpis(output, &ii, &[&rest[..]], &["rest"])?; - }, + } // crypto::mpi::Signature is non-exhaustive. u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, } } - }, + } OnePassSig(ref o) => { writeln!(output, "{} Version: {}", i, o.version())?; @@ -548,69 +606,82 @@ impl PacketDumper { writeln!(output, "{} Hash algo: {}", i, o.hash_algo())?; writeln!(output, "{} Issuer: {}", i, o.issuer())?; writeln!(output, "{} Last: {}", i, o.last())?; - }, + } Trust(ref p) => { writeln!(output, "{} Value:", i)?; let mut hd = hex::Dumper::new( &mut output, - self.indentation_for_hexdump(&format!("{} ", i), 16)); + self.indentation_for_hexdump(&format!("{} ", i), 16), + ); hd.write_ascii(p.value())?; - }, + } UserID(ref u) => { - writeln!(output, "{} Value: {}", i, - String::from_utf8_lossy(u.value()))?; - }, + writeln!( + output, + "{} Value: {}", + i, + String::from_utf8_lossy(u.value()) + )?; + } UserAttribute(ref u) => { - use self::openpgp::packet::user_attribute::{Subpacket, Image}; + use self::openpgp::packet::user_attribute::{Image, Subpacket}; for subpacket in u.subpackets() { match subpacket { Ok(Subpacket::Image(image)) => match image { - Image::JPEG(data) => - writeln!(output, "{} JPEG: {} bytes", i, - data.len())?, - Image::Private(n, data) => - writeln!(output, - "{} Private image({}): {} bytes", i, - n, data.len())?, - Image::Unknown(n, data) => - writeln!(output, - "{} Unknown image({}): {} bytes", i, - n, data.len())?, + Image::JPEG(data) => { + writeln!(output, "{} JPEG: {} bytes", i, data.len())? + } + Image::Private(n, data) => writeln!( + output, + "{} Private image({}): {} bytes", + i, + n, + data.len() + )?, + Image::Unknown(n, data) => writeln!( + output, + "{} Unknown image({}): {} bytes", + i, + n, + data.len() + )?, }, - Ok(Subpacket::Unknown(n, data)) => - writeln!(output, - "{} Unknown subpacket({}): {} bytes", i, - n, data.len())?, - Err(e) => - writeln!(output, - "{} Invalid subpacket encoding: {}", i, - e)?, + Ok(Subpacket::Unknown(n, data)) => writeln!( + output, + "{} Unknown subpacket({}): {} bytes", + i, + n, + data.len() + )?, + Err(e) => writeln!(output, "{} Invalid subpacket encoding: {}", i, e)?, } } - }, + } - Marker(_) => { - }, + Marker(_) => {} Literal(ref l) => { writeln!(output, "{} Format: {}", i, l.format())?; if let Some(filename) = l.filename() { - writeln!(output, "{} Filename: {}", i, - String::from_utf8_lossy(filename))?; + writeln!( + output, + "{} Filename: {}", + i, + String::from_utf8_lossy(filename) + )?; } if let Some(timestamp) = l.date() { - writeln!(output, "{} Timestamp: {}", i, - timestamp.convert())?; + writeln!(output, "{} Timestamp: {}", i, timestamp.convert())?; } - }, + } CompressedData(ref c) => { writeln!(output, "{} Algorithm: {}", i, c.algo())?; - }, + } PKESK(ref p) => { writeln!(output, "{} Version: {}", i, p.version())?; @@ -622,88 +693,81 @@ impl PacketDumper { let ii = format!("{} ", i); match p.esk() { - mpi::Ciphertext::RSA { c } => - self.dump_mpis(output, &ii, - &[c.value()], - &["c"])?, - mpi::Ciphertext::ElGamal { e, c } => - self.dump_mpis(output, &ii, - &[e.value(), c.value()], - &["e", "c"])?, - mpi::Ciphertext::ECDH { e, key } => - self.dump_mpis(output, &ii, - &[e.value(), key], - &["e", "key"])?, + mpi::Ciphertext::RSA { c } => { + self.dump_mpis(output, &ii, &[c.value()], &["c"])? + } + mpi::Ciphertext::ElGamal { e, c } => { + self.dump_mpis(output, &ii, &[e.value(), c.value()], &["e", "c"])? + } + mpi::Ciphertext::ECDH { e, key } => { + self.dump_mpis(output, &ii, &[e.value(), key], &["e", "key"])? + } mpi::Ciphertext::Unknown { mpis, rest } => { let keys: Vec = - (0..mpis.len()).map( - |i| format!("mpi{}", i)).collect(); + (0..mpis.len()).map(|i| format!("mpi{}", i)).collect(); self.dump_mpis( - output, &ii, - &mpis.iter().map(|m| { - m.value().iter().as_slice() - }).collect::>()[..], - &keys.iter().map(|k| k.as_str()) + output, + &ii, + &mpis + .iter() + .map(|m| m.value().iter().as_slice()) .collect::>()[..], + &keys.iter().map(|k| k.as_str()).collect::>()[..], )?; self.dump_mpis(output, &ii, &[rest], &["rest"])?; - }, + } // crypto::mpi::Ciphertext is non-exhaustive. u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, } } - }, + } SKESK(ref s) => { writeln!(output, "{} Version: {}", i, s.version())?; match s { self::openpgp::packet::SKESK::V4(ref s) => { - writeln!(output, "{} Symmetric algo: {}", i, - s.symmetric_algo())?; + writeln!(output, "{} Symmetric algo: {}", i, s.symmetric_algo())?; write!(output, "{} S2K: ", i)?; self.dump_s2k(output, i, s.s2k())?; if let Ok(Some(esk)) = s.esk() { - writeln!(output, "{} ESK: {}", i, - hex::encode(esk))?; + writeln!(output, "{} ESK: {}", i, hex::encode(esk))?; } - }, + } self::openpgp::packet::SKESK::V5(ref s) => { - writeln!(output, "{} Symmetric algo: {}", i, - s.symmetric_algo())?; - writeln!(output, "{} AEAD: {}", i, - s.aead_algo())?; + writeln!(output, "{} Symmetric algo: {}", i, s.symmetric_algo())?; + writeln!(output, "{} AEAD: {}", i, s.aead_algo())?; write!(output, "{} S2K: ", i)?; self.dump_s2k(output, i, s.s2k())?; if let Ok(iv) = s.aead_iv() { - writeln!(output, "{} IV: {}", i, - hex::encode(iv))?; + writeln!(output, "{} IV: {}", i, hex::encode(iv))?; } if let Ok(Some(esk)) = s.esk() { - writeln!(output, "{} ESK: {}", i, - hex::encode(esk))?; + writeln!(output, "{} ESK: {}", i, hex::encode(esk))?; } - writeln!(output, "{} Digest: {}", i, - hex::encode(s.aead_digest()))?; - }, + writeln!(output, "{} Digest: {}", i, hex::encode(s.aead_digest()))?; + } // SKESK is non-exhaustive. u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, } - }, + } SEIP(ref s) => { writeln!(output, "{} Version: {}", i, s.version())?; - }, + } MDC(ref m) => { - writeln!(output, "{} Digest: {}", - i, hex::encode(m.digest()))?; - writeln!(output, "{} Computed digest: {}", - i, hex::encode(m.computed_digest()))?; - }, + writeln!(output, "{} Digest: {}", i, hex::encode(m.digest()))?; + writeln!( + output, + "{} Computed digest: {}", + i, + hex::encode(m.computed_digest()) + )?; + } AED(ref a) => { writeln!(output, "{} Version: {}", i, a.version())?; @@ -711,7 +775,7 @@ impl PacketDumper { writeln!(output, "{} AEAD: {}", i, a.aead())?; writeln!(output, "{} Chunk size: {}", i, a.chunk_size())?; writeln!(output, "{} IV: {}", i, hex::encode(a.iv()))?; - }, + } // openpgp::Packet is non-exhaustive. u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, @@ -725,11 +789,22 @@ impl PacketDumper { if let Some(map) = map { writeln!(output, "{}", i)?; - let mut hd = hex::Dumper::new(output, self.indentation_for_hexdump( - i, map.iter() - .map(|f| if f.name() == "body" { 16 } else { f.name().len() }) - .max() - .expect("we always have one entry"))); + let mut hd = hex::Dumper::new( + output, + self.indentation_for_hexdump( + i, + map.iter() + .map(|f| { + if f.name() == "body" { + 16 + } else { + f.name().len() + } + }) + .max() + .expect("we always have one entry"), + ), + ); for field in map.iter() { if field.name() == "body" { @@ -748,126 +823,190 @@ impl PacketDumper { Ok(()) } - fn dump_subpacket(&self, output: &mut dyn io::Write, i: &str, - s: &Subpacket, sig: &Signature) - -> Result<()> { + fn dump_subpacket( + &self, + output: &mut dyn io::Write, + i: &str, + s: &Subpacket, + sig: &Signature, + ) -> Result<()> { use self::SubpacketValue::*; let hexdump_unknown = |output: &mut dyn io::Write, buf| -> Result<()> { - let mut hd = hex::Dumper::new(output, self.indentation_for_hexdump( - &format!("{} ", i), 0)); + let mut hd = hex::Dumper::new( + output, + self.indentation_for_hexdump(&format!("{} ", i), 0), + ); hd.write_labeled(buf, |_, _| None)?; Ok(()) }; match s.value() { Unknown { body, .. } => { - writeln!(output, "{} {:?}{}:", i, s.tag(), - if s.critical() { " (critical)" } else { "" })?; + writeln!( + output, + "{} {:?}{}:", + i, + s.tag(), + if s.critical() { " (critical)" } else { "" } + )?; hexdump_unknown(output, body.as_slice())?; - }, - SignatureCreationTime(t) => - write!(output, "{} Signature creation time: {}", i, - (*t).convert())?, - SignatureExpirationTime(t) => - write!(output, "{} Signature expiration time: {} ({})", - i, t.convert(), - if let Some(creation) = sig.signature_creation_time() { - (creation + std::time::Duration::from(*t)) - .convert().to_string() - } else { - " (no Signature Creation Time subpacket)".into() - })?, - ExportableCertification(e) => - write!(output, "{} Exportable certification: {}", i, e)?, - TrustSignature{level, trust} => - write!(output, "{} Trust signature: level {} trust {}", i, - level, trust)?, - RegularExpression(ref r) => - write!(output, "{} Regular expression: {}", i, - String::from_utf8_lossy(r))?, - Revocable(r) => - write!(output, "{} Revocable: {}", i, r)?, - KeyExpirationTime(t) => - write!(output, "{} Key expiration time: {}", i, - t.convert())?, - PreferredSymmetricAlgorithms(ref c) => - write!(output, "{} Symmetric algo preferences: {}", i, - c.iter().map(|c| format!("{:?}", c)) - .collect::>().join(", "))?, + } + SignatureCreationTime(t) => write!( + output, + "{} Signature creation time: {}", + i, + (*t).convert() + )?, + SignatureExpirationTime(t) => write!( + output, + "{} Signature expiration time: {} ({})", + i, + t.convert(), + if let Some(creation) = sig.signature_creation_time() { + (creation + std::time::Duration::from(*t)) + .convert() + .to_string() + } else { + " (no Signature Creation Time subpacket)".into() + } + )?, + ExportableCertification(e) => { + write!(output, "{} Exportable certification: {}", i, e)? + } + TrustSignature { level, trust } => write!( + output, + "{} Trust signature: level {} trust {}", + i, level, trust + )?, + RegularExpression(ref r) => write!( + output, + "{} Regular expression: {}", + i, + String::from_utf8_lossy(r) + )?, + Revocable(r) => write!(output, "{} Revocable: {}", i, r)?, + KeyExpirationTime(t) => { + write!(output, "{} Key expiration time: {}", i, t.convert())? + } + PreferredSymmetricAlgorithms(ref c) => write!( + output, + "{} Symmetric algo preferences: {}", + i, + c.iter() + .map(|c| format!("{:?}", c)) + .collect::>() + .join(", ") + )?, RevocationKey(rk) => { let (pk_algo, fp) = rk.revoker(); - write!(output, - "{} Revocation key: {}/{}", i, - fp, pk_algo)?; + write!(output, "{} Revocation key: {}/{}", i, fp, pk_algo)?; if rk.sensitive() { write!(output, ", sensitive")?; } - }, - Issuer(ref is) => - write!(output, "{} Issuer: {}", i, is)?, - NotationData(n) => if n.flags().human_readable() { - write!(output, "{} Notation: {}", i, n)?; - if s.critical() { - write!(output, " (critical)")?; - } - writeln!(output)?; - } else { - write!(output, "{} Notation: {}", i, n.name())?; - let flags = format!("{:?}", n.flags()); - if ! flags.is_empty() { - write!(output, "{}", flags)?; - } - if s.critical() { - write!(output, " (critical)")?; - } - writeln!(output)?; - hexdump_unknown(output, n.value())?; - }, - PreferredHashAlgorithms(ref h) => - write!(output, "{} Hash preferences: {}", i, - h.iter().map(|h| format!("{:?}", h)) - .collect::>().join(", "))?, - PreferredCompressionAlgorithms(ref c) => - write!(output, "{} Compression preferences: {}", i, - c.iter().map(|c| format!("{:?}", c)) - .collect::>().join(", "))?, - KeyServerPreferences(ref p) => - write!(output, "{} Keyserver preferences: {:?}", i, p)?, - PreferredKeyServer(ref k) => - write!(output, "{} Preferred keyserver: {}", i, - String::from_utf8_lossy(k))?, - PrimaryUserID(p) => - write!(output, "{} Primary User ID: {}", i, p)?, - PolicyURI(ref p) => - write!(output, "{} Policy URI: {}", i, - String::from_utf8_lossy(p))?, - KeyFlags(ref k) => - write!(output, "{} Key flags: {:?}", i, k)?, - SignersUserID(ref u) => - write!(output, "{} Signer's User ID: {}", i, - String::from_utf8_lossy(u))?, - ReasonForRevocation{code, ref reason} => { - let reason = String::from_utf8_lossy(reason); - write!(output, "{} Reason for revocation: {}{}{}", i, code, - if reason.len() > 0 { ", " } else { "" }, reason)? } - Features(ref f) => - write!(output, "{} Features: {:?}", i, f)?, - SignatureTarget{pk_algo, hash_algo, ref digest} => - write!(output, "{} Signature target: {}, {}, {}", i, - pk_algo, hash_algo, hex::encode(digest))?, + Issuer(ref is) => write!(output, "{} Issuer: {}", i, is)?, + NotationData(n) => { + if n.flags().human_readable() { + write!(output, "{} Notation: {}", i, n)?; + if s.critical() { + write!(output, " (critical)")?; + } + writeln!(output)?; + } else { + write!(output, "{} Notation: {}", i, n.name())?; + let flags = format!("{:?}", n.flags()); + if !flags.is_empty() { + write!(output, "{}", flags)?; + } + if s.critical() { + write!(output, " (critical)")?; + } + writeln!(output)?; + hexdump_unknown(output, n.value())?; + } + } + PreferredHashAlgorithms(ref h) => write!( + output, + "{} Hash preferences: {}", + i, + h.iter() + .map(|h| format!("{:?}", h)) + .collect::>() + .join(", ") + )?, + PreferredCompressionAlgorithms(ref c) => write!( + output, + "{} Compression preferences: {}", + i, + c.iter() + .map(|c| format!("{:?}", c)) + .collect::>() + .join(", ") + )?, + KeyServerPreferences(ref p) => { + write!(output, "{} Keyserver preferences: {:?}", i, p)? + } + PreferredKeyServer(ref k) => write!( + output, + "{} Preferred keyserver: {}", + i, + String::from_utf8_lossy(k) + )?, + PrimaryUserID(p) => write!(output, "{} Primary User ID: {}", i, p)?, + PolicyURI(ref p) => write!( + output, + "{} Policy URI: {}", + i, + String::from_utf8_lossy(p) + )?, + KeyFlags(ref k) => write!(output, "{} Key flags: {:?}", i, k)?, + SignersUserID(ref u) => write!( + output, + "{} Signer's User ID: {}", + i, + String::from_utf8_lossy(u) + )?, + ReasonForRevocation { code, ref reason } => { + let reason = String::from_utf8_lossy(reason); + write!( + output, + "{} Reason for revocation: {}{}{}", + i, + code, + if reason.len() > 0 { ", " } else { "" }, + reason + )? + } + Features(ref f) => write!(output, "{} Features: {:?}", i, f)?, + SignatureTarget { + pk_algo, + hash_algo, + ref digest, + } => write!( + output, + "{} Signature target: {}, {}, {}", + i, + pk_algo, + hash_algo, + hex::encode(digest) + )?, EmbeddedSignature(_) => // Embedded signature is dumped below. - write!(output, "{} Embedded signature: ", i)?, - IssuerFingerprint(ref fp) => - write!(output, "{} Issuer Fingerprint: {}", i, fp)?, - PreferredAEADAlgorithms(ref c) => - write!(output, "{} AEAD preferences: {}", i, - c.iter().map(|c| format!("{:?}", c)) - .collect::>().join(", "))?, - IntendedRecipient(ref fp) => - write!(output, "{} Intended Recipient: {}", i, fp)?, + { + write!(output, "{} Embedded signature: ", i)? + } + IssuerFingerprint(ref fp) => write!(output, "{} Issuer Fingerprint: {}", i, fp)?, + PreferredAEADAlgorithms(ref c) => write!( + output, + "{} AEAD preferences: {}", + i, + c.iter() + .map(|c| format!("{:?}", c)) + .collect::>() + .join(", ") + )?, + IntendedRecipient(ref fp) => write!(output, "{} Intended Recipient: {}", i, fp)?, AttestedCertifications(digests) => { write!(output, "{} Attested Certifications:", i)?; if digests.is_empty() { @@ -878,7 +1017,7 @@ impl PacketDumper { writeln!(output, "{} {}", i, hex::encode(d))?; } } - }, + } // SubpacketValue is non-exhaustive. u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, @@ -894,9 +1033,8 @@ impl PacketDumper { writeln!(output)?; let indent = format!("{} ", i); write!(output, "{}", indent)?; - self.dump_packet(output, &indent, None, &sig.clone().into(), - None, None)?; - }, + self.dump_packet(output, &indent, None, &sig.clone().into(), None, None)?; + } _ => { if s.critical() { write!(output, " (critical)")?; @@ -908,40 +1046,43 @@ impl PacketDumper { Ok(()) } - fn dump_s2k(&self, output: &mut dyn io::Write, i: &str, s2k: &S2K) - -> Result<()> { + fn dump_s2k(&self, output: &mut dyn io::Write, i: &str, s2k: &S2K) -> Result<()> { use self::S2K::*; #[allow(deprecated)] match s2k { Simple { hash } => { writeln!(output, "Simple")?; writeln!(output, "{} Hash: {}", i, hash)?; - }, + } Salted { hash, ref salt } => { writeln!(output, "Salted")?; writeln!(output, "{} Hash: {}", i, hash)?; writeln!(output, "{} Salt: {}", i, hex::encode(salt))?; - }, - Iterated { hash, ref salt, hash_bytes } => { + } + Iterated { + hash, + ref salt, + hash_bytes, + } => { writeln!(output, "Iterated")?; writeln!(output, "{} Hash: {}", i, hash)?; writeln!(output, "{} Salt: {}", i, hex::encode(salt))?; writeln!(output, "{} Hash bytes: {}", i, hash_bytes)?; - }, + } Private { tag, parameters } => { writeln!(output, "Private")?; writeln!(output, "{} Tag: {}", i, tag)?; if let Some(p) = parameters.as_ref() { writeln!(output, "{} Parameters: {:?}", i, p)?; } - }, + } Unknown { tag, parameters } => { writeln!(output, "Unknown")?; writeln!(output, "{} Tag: {}", i, tag)?; if let Some(p) = parameters.as_ref() { writeln!(output, "{} Parameters: {:?}", i, p)?; } - }, + } // S2K is non-exhaustive u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, @@ -949,8 +1090,13 @@ impl PacketDumper { Ok(()) } - fn dump_mpis(&self, output: &mut dyn io::Write, i: &str, - chunks: &[&[u8]], keys: &[&str]) -> Result<()> { + fn dump_mpis( + &self, + output: &mut dyn io::Write, + i: &str, + chunks: &[&[u8]], + keys: &[&str], + ) -> Result<()> { assert_eq!(chunks.len(), keys.len()); if chunks.is_empty() { return Ok(()); @@ -960,8 +1106,7 @@ impl PacketDumper { for (chunk, key) in chunks.iter().zip(keys.iter()) { writeln!(output, "{}", i)?; - let mut hd = hex::Dumper::new( - Vec::new(), self.indentation_for_hexdump(i, max_key_len)); + let mut hd = hex::Dumper::new(Vec::new(), self.indentation_for_hexdump(i, max_key_len)); hd.write(*chunk, *key)?; output.write_all(&hd.into_inner())?; } @@ -980,11 +1125,10 @@ impl PacketDumper { self.width as isize - 63 // Length of address, hex digits, and whitespace. - max_label_len as isize, - i.len() as isize), + i.len() as isize, + ), ) as usize; format!("{} ", &i.chars().take(amount).collect::()) } - - } diff --git a/src/i18n.rs b/src/i18n.rs index 903b0d0..d0cf449 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -1,10 +1,9 @@ use handlebars::{ - Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderError + Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderError, }; use std::io; - pub struct I18NHelper { catalogs: Vec<(&'static str, gettext::Catalog)>, } @@ -14,11 +13,9 @@ impl I18NHelper { Self { catalogs } } - pub fn get_catalog( - &self, - lang: &str, - ) -> &gettext::Catalog { - let (_, ref catalog) = self.catalogs + pub fn get_catalog(&self, lang: &str) -> &gettext::Catalog { + let (_, ref catalog) = self + .catalogs .iter() .find(|(candidate, _)| *candidate == lang) .unwrap_or_else(|| self.catalogs.get(0).unwrap()); @@ -75,10 +72,8 @@ impl HelperDef for I18NHelper { let rerender = h .param(1) - .and_then(|p| p - .relative_path() - .map(|v| v == "rerender") - ).unwrap_or(false); + .and_then(|p| p.relative_path().map(|v| v == "rerender")) + .unwrap_or(false); let lang = context .data() @@ -89,14 +84,15 @@ impl HelperDef for I18NHelper { fn render_error_with(e: E) -> RenderError where - E: std::error::Error + Send + Sync + 'static + E: std::error::Error + Send + Sync + 'static, { RenderError::from_error("Failed to render", e) } let response = self.lookup(lang, id); if rerender { let data = rcx.evaluate(context, "this").unwrap(); - let response = reg.render_template(response, data.as_json()) + let response = reg + .render_template(response, data.as_json()) .map_err(render_error_with)?; out.write(&response).map_err(render_error_with)?; } else { diff --git a/src/i18n_helpers.rs b/src/i18n_helpers.rs index 3b3d41c..c85a4ec 100644 --- a/src/i18n_helpers.rs +++ b/src/i18n_helpers.rs @@ -1,16 +1,21 @@ -use rocket_i18n::I18n; use crate::database::Query; use gettext_macros::i18n; +use rocket_i18n::I18n; pub fn describe_query_error(i18n: &I18n, q: &Query) -> String { match q { - Query::ByFingerprint(fpr) => - i18n!(i18n.catalog, "No key found for fingerprint {}"; fpr), - Query::ByKeyID(key_id) => - i18n!(i18n.catalog, "No key found for key id {}"; key_id), - Query::ByEmail(email) => - i18n!(i18n.catalog, "No key found for email address {}"; email), - Query::InvalidShort() => i18n!(i18n.catalog, "Search by Short Key ID is not supported."), + Query::ByFingerprint(fpr) => { + i18n!(i18n.catalog, "No key found for fingerprint {}"; fpr) + } + Query::ByKeyID(key_id) => { + i18n!(i18n.catalog, "No key found for key id {}"; key_id) + } + Query::ByEmail(email) => { + i18n!(i18n.catalog, "No key found for email address {}"; email) + } + Query::InvalidShort() => { + i18n!(i18n.catalog, "Search by Short Key ID is not supported.") + } Query::Invalid() => i18n!(i18n.catalog, "Invalid search query."), } } diff --git a/src/mail.rs b/src/mail.rs index ef36ee2..7b48989 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -1,14 +1,14 @@ -use std::path::{PathBuf, Path}; +use std::path::{Path, PathBuf}; +use crate::counters; use handlebars::Handlebars; -use lettre::{Transport as LettreTransport, SendmailTransport, file::FileTransport}; -use lettre::builder::{EmailBuilder, PartBuilder, Mailbox, MimeMultipartType}; +use lettre::builder::{EmailBuilder, Mailbox, MimeMultipartType, PartBuilder}; +use lettre::{file::FileTransport, SendmailTransport, Transport as LettreTransport}; use serde::Serialize; use uuid::Uuid; -use crate::counters; -use rocket_i18n::I18n; use gettext_macros::i18n; +use rocket_i18n::I18n; use rfc2047::rfc2047_encode; @@ -67,17 +67,26 @@ impl Service { /// Sends mail by storing it in the given directory. pub fn filemail(from: &str, base_uri: &str, template_dir: &Path, path: &Path) -> Result { - Self::new(from, base_uri, template_dir, Transport::Filemail(path.to_owned())) + Self::new( + from, + base_uri, + template_dir, + Transport::Filemail(path.to_owned()), + ) } - fn new(from: &str, base_uri: &str, template_dir: &Path, transport: Transport) - -> Result { + fn new(from: &str, base_uri: &str, template_dir: &Path, transport: Transport) -> Result { let templates = template_helpers::load_handlebars(template_dir)?; - let domain = - url::Url::parse(base_uri) - ?.host_str().ok_or_else(|| anyhow!("No host in base-URI")) - ?.to_string(); - Ok(Self { from: from.into(), domain, templates, transport }) + let domain = url::Url::parse(base_uri)? + .host_str() + .ok_or_else(|| anyhow!("No host in base-URI"))? + .to_string(); + Ok(Self { + from: from.into(), + domain, + templates, + transport, + }) } pub fn send_verification( @@ -86,7 +95,7 @@ impl Service { base_uri: &str, tpk_name: String, userid: &Email, - token: &str + token: &str, ) -> Result<()> { let ctx = context::Verification { lang: i18n.lang.to_string(), @@ -151,7 +160,7 @@ impl Service { base_uri: &str, tpk_name: String, userid: &Email, - token: &str + token: &str, ) -> Result<()> { let ctx = context::Welcome { lang: "en".to_owned(), @@ -176,12 +185,16 @@ impl Service { &self, template: &str, locale: &str, - ctx: impl Serialize + ctx: impl Serialize, ) -> Result<(String, String)> { - let html = self.templates.render(&format!("{}/{}.htm", locale, template), &ctx) + let html = self + .templates + .render(&format!("{}/{}.htm", locale, template), &ctx) .or_else(|_| self.templates.render(&format!("{}.htm", template), &ctx)) .map_err(|_| anyhow!("Email template failed to render"))?; - let txt = self.templates.render(&format!("{}/{}.txt", locale, template), &ctx) + let txt = self + .templates + .render(&format!("{}/{}.txt", locale, template), &ctx) .or_else(|_| self.templates.render(&format!("{}.txt", template), &ctx)) .map_err(|_| anyhow!("Email template failed to render"))?; @@ -194,7 +207,7 @@ impl Service { subject: &str, template: &str, locale: &str, - ctx: impl Serialize + ctx: impl Serialize, ) -> Result<()> { let (html, txt) = self.render_template(template, locale, ctx)?; @@ -235,18 +248,17 @@ impl Service { Transport::Sendmail => { let mut transport = SendmailTransport::new(); transport.send(email)?; - }, + } Transport::Filemail(ref path) => { let mut transport = FileTransport::new(path); transport.send(email)?; - }, + } } Ok(()) } } - // for some reason, this is no longer public in lettre itself // FIXME replace with builtin struct on lettre update // see https://github.com/lettre/lettre/blob/master/lettre/src/file/mod.rs#L41 @@ -281,9 +293,9 @@ pub fn pop_mail(dir: &Path) -> Result> { #[cfg(test)] mod test { use super::*; - use tempfile::{tempdir, TempDir}; - use gettext_macros::{include_i18n}; + use gettext_macros::include_i18n; use std::str::FromStr; + use tempfile::{tempdir, TempDir}; const BASEDIR: &str = "http://localhost/"; const FROM: &str = "test@localhost"; @@ -291,12 +303,22 @@ mod test { fn configure_i18n(lang: &'static str) -> I18n { let langs = include_i18n!(); - let catalog = langs.clone().into_iter().find(|(l, _)| *l == lang).unwrap().1; + let catalog = langs + .clone() + .into_iter() + .find(|(l, _)| *l == lang) + .unwrap() + .1; rocket_i18n::I18n { catalog, lang } } fn configure_mail() -> (Service, TempDir) { - let template_dir: PathBuf = ::std::env::current_dir().unwrap().join("dist/email-templates").to_str().unwrap().into(); + let template_dir: PathBuf = ::std::env::current_dir() + .unwrap() + .join("dist/email-templates") + .to_str() + .unwrap() + .into(); let tempdir = tempdir().unwrap(); let service = Service::filemail(FROM, BASEDIR, &template_dir, tempdir.path()).unwrap(); (service, tempdir) @@ -328,7 +350,9 @@ mod test { assert!(headers.contains(&("Content-Type", "text/html; charset=utf-8"))); assert!(headers.contains(&("From", ""))); assert!(headers.contains(&("To", ""))); - assert_header(&headers, "Content-Type", |v| v.starts_with("multipart/alternative")); + assert_header(&headers, "Content-Type", |v| { + v.starts_with("multipart/alternative") + }); assert_header(&headers, "Date", |v| v.contains("+0000")); assert_header(&headers, "Message-ID", |v| v.contains("@localhost>")); } @@ -345,7 +369,14 @@ mod test { let i18n = configure_i18n("en"); let recipient = Email::from_str(TO).unwrap(); - mail.send_verification(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap(); + mail.send_verification( + &i18n, + "test", + "fingerprintoo".to_owned(), + &recipient, + "token", + ) + .unwrap(); let mail_content = pop_mail(tempdir.path()).unwrap().unwrap(); check_headers(&mail_content); @@ -363,7 +394,14 @@ mod test { let i18n = configure_i18n("ja"); let recipient = Email::from_str(TO).unwrap(); - mail.send_verification(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap(); + mail.send_verification( + &i18n, + "test", + "fingerprintoo".to_owned(), + &recipient, + "token", + ) + .unwrap(); let mail_content = pop_mail(tempdir.path()).unwrap().unwrap(); check_headers(&mail_content); @@ -373,8 +411,9 @@ mod test { assert!(mail_content.contains("test/verify/token")); assert!(mail_content.contains("test/about")); assert!(mail_content.contains("あなたのメールアド")); - assert!(mail_content.contains("Subject: =?utf-8?q?localhost=E3=81=AE=E3=81=82=E3=81=AA=E3=81=9F=E3=81=AE?=")); - + assert!(mail_content.contains( + "Subject: =?utf-8?q?localhost=E3=81=AE=E3=81=82=E3=81=AA=E3=81=9F=E3=81=AE?=" + )); } #[test] @@ -383,7 +422,14 @@ mod test { let i18n = configure_i18n("en"); let recipient = Email::from_str(TO).unwrap(); - mail.send_manage_token(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap(); + mail.send_manage_token( + &i18n, + "test", + "fingerprintoo".to_owned(), + &recipient, + "token", + ) + .unwrap(); let mail_content = pop_mail(tempdir.path()).unwrap().unwrap(); check_headers(&mail_content); @@ -401,7 +447,14 @@ mod test { let i18n = configure_i18n("ja"); let recipient = Email::from_str(TO).unwrap(); - mail.send_manage_token(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap(); + mail.send_manage_token( + &i18n, + "test", + "fingerprintoo".to_owned(), + &recipient, + "token", + ) + .unwrap(); let mail_content = pop_mail(tempdir.path()).unwrap().unwrap(); check_headers(&mail_content); @@ -412,7 +465,9 @@ mod test { assert!(mail_content.contains("testtoken")); assert!(mail_content.contains("test/about")); assert!(mail_content.contains("この鍵の掲示されたア")); - assert!(mail_content.contains("Subject: =?utf-8?q?localhost=E3=81=AE=E9=8D=B5=E3=82=92=E7=AE=A1=E7=90=86?=")); + assert!(mail_content.contains( + "Subject: =?utf-8?q?localhost=E3=81=AE=E9=8D=B5=E3=82=92=E7=AE=A1=E7=90=86?=" + )); } #[test] @@ -420,7 +475,8 @@ mod test { let (mail, tempdir) = configure_mail(); let recipient = Email::from_str(TO).unwrap(); - mail.send_welcome("test", "fingerprintoo".to_owned(), &recipient, "token").unwrap(); + mail.send_welcome("test", "fingerprintoo".to_owned(), &recipient, "token") + .unwrap(); let mail_content = pop_mail(tempdir.path()).unwrap().unwrap(); check_headers(&mail_content); @@ -432,4 +488,3 @@ mod test { assert!(mail_content.contains("first time")); } } - diff --git a/src/main.rs b/src/main.rs index 20e0037..51eb304 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ #[macro_use] extern crate anyhow; -use anyhow::Result as Result; +use anyhow::Result; #[macro_use] extern crate serde_derive; @@ -23,18 +23,18 @@ init_i18n!("hagrid", en, de, ja); #[cfg(not(debug_assertions))] init_i18n!("hagrid", en, de, fr, it, ja, nb, pl, tr, zh_Hans, ko, nl, ru, ar, sv, es, ro); -mod mail; mod anonymize_utils; -mod tokens; -mod sealed_state; -mod rate_limiter; -mod dump; mod counters; +mod dump; +mod gettext_strings; mod i18n; mod i18n_helpers; -mod gettext_strings; -mod web; +mod mail; +mod rate_limiter; +mod sealed_state; mod template_helpers; +mod tokens; +mod web; #[launch] fn rocket() -> _ { diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs index c066d77..5a43ab9 100644 --- a/src/rate_limiter.rs +++ b/src/rate_limiter.rs @@ -1,6 +1,6 @@ -use std::sync::Mutex; use std::collections::HashMap; -use std::time::{Instant,Duration}; +use std::sync::Mutex; +use std::time::{Duration, Instant}; pub struct RateLimiter { locked_map: Mutex>, @@ -23,11 +23,12 @@ impl RateLimiter { self.maybe_cleanup(); let mut locked_map = self.locked_map.lock().unwrap(); - let action_ok = locked_map.get(&identifier) + let action_ok = locked_map + .get(&identifier) .map(|instant| instant.elapsed()) .map(|duration| duration >= self.timeout) .unwrap_or(true); - if action_ok { + if action_ok { locked_map.insert(identifier, Instant::now()); } action_ok @@ -35,7 +36,8 @@ impl RateLimiter { pub fn action_check(&self, identifier: String) -> bool { let locked_map = self.locked_map.lock().unwrap(); - locked_map.get(&identifier) + locked_map + .get(&identifier) .map(|instant| instant.elapsed()) .map(|duration| duration >= self.timeout) .unwrap_or(true) diff --git a/src/sealed_state.rs b/src/sealed_state.rs index b794666..ec548c6 100644 --- a/src/sealed_state.rs +++ b/src/sealed_state.rs @@ -1,8 +1,8 @@ -use ring::aead::{seal_in_place, open_in_place, Algorithm, AES_256_GCM}; +use ring::aead::{open_in_place, seal_in_place, Algorithm, AES_256_GCM}; use ring::aead::{OpeningKey, SealingKey}; -use ring::rand::{SecureRandom, SystemRandom}; -use ring::hmac; use ring::digest; +use ring::hmac; +use ring::rand::{SecureRandom, SystemRandom}; // Keep these in sync, and keep the key len synced with the `private` docs as // well as the `KEYS_INFO` const in secure::Key. @@ -14,7 +14,7 @@ pub struct SealedState { opening_key: OpeningKey, } -impl SealedState { +impl SealedState { pub fn new(secret: &str) -> Self { let salt = hmac::SigningKey::new(&digest::SHA256, b"hagrid"); let mut key = vec![0; 32]; @@ -23,7 +23,10 @@ impl SealedState { let sealing_key = SealingKey::new(ALGO, key.as_ref()).expect("sealing key creation"); let opening_key = OpeningKey::new(ALGO, key.as_ref()).expect("sealing key creation"); - SealedState { sealing_key, opening_key } + SealedState { + sealing_key, + opening_key, + } } pub fn unseal(&self, mut data: Vec) -> Result { @@ -43,7 +46,9 @@ impl SealedState { data = vec![0; NONCE_LEN + input.len() + overhead]; let (nonce, in_out) = data.split_at_mut(NONCE_LEN); - SystemRandom::new().fill(nonce).expect("couldn't random fill nonce"); + SystemRandom::new() + .fill(nonce) + .expect("couldn't random fill nonce"); in_out[..input.len()].copy_from_slice(input.as_bytes()); seal_in_place(&self.sealing_key, nonce, &[], in_out, overhead).expect("in-place seal") diff --git a/src/template_helpers.rs b/src/template_helpers.rs index c3ce6bd..06dc3ae 100644 --- a/src/template_helpers.rs +++ b/src/template_helpers.rs @@ -1,12 +1,12 @@ -use std::path::{Path, PathBuf}; use std::collections::HashSet; +use std::path::{Path, PathBuf}; use handlebars::Handlebars; use gettext_macros::include_i18n; -use crate::Result; use crate::i18n::I18NHelper; +use crate::Result; #[derive(Debug)] pub struct TemplateOverrides(String, HashSet); @@ -17,7 +17,7 @@ impl TemplateOverrides { .map(|vec| Self(localized_dir.to_owned(), vec)) } - pub fn get_template_override(&self, lang: &str, tmpl: &str) -> Option { + pub fn get_template_override(&self, lang: &str, tmpl: &str) -> Option { let template_name = format!("{}/{}/{}", self.0, lang, tmpl); if self.1.contains(&template_name) { println!("{}", &template_name); @@ -28,7 +28,10 @@ impl TemplateOverrides { } } -fn load_localized_template_names(template_path: &Path, localized_dir: &str) -> Result> { +fn load_localized_template_names( + template_path: &Path, + localized_dir: &str, +) -> Result> { let language_glob = template_path.join(localized_dir).join("*"); glob::glob(language_glob.to_str().expect("valid glob path string")) .unwrap() @@ -41,11 +44,12 @@ fn load_localized_template_names(template_path: &Path, localized_dir: &str) -> R .flatten() .map(move |path| { // TODO this is a hack - let template_name = remove_extension(remove_extension(path.strip_prefix(&template_path)?)); + let template_name = + remove_extension(remove_extension(path.strip_prefix(&template_path)?)); Ok(template_name.to_string_lossy().into_owned()) }) - }) - .collect() + }) + .collect() } pub fn load_handlebars(template_dir: &Path) -> Result> { @@ -71,12 +75,11 @@ fn remove_extension>(path: P) -> PathBuf { let path = path.as_ref(); let stem = match path.file_stem() { Some(stem) => stem, - None => return path.to_path_buf() + None => return path.to_path_buf(), }; match path.parent() { Some(parent) => parent.join(stem), - None => PathBuf::from(stem) + None => PathBuf::from(stem), } } - diff --git a/src/tokens.rs b/src/tokens.rs index cdc9f87..c22d1b7 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -1,17 +1,16 @@ use crate::sealed_state::SealedState; -use serde::{Serialize,de::DeserializeOwned}; use crate::Result; +use serde::{de::DeserializeOwned, Serialize}; -pub trait StatelessSerializable : Serialize + DeserializeOwned { -} +pub trait StatelessSerializable: Serialize + DeserializeOwned {} pub struct Service { sealed_state: SealedState, validity: u64, } -#[derive(Serialize,Deserialize)] +#[derive(Serialize, Deserialize)] struct Token { #[serde(rename = "c")] creation: u64, @@ -22,7 +21,10 @@ struct Token { impl Service { pub fn init(secret: &str, validity: u64) -> Self { let sealed_state = SealedState::new(secret); - Service { sealed_state, validity } + Service { + sealed_state, + validity, + } } pub fn create(&self, payload_content: &impl StatelessSerializable) -> String { @@ -37,13 +39,17 @@ impl Service { } pub fn check(&self, token_encoded: &str) -> Result - where T: StatelessSerializable { + where + T: StatelessSerializable, + { let token_sealed = base64::decode_config(&token_encoded, base64::URL_SAFE_NO_PAD) .map_err(|_| anyhow!("invalid b64"))?; - let token_str = self.sealed_state.unseal(token_sealed) + let token_str = self + .sealed_state + .unseal(token_sealed) .map_err(|_| anyhow!("failed to validate"))?; - let token: Token = serde_json::from_str(&token_str) - .map_err(|_| anyhow!("failed to deserialize"))?; + let token: Token = + serde_json::from_str(&token_str).map_err(|_| anyhow!("failed to deserialize"))?; let elapsed = current_time() - token.creation; if elapsed > self.validity { @@ -55,13 +61,15 @@ impl Service { Ok(payload) } - } #[cfg(not(test))] fn current_time() -> u64 { use std::time::SystemTime; - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() } #[cfg(test)] @@ -73,23 +81,23 @@ fn current_time() -> u64 { mod tests { use super::*; - #[derive(Debug,Serialize,Deserialize,Clone,PartialEq)] + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] struct TestStruct1 { payload: String, } - impl StatelessSerializable for TestStruct1 { - } + impl StatelessSerializable for TestStruct1 {} - #[derive(Debug,Serialize,Deserialize,Clone,PartialEq)] + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] struct TestStruct2 { something: String, } - impl StatelessSerializable for TestStruct2 { - } + impl StatelessSerializable for TestStruct2 {} #[test] fn test_create_check() { - let payload = TestStruct1 { payload: "hello".to_owned() }; + let payload = TestStruct1 { + payload: "hello".to_owned(), + }; let mt = Service::init("secret", 60); let token = mt.create(&payload); // println!("{}", &token); @@ -102,7 +110,9 @@ mod tests { #[test] fn test_ok() { - let payload = TestStruct1 { payload: "hello".to_owned() }; + let payload = TestStruct1 { + payload: "hello".to_owned(), + }; let token = "rwM_S9gZaRQaf6DLvmWtZSipQhH_G5ronSIJv2FrMdwGBPSYYQ-1jaP58dTHU5WuC14vb8jxmz2Xf_b3pqzpCGTEJj9drm4t"; let mt = Service::init("secret", 60); @@ -113,7 +123,9 @@ mod tests { #[test] fn test_bad_type() { - let payload = TestStruct1 { payload: "hello".to_owned() }; + let payload = TestStruct1 { + payload: "hello".to_owned(), + }; let mt = Service::init("secret", 60); let token = mt.create(&payload); diff --git a/src/web/debug_web.rs b/src/web/debug_web.rs index adc466d..8ce1107 100644 --- a/src/web/debug_web.rs +++ b/src/web/debug_web.rs @@ -3,17 +3,13 @@ use std::io; use rocket_i18n::I18n; use crate::dump::{self, Kind}; -use crate::web::MyResponse; use crate::i18n_helpers::describe_query_error; +use crate::web::MyResponse; use crate::database::{Database, KeyDatabase, Query}; #[get("/debug?")] -pub fn debug_info( - db: &rocket::State, - i18n: I18n, - q: String, -) -> MyResponse { +pub fn debug_info(db: &rocket::State, i18n: I18n, q: String) -> MyResponse { let query = match q.parse::() { Ok(query) => query, Err(_) => return MyResponse::bad_request_plain("bad request"), @@ -38,11 +34,9 @@ pub fn debug_info( 32 * 4 + 80, ); match dump_result { - Ok(Kind::Cert) => { - match String::from_utf8(result) { - Ok(dump_text) => MyResponse::plain(dump_text), - Err(e) => MyResponse::ise(e.into()), - } + Ok(Kind::Cert) => match String::from_utf8(result) { + Ok(dump_text) => MyResponse::plain(dump_text), + Err(e) => MyResponse::ise(e.into()), }, Ok(_) => MyResponse::ise(anyhow!("Internal parsing error!")), Err(e) => MyResponse::ise(e), diff --git a/src/web/hkp.rs b/src/web/hkp.rs index d1bf80f..a0f8fdf 100644 --- a/src/web/hkp.rs +++ b/src/web/hkp.rs @@ -1,29 +1,29 @@ use std::fmt; -use std::time::SystemTime; use std::collections::HashMap; +use std::time::SystemTime; -use rocket::Data; use rocket::form::{Form, ValueField}; -use rocket::outcome::Outcome; use rocket::http::{ContentType, Status}; -use rocket::request::{self, Request, FromRequest}; +use rocket::outcome::Outcome; +use rocket::request::{self, FromRequest, Request}; +use rocket::Data; use rocket_i18n::I18n; -use url::percent_encoding::{DEFAULT_ENCODE_SET, utf8_percent_encode}; +use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; -use crate::database::{Database, Query, KeyDatabase}; use crate::database::types::{Email, Fingerprint, KeyID}; +use crate::database::{Database, KeyDatabase, Query}; -use crate::rate_limiter::RateLimiter; use crate::i18n_helpers::describe_query_error; +use crate::rate_limiter::RateLimiter; use crate::tokens; -use crate::web; use crate::mail; -use crate::web::{RequestOrigin, MyResponse, vks_web}; -use crate::web::vks::response::UploadResponse; +use crate::web; use crate::web::vks::response::EmailStatus; +use crate::web::vks::response::UploadResponse; +use crate::web::{vks_web, MyResponse, RequestOrigin}; #[derive(Debug)] pub enum Hkp { @@ -31,17 +31,17 @@ pub enum Hkp { KeyID { keyid: KeyID, index: bool }, ShortKeyID { query: String, index: bool }, Email { email: Email, index: bool }, - Invalid { query: String, }, + Invalid { query: String }, } impl fmt::Display for Hkp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Hkp::Fingerprint{ ref fpr,.. } => write!(f, "{}", fpr), - Hkp::KeyID{ ref keyid,.. } => write!(f, "{}", keyid), - Hkp::Email{ ref email,.. } => write!(f, "{}", email), - Hkp::ShortKeyID{ ref query,.. } => write!(f, "{}", query), - Hkp::Invalid{ ref query } => write!(f, "{}", query), + Hkp::Fingerprint { ref fpr, .. } => write!(f, "{}", fpr), + Hkp::KeyID { ref keyid, .. } => write!(f, "{}", keyid), + Hkp::Email { ref email, .. } => write!(f, "{}", email), + Hkp::ShortKeyID { ref query, .. } => write!(f, "{}", query), + Hkp::Invalid { ref query } => write!(f, "{}", query), } } } @@ -53,58 +53,48 @@ impl<'r> FromRequest<'r> for Hkp { async fn from_request(request: &'r Request<'_>) -> request::Outcome { use std::str::FromStr; - let query = request.uri().query().map(|q| q.as_str()).unwrap_or_default(); + let query = request + .uri() + .query() + .map(|q| q.as_str()) + .unwrap_or_default(); let fields = Form::values(query) - .map(|ValueField { name, value }| { - (name.to_string(), value.to_string()) - }) + .map(|ValueField { name, value }| (name.to_string(), value.to_string())) .collect::>(); if fields.contains_key("search") && fields - .get("op") - .map(|x| x == "get" || x == "index") - .unwrap_or(false) + .get("op") + .map(|x| x == "get" || x == "index") + .unwrap_or(false) { let index = fields.get("op").map(|x| x == "index").unwrap_or(false); let search = fields.get("search").cloned().unwrap_or_default(); let maybe_fpr = Fingerprint::from_str(&search); let maybe_keyid = KeyID::from_str(&search); - let looks_like_short_key_id = !search.contains('@') && - (search.starts_with("0x") && search.len() < 16 || search.len() == 8); + let looks_like_short_key_id = !search.contains('@') + && (search.starts_with("0x") && search.len() < 16 || search.len() == 8); if looks_like_short_key_id { Outcome::Success(Hkp::ShortKeyID { query: search, index, }) } else if let Ok(fpr) = maybe_fpr { - Outcome::Success(Hkp::Fingerprint { - fpr, - index, - }) + Outcome::Success(Hkp::Fingerprint { fpr, index }) } else if let Ok(keyid) = maybe_keyid { - Outcome::Success(Hkp::KeyID { - keyid, - index, - }) + Outcome::Success(Hkp::KeyID { keyid, index }) } else { match Email::from_str(&search) { - Ok(email) => { - Outcome::Success(Hkp::Email { - email, - index, - }) - } - Err(_) => { - Outcome::Success(Hkp::Invalid{ - query: search.to_string(), - }) - } + Ok(email) => Outcome::Success(Hkp::Email { email, index }), + Err(_) => Outcome::Success(Hkp::Invalid { + query: search.to_string(), + }), } } - } else if fields.get("op").map(|x| x == "vindex" - || x.starts_with("x-")) + } else if fields + .get("op") + .map(|x| x == "vindex" || x.starts_with("x-")) .unwrap_or(false) { Outcome::Failure((Status::NotImplemented, ())) @@ -131,7 +121,11 @@ pub async fn pks_add_form_data( } } -#[post("/pks/add", format = "application/x-www-form-urlencoded", data = "")] +#[post( + "/pks/add", + format = "application/x-www-form-urlencoded", + data = "" +)] pub async fn pks_add_form( origin: RequestOrigin, db: &rocket::State, @@ -142,8 +136,24 @@ pub async fn pks_add_form( data: Data<'_>, ) -> MyResponse { match vks_web::process_post_form(db, tokens_stateless, rate_limiter, &i18n, data).await { - Ok(UploadResponse::Ok { is_new_key, key_fpr, primary_uid, token, status, .. }) => { - let msg = pks_add_ok(&origin, mail_service, rate_limiter, token, status, is_new_key, key_fpr, primary_uid); + Ok(UploadResponse::Ok { + is_new_key, + key_fpr, + primary_uid, + token, + status, + .. + }) => { + let msg = pks_add_ok( + &origin, + mail_service, + rate_limiter, + token, + status, + is_new_key, + key_fpr, + primary_uid, + ); MyResponse::plain(msg) } Ok(_) => { @@ -165,16 +175,17 @@ fn pks_add_ok( primary_uid: Option, ) -> String { if primary_uid.is_none() { - return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri()) + return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri()); } let primary_uid = primary_uid.unwrap(); if is_new_key { if send_welcome_mail(origin, mail_service, key_fpr, &primary_uid, token) { rate_limiter.action_perform(format!("hkp-sent-{}", &primary_uid)); - return "Upload successful. This is a new key, a welcome email has been sent.".to_string(); + return "Upload successful. This is a new key, a welcome email has been sent." + .to_string(); } - return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri()) + return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri()); } let has_unverified = status.iter().any(|(_, v)| *v == EmailStatus::Unpublished); @@ -182,7 +193,7 @@ fn pks_add_ok( return "Upload successful.".to_string(); } - return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri()) + return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri()); } fn send_welcome_mail( @@ -192,25 +203,21 @@ fn send_welcome_mail( primary_uid: &Email, token: String, ) -> bool { - mail_service.send_welcome(origin.get_base_uri(), fpr, primary_uid, &token).is_ok() + mail_service + .send_welcome(origin.get_base_uri(), fpr, primary_uid, &token) + .is_ok() } #[get("/pks/lookup")] -pub fn pks_lookup( - db: &rocket::State, - i18n: I18n, - key: Hkp -) -> MyResponse { +pub fn pks_lookup(db: &rocket::State, i18n: I18n, key: Hkp) -> MyResponse { let (query, index) = match key { - Hkp::Fingerprint { fpr, index } => - (Query::ByFingerprint(fpr), index), - Hkp::KeyID { keyid, index } => - (Query::ByKeyID(keyid), index), - Hkp::Email { email, index } => { - (Query::ByEmail(email), index) - } + Hkp::Fingerprint { fpr, index } => (Query::ByFingerprint(fpr), index), + Hkp::KeyID { keyid, index } => (Query::ByKeyID(keyid), index), + Hkp::Email { email, index } => (Query::ByEmail(email), index), Hkp::ShortKeyID { query: _, .. } => { - return MyResponse::bad_request_plain("Search by short key ids is not supported, sorry!"); + return MyResponse::bad_request_plain( + "Search by short key ids is not supported, sorry!", + ); } Hkp::Invalid { query: _ } => { return MyResponse::bad_request_plain("Invalid search query!"); @@ -232,47 +239,50 @@ pub fn pks_internal_index( ) -> MyResponse { match query_string.parse() { Ok(query) => key_to_hkp_index(db, i18n, query), - Err(_) => MyResponse::bad_request_plain("Invalid search query!") + Err(_) => MyResponse::bad_request_plain("Invalid search query!"), } } -fn key_to_hkp_index( - db: &rocket::State, - i18n: I18n, - query: Query, -) -> MyResponse { - use sequoia_openpgp::types::RevocationStatus; +fn key_to_hkp_index(db: &rocket::State, i18n: I18n, query: Query) -> MyResponse { use sequoia_openpgp::policy::StandardPolicy; + use sequoia_openpgp::types::RevocationStatus; let tpk = match db.lookup(&query) { Ok(Some(tpk)) => tpk, Ok(None) => return MyResponse::not_found_plain(describe_query_error(&i18n, &query)), - Err(err) => { return MyResponse::ise(err); } + Err(err) => { + return MyResponse::ise(err); + } }; let mut out = String::default(); let p = tpk.primary_key(); let policy = &StandardPolicy::new(); - let ctime = format!("{}", p.creation_time().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()); - let is_rev = - if tpk.revocation_status(policy, None) != RevocationStatus::NotAsFarAsWeKnow { - "r" - } else { - "" - }; + let ctime = format!( + "{}", + p.creation_time() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + ); + let is_rev = if tpk.revocation_status(policy, None) != RevocationStatus::NotAsFarAsWeKnow { + "r" + } else { + "" + }; let algo: u8 = p.pk_algo().into(); out.push_str("info:1:1\r\n"); out.push_str(&format!( - "pub:{}:{}:{}:{}:{}:{}{}\r\n", - p.fingerprint().to_string().replace(" ", ""), - algo, - p.mpis().bits().unwrap_or(0), - ctime, - "", - "", - is_rev + "pub:{}:{}:{}:{}:{}:{}{}\r\n", + p.fingerprint().to_string().replace(" ", ""), + algo, + p.mpis().bits().unwrap_or(0), + ctime, + "", + "", + is_rev )); for uid in tpk.userids() { @@ -285,18 +295,13 @@ fn key_to_hkp_index( .and_then(|time| time.duration_since(SystemTime::UNIX_EPOCH).ok()) .map(|x| format!("{}", x.as_secs())) .unwrap_or_default(); - let is_rev = if uid.revocation_status(policy, None) - != RevocationStatus::NotAsFarAsWeKnow - { - "r" - } else { - "" - }; + let is_rev = if uid.revocation_status(policy, None) != RevocationStatus::NotAsFarAsWeKnow { + "r" + } else { + "" + }; - out.push_str(&format!( - "uid:{}:{}:{}:{}{}\r\n", - u, ctime, "", "", is_rev - )); + out.push_str(&format!("uid:{}:{}:{}:{}{}\r\n", u, ctime, "", "", is_rev)); } MyResponse::plain(out) @@ -304,13 +309,13 @@ fn key_to_hkp_index( #[cfg(test)] mod tests { - use rocket::http::Status; use rocket::http::ContentType; + use rocket::http::Status; use sequoia_openpgp::serialize::Serialize; - use crate::web::tests::*; use crate::mail::pop_mail; + use crate::web::tests::*; #[test] fn hkp() { @@ -326,9 +331,8 @@ mod tests { // Prepare to /pks/add let mut armored = Vec::new(); { - use sequoia_openpgp::armor::{Writer, Kind}; - let mut w = Writer::new(&mut armored, Kind::PublicKey) - .unwrap(); + use sequoia_openpgp::armor::{Kind, Writer}; + let mut w = Writer::new(&mut armored, Kind::PublicKey).unwrap(); tpk.serialize(&mut w).unwrap(); w.finalize().unwrap(); } @@ -338,7 +342,8 @@ mod tests { } // Add! - let response = client.post("/pks/add") + let response = client + .post("/pks/add") .body(post_data.as_bytes()) .header(ContentType::Form) .dispatch(); @@ -351,7 +356,8 @@ mod tests { assert!(welcome_mail.is_some()); // Add! - let response = client.post("/pks/add") + let response = client + .post("/pks/add") .body(post_data.as_bytes()) .header(ContentType::Form) .dispatch(); @@ -374,7 +380,8 @@ mod tests { check_hr_responses_by_fingerprint(&client, &tpk, 0); // Upload the same key again, make sure the welcome mail is not sent again - let response = client.post("/pks/add") + let response = client + .post("/pks/add") .body(post_data.as_bytes()) .header(ContentType::Form) .dispatch(); @@ -399,14 +406,14 @@ mod tests { let mut armored_first = Vec::new(); let mut armored_both = Vec::new(); { - use sequoia_openpgp::armor::{Writer, Kind}; + use sequoia_openpgp::armor::{Kind, Writer}; let mut w = Writer::new(&mut armored_both, Kind::PublicKey).unwrap(); tpk_0.serialize(&mut w).unwrap(); tpk_1.serialize(&mut w).unwrap(); w.finalize().unwrap(); } { - use sequoia_openpgp::armor::{Writer, Kind}; + use sequoia_openpgp::armor::{Kind, Writer}; let mut w = Writer::new(&mut armored_first, Kind::PublicKey).unwrap(); tpk_0.serialize(&mut w).unwrap(); w.finalize().unwrap(); @@ -421,7 +428,8 @@ mod tests { } // Add! - let response = client.post("/pks/add") + let response = client + .post("/pks/add") .body(post_data_both.as_bytes()) .header(ContentType::Form) .dispatch(); @@ -432,7 +440,8 @@ mod tests { assert!(welcome_mail.is_none()); // Add the first again - let response = client.post("/pks/add") + let response = client + .post("/pks/add") .body(post_data_first.as_bytes()) .header(ContentType::Form) .dispatch(); diff --git a/src/web/maintenance.rs b/src/web/maintenance.rs index 69b4d9d..73f7e6c 100644 --- a/src/web/maintenance.rs +++ b/src/web/maintenance.rs @@ -1,6 +1,6 @@ -use rocket::{Request, Data}; use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::Method; +use rocket::{Data, Request}; use rocket_dyn_templates::Template; use rocket_i18n::I18n; use serde_json::json; @@ -29,7 +29,7 @@ impl Fairing for MaintenanceMode { fn info(&self) -> Info { Info { name: "Maintenance Mode", - kind: Kind::Request + kind: Kind::Request, } } @@ -59,8 +59,7 @@ impl MaintenanceMode { } fn is_request_json(&self, path: &str) -> bool { - path.starts_with("/vks/v1/upload") || - path.starts_with("/vks/v1/request-verify") + path.starts_with("/vks/v1/upload") || path.starts_with("/vks/v1/request-verify") } fn is_request_plain(&self, path: &str, method: Method) -> bool { @@ -68,9 +67,7 @@ impl MaintenanceMode { } fn is_request_web(&self, path: &str) -> bool { - path.starts_with("/upload") || - path.starts_with("/manage") || - path.starts_with("/verify") + path.starts_with("/upload") || path.starts_with("/manage") || path.starts_with("/verify") } fn get_maintenance_message(&self) -> Option { @@ -93,15 +90,12 @@ struct JsonErrorMessage { #[get("/maintenance/json/")] pub fn maintenance_error_json(message: String) -> MyResponse { - MyResponse::MaintenanceJson(json!(JsonErrorMessage{ message })) + MyResponse::MaintenanceJson(json!(JsonErrorMessage { message })) } #[get("/maintenance/web/")] -pub fn maintenance_error_web( - message: String, - i18n: I18n, -) -> MyResponse { - let ctx = templates::MaintenanceMode{ +pub fn maintenance_error_web(message: String, i18n: I18n) -> MyResponse { + let ctx = templates::MaintenanceMode { message, version: env!("VERGEN_SEMVER").to_string(), commit: env!("VERGEN_SHA_SHORT").to_string(), diff --git a/src/web/manage.rs b/src/web/manage.rs index a79084b..42af5ad 100644 --- a/src/web/manage.rs +++ b/src/web/manage.rs @@ -5,20 +5,19 @@ use crate::Result; use gettext_macros::i18n; -use crate::web::{RequestOrigin, MyResponse}; -use crate::web::vks_web; -use crate::database::{Database, KeyDatabase, types::Email, types::Fingerprint}; -use crate::mail; use crate::counters; +use crate::database::{types::Email, types::Fingerprint, Database, KeyDatabase}; +use crate::mail; use crate::rate_limiter::RateLimiter; use crate::tokens::{self, StatelessSerializable}; +use crate::web::vks_web; +use crate::web::{MyResponse, RequestOrigin}; -#[derive(Debug,Serialize,Deserialize)] +#[derive(Debug, Serialize, Deserialize)] struct StatelessVerifyToken { - fpr: Fingerprint, -} -impl StatelessSerializable for StatelessVerifyToken { + fpr: Fingerprint, } +impl StatelessSerializable for StatelessVerifyToken {} mod templates { #[derive(Serialize)] @@ -37,8 +36,8 @@ mod templates { #[derive(Serialize)] pub struct ManageKeyUidStatus { - pub address: String, - pub published: bool, + pub address: String, + pub published: bool, } } @@ -62,11 +61,11 @@ pub fn vks_manage(origin: RequestOrigin, i18n: I18n) -> MyResponse { #[get("/manage/")] pub fn vks_manage_key( - origin: RequestOrigin, - db: &rocket::State, - i18n: I18n, - token: String, - token_service: &rocket::State, + origin: RequestOrigin, + db: &rocket::State, + i18n: I18n, + token: String, + token_service: &rocket::State, ) -> MyResponse { use crate::database::types::Fingerprint; use std::convert::TryFrom; @@ -74,19 +73,21 @@ pub fn vks_manage_key( match db.lookup(&database::Query::ByFingerprint(fpr)) { Ok(Some(tpk)) => { let fp = Fingerprint::try_from(tpk.fingerprint()).unwrap(); - let mut emails: Vec = tpk.userids() + let mut emails: Vec = tpk + .userids() .map(|u| u.userid().to_string().parse::()) .flatten() .collect(); emails.sort_unstable(); emails.dedup(); - let uid_status = emails.into_iter().map(|email| - templates::ManageKeyUidStatus { + let uid_status = emails + .into_iter() + .map(|email| templates::ManageKeyUidStatus { address: email.to_string(), published: true, - } - ).collect(); - let key_link = uri!(vks_web::search(q = fp.to_string())).to_string(); + }) + .collect(); + let key_link = uri!(vks_web::search(q = fp.to_string())).to_string(); let context = templates::ManageKey { key_fpr: fp.to_string(), key_link, @@ -95,11 +96,12 @@ pub fn vks_manage_key( base_uri: origin.get_base_uri().to_owned(), }; MyResponse::ok("manage/manage_key", context, i18n, origin) - }, + } Ok(None) => MyResponse::not_found( Some("manage/manage"), Some(i18n!(i18n.catalog, "This link is invalid or expired")), - i18n, origin, + i18n, + origin, ), Err(e) => MyResponse::ise(e), } @@ -107,11 +109,13 @@ pub fn vks_manage_key( MyResponse::not_found( Some("manage/manage"), Some(i18n!(i18n.catalog, "This link is invalid or expired")), - i18n, origin) + i18n, + origin, + ) } } -#[post("/manage", data="")] +#[post("/manage", data = "")] pub fn vks_manage_post( db: &rocket::State, origin: RequestOrigin, @@ -125,35 +129,48 @@ pub fn vks_manage_post( let email = match request.search_term.parse::() { Ok(email) => email, - Err(_) => return MyResponse::not_found( - Some("manage/manage"), - Some(i18n!(i18n.catalog, "Malformed address: {}"; &request.search_term)), - i18n, origin) + Err(_) => { + return MyResponse::not_found( + Some("manage/manage"), + Some(i18n!(i18n.catalog, "Malformed address: {}"; &request.search_term)), + i18n, + origin, + ) + } }; let tpk = match db.lookup(&database::Query::ByEmail(email.clone())) { Ok(Some(tpk)) => tpk, - Ok(None) => return MyResponse::not_found( - Some("manage/manage"), - Some(i18n!(i18n.catalog, "No key for address: {}"; &request.search_term)), - i18n, origin), + Ok(None) => { + return MyResponse::not_found( + Some("manage/manage"), + Some(i18n!(i18n.catalog, "No key for address: {}"; &request.search_term)), + i18n, + origin, + ) + } Err(e) => return MyResponse::ise(e), }; - let email_exists = tpk.userids() + let email_exists = tpk + .userids() .flat_map(|binding| binding.userid().to_string().parse::()) .any(|candidate| candidate == email); if !email_exists { - return MyResponse::ise( - anyhow!("Internal error: address check failed!")); + return MyResponse::ise(anyhow!("Internal error: address check failed!")); } if !rate_limiter.action_perform(format!("manage-{}", &email)) { return MyResponse::not_found( Some("manage/manage"), - Some(i18n!(i18n.catalog, "A request has already been sent for this address recently.")), - i18n, origin); + Some(i18n!( + i18n.catalog, + "A request has already been sent for this address recently." + )), + i18n, + origin, + ); } let fpr: Fingerprint = tpk.fingerprint().try_into().unwrap(); @@ -172,7 +189,7 @@ pub fn vks_manage_post( MyResponse::ok("manage/manage_link_sent", ctx, i18n, origin) } -#[post("/manage/unpublish", data="")] +#[post("/manage/unpublish", data = "")] pub fn vks_manage_unpublish( origin: RequestOrigin, db: &rocket::State, @@ -199,5 +216,11 @@ pub fn vks_manage_unpublish_or_fail( db.set_email_unpublished(&verify_token.fpr, &email)?; counters::inc_address_unpublished(&email); - Ok(vks_manage_key(origin, db, i18n, request.token.to_owned(), token_service)) + Ok(vks_manage_key( + origin, + db, + i18n, + request.token.to_owned(), + token_service, + )) } diff --git a/src/web/mod.rs b/src/web/mod.rs index 0c0e984..de4b81c 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,14 +1,14 @@ +use hyperx::header::{Charset, ContentDisposition, DispositionParam, DispositionType}; use rocket::figment::Figment; use rocket::fs::NamedFile; use rocket::http::{Header, Status}; -use rocket::request; use rocket::outcome::Outcome; -use rocket::response::{Responder, Response}; +use rocket::request; use rocket::response::status::Custom; +use rocket::response::{Responder, Response}; use rocket_dyn_templates::{Engines, Template}; use rocket_i18n::I18n; use rocket_prometheus::PrometheusMetrics; -use hyperx::header::{ContentDisposition, DispositionType, DispositionParam, Charset}; use gettext_macros::{compile_i18n, include_i18n}; @@ -16,27 +16,27 @@ use serde::Serialize; use std::path::PathBuf; -use crate::mail; -use crate::tokens; use crate::counters; -use crate::i18n_helpers::describe_query_error; -use crate::template_helpers::TemplateOverrides; use crate::i18n::I18NHelper; +use crate::i18n_helpers::describe_query_error; +use crate::mail; use crate::rate_limiter::RateLimiter; +use crate::template_helpers::TemplateOverrides; +use crate::tokens; -use crate::database::{Database, KeyDatabase, Query}; use crate::database::types::Fingerprint; +use crate::database::{Database, KeyDatabase, Query}; use crate::Result; use std::convert::TryInto; -mod hkp; -mod manage; -mod maintenance; -mod vks; -mod vks_web; -mod vks_api; mod debug_web; +mod hkp; +mod maintenance; +mod manage; +mod vks; +mod vks_api; +mod vks_web; mod wkd; use crate::web::maintenance::MaintenanceMode; @@ -44,10 +44,16 @@ use crate::web::maintenance::MaintenanceMode; pub struct HagridTemplate(&'static str, serde_json::Value, I18n, RequestOrigin); impl<'r> Responder<'r, 'static> for HagridTemplate { - fn respond_to(self, req: &'r rocket::Request) -> std::result::Result, Status> { + fn respond_to( + self, + req: &'r rocket::Request, + ) -> std::result::Result, Status> { let HagridTemplate(tmpl, ctx, i18n, origin) = self; - let template_overrides: &TemplateOverrides = req.rocket().state().expect("TemplateOverrides must be in managed state"); + let template_overrides: &TemplateOverrides = req + .rocket() + .state() + .expect("TemplateOverrides must be in managed state"); let template_override = template_overrides.get_template_override(i18n.lang, tmpl); let layout_context = templates::HagridLayout::new(ctx, i18n, origin); @@ -55,7 +61,8 @@ impl<'r> Responder<'r, 'static> for HagridTemplate { Template::render(template_override, layout_context) } else { Template::render(tmpl, layout_context) - }.respond_to(req) + } + .respond_to(req) } } @@ -114,12 +121,14 @@ impl MyResponse { rocket::http::hyper::header::CONTENT_DISPOSITION.as_str(), ContentDisposition { disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::Filename( - Charset::Us_Ascii, None, - (fp.to_string() + ".asc").into_bytes()), - ], - }.to_string()); + parameters: vec![DispositionParam::Filename( + Charset::Us_Ascii, + None, + (fp.to_string() + ".asc").into_bytes(), + )], + } + .to_string(), + ); MyResponse::Key(armored_key, content_disposition) } @@ -128,12 +137,14 @@ impl MyResponse { rocket::http::hyper::header::CONTENT_DISPOSITION.as_str(), ContentDisposition { disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::Filename( - Charset::Us_Ascii, None, - (wkd_hash.to_string() + ".pgp").into_bytes()), - ], - }.to_string()); + parameters: vec![DispositionParam::Filename( + Charset::Us_Ascii, + None, + (wkd_hash.to_string() + ".pgp").into_bytes(), + )], + } + .to_string(), + ); MyResponse::WkdKey(binary_key, content_disposition) } @@ -148,8 +159,15 @@ impl MyResponse { MyResponse::ServerError(Template::render("500", ctx)) } - pub fn bad_request(template: &'static str, e: anyhow::Error, i18n: I18n, origin: RequestOrigin) -> Self { - let ctx = templates::Error { error: format!("{}", e) }; + pub fn bad_request( + template: &'static str, + e: anyhow::Error, + i18n: I18n, + origin: RequestOrigin, + ) -> Self { + let ctx = templates::Error { + error: format!("{}", e), + }; let context_json = serde_json::to_value(ctx).unwrap(); MyResponse::BadRequest(HagridTemplate(template, context_json, i18n, origin)) } @@ -168,10 +186,16 @@ impl MyResponse { i18n: I18n, origin: RequestOrigin, ) -> Self { - let ctx = templates::Error { error: message.into() - .unwrap_or_else(|| "Key not found".to_owned()) }; + let ctx = templates::Error { + error: message.into().unwrap_or_else(|| "Key not found".to_owned()), + }; let context_json = serde_json::to_value(ctx).unwrap(); - MyResponse::NotFound(HagridTemplate(tmpl.unwrap_or("index"), context_json, i18n, origin)) + MyResponse::NotFound(HagridTemplate( + tmpl.unwrap_or("index"), + context_json, + i18n, + origin, + )) } } @@ -219,8 +243,16 @@ mod templates { base_uri: origin.get_base_uri().to_string(), page, lang: i18n.lang.to_string(), - htmldir: if is_rtl { "rtl".to_owned() } else { "ltr".to_owned() }, - htmlclass: if is_rtl { "rtl".to_owned() } else { "".to_owned() }, + htmldir: if is_rtl { + "rtl".to_owned() + } else { + "ltr".to_owned() + }, + htmlclass: if is_rtl { + "rtl".to_owned() + } else { + "".to_owned() + }, } } } @@ -245,7 +277,9 @@ pub enum RequestOrigin { impl<'r> request::FromRequest<'r> for RequestOrigin { type Error = (); - async fn from_request(request: &'r request::Request<'_>) -> request::Outcome { + async fn from_request( + request: &'r request::Request<'_>, + ) -> request::Outcome { let hagrid_state = request.rocket().state::().unwrap(); let result = match request.headers().get("x-is-onion").next() { Some(_) => RequestOrigin::OnionService(hagrid_state.base_uri_onion.clone()), @@ -342,14 +376,16 @@ fn errors( code: u16, template: String, ) -> std::result::Result, &'static str> { - if !template.chars().all(|x| x == '-' || char::is_ascii_alphabetic(&x)) { + if !template + .chars() + .all(|x| x == '-' || char::is_ascii_alphabetic(&x)) + { return Err("bad request"); } - let status_code = Status::from_code(code) - .ok_or("bad request")?; + let status_code = Status::from_code(code).ok_or("bad request")?; let response_body = Template::render( format!("errors/{}-{}", code, template), - templates::HagridLayout::new(templates::Bare{dummy: ()}, i18n, origin) + templates::HagridLayout::new(templates::Bare { dummy: () }, i18n, origin), ); Ok(Custom(status_code, response_body)) } @@ -360,7 +396,9 @@ pub fn serve() -> Result> { compile_i18n!(); -fn rocket_factory(mut rocket: rocket::Rocket) -> Result> { +fn rocket_factory( + mut rocket: rocket::Rocket, +) -> Result> { let routes = routes![ // infra root, @@ -428,21 +466,23 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result Result { // State let base_uri: String = config.extract_inner("base-URI")?; - let base_uri_onion = config.extract_inner::("base-URI-Onion") + let base_uri_onion = config + .extract_inner::("base-URI-Onion") .unwrap_or_else(|_| base_uri.clone()); Ok(HagridState { assets_dir, @@ -524,7 +565,8 @@ fn configure_localized_template_list(config: &Figment) -> Result Result { - let maintenance_file: PathBuf = config.extract_inner("maintenance_file") + let maintenance_file: PathBuf = config + .extract_inner("maintenance_file") .unwrap_or_else(|_| PathBuf::from("maintenance")); Ok(MaintenanceMode::new(maintenance_file)) } @@ -532,27 +574,27 @@ fn configure_maintenance_mode(config: &Figment) -> Result { #[cfg(test)] pub mod tests { use regex; + use rocket::http::ContentType; + use rocket::http::Header; + use rocket::http::Status; use rocket::local::blocking::{Client, LocalResponse}; use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; use tempfile::{tempdir, TempDir}; - use rocket::http::Status; - use rocket::http::ContentType; - use rocket::http::Header; - use sequoia_openpgp::Cert; use sequoia_openpgp::cert::CertBuilder; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::serialize::Serialize; + use sequoia_openpgp::Cert; use std::time::SystemTime; use mail::pop_mail; - use crate::database::*; use super::*; + use crate::database::*; /// Fake base URI to use in tests. const BASE_URI: &str = "http://local.connection"; @@ -583,27 +625,56 @@ pub mod tests { let config = rocket::Config::figment() .select("staging") .merge(("root", root.path())) - .merge(("template_dir", - ::std::env::current_dir().unwrap().join("dist/templates") - .to_str().unwrap())) - .merge(("email_template_dir", - ::std::env::current_dir().unwrap().join("dist/email-templates") - .to_str().unwrap())) - .merge(("assets_dir", - ::std::env::current_dir().unwrap().join("dist/assets") - .to_str().unwrap())) - .merge(("keys_internal_dir", base_dir.join("keys_internal").to_str().unwrap())) - .merge(("keys_external_dir", base_dir.join("keys_external").to_str().unwrap())) + .merge(( + "template_dir", + ::std::env::current_dir() + .unwrap() + .join("dist/templates") + .to_str() + .unwrap(), + )) + .merge(( + "email_template_dir", + ::std::env::current_dir() + .unwrap() + .join("dist/email-templates") + .to_str() + .unwrap(), + )) + .merge(( + "assets_dir", + ::std::env::current_dir() + .unwrap() + .join("dist/assets") + .to_str() + .unwrap(), + )) + .merge(( + "keys_internal_dir", + base_dir.join("keys_internal").to_str().unwrap(), + )) + .merge(( + "keys_external_dir", + base_dir.join("keys_external").to_str().unwrap(), + )) .merge(("tmp_dir", base_dir.join("tmp").to_str().unwrap())) .merge(("token_dir", base_dir.join("tokens").to_str().unwrap())) - .merge(("maintenance_file", base_dir.join("maintenance").to_str().unwrap())) + .merge(( + "maintenance_file", + base_dir.join("maintenance").to_str().unwrap(), + )) .merge(("base-URI", BASE_URI)) .merge(("base-URI-Onion", BASE_URI_ONION)) .merge(("from", "from@example.com")) .merge(("token_secret", "hagrid")) .merge(("token_validity", 3600u64)) - .merge(("filemail_into", filemail.into_os_string().into_string() - .expect("path is valid UTF8"))); + .merge(( + "filemail_into", + filemail + .into_os_string() + .into_string() + .expect("path is valid UTF8"), + )); Ok((root, config)) } @@ -625,7 +696,8 @@ pub mod tests { let client = Client::untracked(rocket).expect("valid rocket instance"); // Check that we see the landing page. - let response = client.get("/about") + let response = client + .get("/about") .header(Header::new("Accept-Language", "de")) .dispatch(); assert_eq!(response.status(), Status::Ok); @@ -650,7 +722,10 @@ pub mod tests { let response = client.get("/about").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.into_string().unwrap().contains("distribution and discovery")); + assert!(response + .into_string() + .unwrap() + .contains("distribution and discovery")); // Check that we see the privacy policy. let response = client.get("/about/privacy").dispatch(); @@ -674,7 +749,10 @@ pub mod tests { let response = client.get("/manage").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(response.into_string().unwrap().contains("any verified email address")); + assert!(response + .into_string() + .unwrap() + .contains("any verified email address")); assert_consistency(client.rocket()); } @@ -699,21 +777,30 @@ pub mod tests { let response = client.put("/").dispatch(); assert_eq!(response.status(), Status::ServiceUnavailable); assert_eq!(response.content_type(), Some(ContentType::Plain)); - assert!(response.into_string().unwrap().contains("maintenance-message")); + assert!(response + .into_string() + .unwrap() + .contains("maintenance-message")); fs::remove_file(&maintenance_path).unwrap(); // Check that we see the upload form. let response = client.get("/upload").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); - assert!(!response.into_string().unwrap().contains("maintenance-message")); + assert!(!response + .into_string() + .unwrap() + .contains("maintenance-message")); } fn check_maintenance(client: &Client, uri: &str, content_type: ContentType) { let response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::ServiceUnavailable); assert_eq!(response.content_type(), Some(content_type)); - assert!(response.into_string().unwrap().contains("maintenance-message")); + assert!(response + .into_string() + .unwrap() + .contains("maintenance-message")); } #[test] @@ -755,7 +842,11 @@ pub mod tests { vks_manage(&client, "foo@invalid.example.com"); // Confirm deletion. - check_mails_and_confirm_deletion(&client, filemail_into.as_path(), "foo@invalid.example.com"); + check_mails_and_confirm_deletion( + &client, + filemail_into.as_path(), + "foo@invalid.example.com", + ); // Now, we should no longer be able to look it up by email // address. @@ -912,7 +1003,8 @@ pub mod tests { .append_pair("address", "foo@invalid.example.com") .finish(); - let response = client.post("/upload/request-verify") + let response = client + .post("/upload/request-verify") .header(ContentType::Form) .header(Header::new("X-Is-Onion", "true")) .body(encoded.as_bytes()) @@ -929,7 +1021,6 @@ pub mod tests { assert_consistency(client.rocket()); } - #[test] fn upload_curl_shortcut() { let (_tmpdir, client) = client().unwrap(); @@ -948,21 +1039,40 @@ pub mod tests { #[test] fn search_invalid() { let (_tmpdir, client) = client().unwrap(); - check_response(&client, "/search?q=0x1234abcd", - Status::BadRequest, "not supported"); - check_response(&client, "/search?q=1234abcd", - Status::BadRequest, "not supported"); - check_response(&client, "/pks/lookup?op=get&search=0x1234abcd", - Status::BadRequest, "not supported"); - check_response(&client, "/pks/lookup?op=get&search=1234abcd", - Status::BadRequest, "not supported"); - + check_response( + &client, + "/search?q=0x1234abcd", + Status::BadRequest, + "not supported", + ); + check_response( + &client, + "/search?q=1234abcd", + Status::BadRequest, + "not supported", + ); + check_response( + &client, + "/pks/lookup?op=get&search=0x1234abcd", + Status::BadRequest, + "not supported", + ); + check_response( + &client, + "/pks/lookup?op=get&search=1234abcd", + Status::BadRequest, + "not supported", + ); } #[test] fn wkd_policy() { let (_tmpdir, client) = client().unwrap(); - check_response(&client, "/.well-known/openpgpkey/example.org/policy", - Status::Ok, ""); + check_response( + &client, + "/.well-known/openpgpkey/example.org/policy", + Status::Ok, + "", + ); } /// Asserts that the given URI 404s. @@ -973,78 +1083,81 @@ pub mod tests { /// Asserts that lookups by the given email 404. pub fn check_null_responses_by_email(client: &Client, addr: &str) { + check_null_response(client, &format!("/vks/v1/by-email/{}", addr)); + check_null_response(client, &format!("/pks/lookup?op=get&search={}", addr)); check_null_response( - client, &format!("/vks/v1/by-email/{}", addr)); - check_null_response( - client, &format!("/pks/lookup?op=get&search={}", addr)); - check_null_response( - client, &format!("/pks/lookup?op=get&options=mr&search={}", - addr)); + client, + &format!("/pks/lookup?op=get&options=mr&search={}", addr), + ); let (wkd_hash, domain) = crate::database::wkd::encode_wkd(addr).unwrap(); check_null_response( &client, - &format!("/.well-known/openpgpkey/{}/hu/{}", domain, wkd_hash)); + &format!("/.well-known/openpgpkey/{}/hu/{}", domain, wkd_hash), + ); } /// Asserts that lookups by the given email are successful. - pub fn check_responses_by_email(client: &Client, addr: &str, tpk: &Cert, - nr_uids: usize) { - check_mr_response( - client, - &format!("/vks/v1/by-email/{}", addr), - tpk, nr_uids); + pub fn check_responses_by_email(client: &Client, addr: &str, tpk: &Cert, nr_uids: usize) { + check_mr_response(client, &format!("/vks/v1/by-email/{}", addr), tpk, nr_uids); check_mr_response( client, &format!("/vks/v1/by-email/{}", addr.replace("@", "%40")), - tpk, nr_uids); + tpk, + nr_uids, + ); check_mr_response( client, &format!("/pks/lookup?op=get&options=mr&search={}", addr), - tpk, nr_uids); - check_hr_response( - client, - &format!("/search?q={}", addr), - tpk, nr_uids); - check_hr_response_onion( - client, - &format!("/search?q={}", addr), - tpk, nr_uids); + tpk, + nr_uids, + ); + check_hr_response(client, &format!("/search?q={}", addr), tpk, nr_uids); + check_hr_response_onion(client, &format!("/search?q={}", addr), tpk, nr_uids); let (wkd_hash, domain) = crate::database::wkd::encode_wkd(addr).unwrap(); check_wkd_response( &client, &format!("/.well-known/openpgpkey/{}/hu/{}", domain, wkd_hash), - &tpk, nr_uids); + &tpk, + nr_uids, + ); } /// Asserts that the given URI returns a Cert matching the given /// one, with the given number of userids. - pub fn check_mr_response(client: &Client, uri: &str, tpk: &Cert, - nr_uids: usize) { + pub fn check_mr_response(client: &Client, uri: &str, tpk: &Cert, nr_uids: usize) { let response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), - Some(ContentType::new("application", "pgp-keys"))); + assert_eq!( + response.content_type(), + Some(ContentType::new("application", "pgp-keys")) + ); let body = response.into_string().unwrap(); assert!(body.contains("END PGP PUBLIC KEY BLOCK")); let tpk_ = Cert::from_bytes(body.as_bytes()).unwrap(); assert_eq!(tpk.fingerprint(), tpk_.fingerprint()); - assert_eq!(tpk.keys().map(|skb| skb.key().fingerprint()) - .collect::>(), - tpk_.keys().map(|skb| skb.key().fingerprint()) - .collect::>()); + assert_eq!( + tpk.keys() + .map(|skb| skb.key().fingerprint()) + .collect::>(), + tpk_.keys() + .map(|skb| skb.key().fingerprint()) + .collect::>() + ); assert_eq!(tpk_.userids().count(), nr_uids); } - // it's a rather "reverse implementation" style test.. can we do better? + // it's a rather "reverse implementation" style test.. can we do better? /// Asserts that the given URI returns a correct hkp "index" /// response for the given Cert. pub fn check_index_response(client: &Client, uri: &str, tpk: &Cert) { let response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), - Some(ContentType::new("text", "plain"))); + assert_eq!( + response.content_type(), + Some(ContentType::new("text", "plain")) + ); let body = response.into_string().unwrap(); assert!(body.contains("info:1:1")); @@ -1052,46 +1165,60 @@ pub mod tests { let algo: u8 = tpk.primary_key().pk_algo().into(); assert!(body.contains(&format!("pub:{}:{}:", primary_fpr, algo))); - let creation_time = tpk.primary_key().creation_time().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + let creation_time = tpk + .primary_key() + .creation_time() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); assert!(body.contains(&format!(":{}:", creation_time))); } /// Asserts that we can get the given Cert back using the various /// by-fingerprint or by-keyid lookup mechanisms. - pub fn check_mr_responses_by_fingerprint(client: &Client, tpk: &Cert, - nr_uids: usize) { + pub fn check_mr_responses_by_fingerprint(client: &Client, tpk: &Cert, nr_uids: usize) { let fp = tpk.fingerprint().to_hex(); let keyid = sequoia_openpgp::KeyID::from(tpk.fingerprint()).to_hex(); + check_mr_response(client, &format!("/vks/v1/by-keyid/{}", keyid), tpk, nr_uids); check_mr_response( - client, &format!("/vks/v1/by-keyid/{}", keyid), tpk, nr_uids); - check_mr_response( - client, &format!("/vks/v1/by-fingerprint/{}", fp), tpk, nr_uids); + client, + &format!("/vks/v1/by-fingerprint/{}", fp), + tpk, + nr_uids, + ); check_mr_response( client, &format!("/pks/lookup?op=get&options=mr&search={}", fp), - tpk, nr_uids); + tpk, + nr_uids, + ); check_mr_response( client, &format!("/pks/lookup?op=get&options=mr&search=0x{}", fp), - tpk, nr_uids); + tpk, + nr_uids, + ); check_mr_response( client, &format!("/pks/lookup?op=get&options=mr&search={}", keyid), - tpk, nr_uids); + tpk, + nr_uids, + ); check_mr_response( client, &format!("/pks/lookup?op=get&options=mr&search=0x{}", keyid), - tpk, nr_uids); + tpk, + nr_uids, + ); check_mr_response( client, &format!("/pks/lookup?op=get&search=0x{}", keyid), - tpk, nr_uids); + tpk, + nr_uids, + ); - check_index_response( - client, - &format!("/pks/lookup?op=index&search={}", fp), - tpk); + check_index_response(client, &format!("/pks/lookup?op=index&search={}", fp), tpk); } /// Asserts that the given URI contains the search string. @@ -1105,8 +1232,7 @@ pub mod tests { /// Asserts that the given URI returns human readable response /// page that contains a URI pointing to the Cert. - pub fn check_hr_response(client: &Client, uri: &str, tpk: &Cert, - nr_uids: usize) { + pub fn check_hr_response(client: &Client, uri: &str, tpk: &Cert, nr_uids: usize) { let response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); @@ -1115,12 +1241,10 @@ pub mod tests { assert!(body.contains(&tpk.fingerprint().to_hex())); // Extract the links. - let link_re = regex::Regex::new( - &format!("{}(/vks/[^ \t\n\"<]*)", BASE_URI)).unwrap(); + let link_re = regex::Regex::new(&format!("{}(/vks/[^ \t\n\"<]*)", BASE_URI)).unwrap(); let mut n = 0; for link in link_re.captures_iter(&body) { - check_mr_response(client, link.get(1).unwrap().as_str(), tpk, - nr_uids); + check_mr_response(client, link.get(1).unwrap().as_str(), tpk, nr_uids); n += 1; } assert!(n > 0); @@ -1128,8 +1252,7 @@ pub mod tests { /// Asserts that the given URI returns human readable response /// page that contains an onion URI pointing to the Cert. - pub fn check_hr_response_onion(client: &Client, uri: &str, tpk: &Cert, - _nr_uids: usize) { + pub fn check_hr_response_onion(client: &Client, uri: &str, tpk: &Cert, _nr_uids: usize) { let response = client .get(uri) .header(Header::new("X-Is-Onion", "true")) @@ -1140,63 +1263,53 @@ pub mod tests { assert!(body.contains(&tpk.fingerprint().to_hex())); // Extract the links. - let link_re = regex::Regex::new( - &format!("{}(/vks/[^ \t\n\"<]*)", BASE_URI_ONION)).unwrap(); + let link_re = regex::Regex::new(&format!("{}(/vks/[^ \t\n\"<]*)", BASE_URI_ONION)).unwrap(); assert!(link_re.is_match(&body)); } - /// Asserts that we can get the given Cert back using the various /// by-fingerprint or by-keyid lookup mechanisms. - pub fn check_hr_responses_by_fingerprint(client: &Client, tpk: &Cert, - nr_uids: usize) { + pub fn check_hr_responses_by_fingerprint(client: &Client, tpk: &Cert, nr_uids: usize) { let fp = tpk.fingerprint().to_hex(); let keyid = sequoia_openpgp::KeyID::from(tpk.fingerprint()).to_hex(); - check_hr_response( - client, - &format!("/search?q={}", fp), - tpk, nr_uids); - check_hr_response( - client, - &format!("/search?q=0x{}", fp), - tpk, nr_uids); - check_hr_response( - client, - &format!("/search?q={}", keyid), - tpk, nr_uids); - check_hr_response( - client, - &format!("/search?q=0x{}", keyid), - tpk, nr_uids); + check_hr_response(client, &format!("/search?q={}", fp), tpk, nr_uids); + check_hr_response(client, &format!("/search?q=0x{}", fp), tpk, nr_uids); + check_hr_response(client, &format!("/search?q={}", keyid), tpk, nr_uids); + check_hr_response(client, &format!("/search?q=0x{}", keyid), tpk, nr_uids); } /// Asserts that the given URI returns correct WKD response with a Cert /// matching the given one, with the given number of userids. - pub fn check_wkd_response(client: &Client, uri: &str, tpk: &Cert, - nr_uids: usize) { + pub fn check_wkd_response(client: &Client, uri: &str, tpk: &Cert, nr_uids: usize) { let response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), - Some(ContentType::new("application", "octet-stream"))); + assert_eq!( + response.content_type(), + Some(ContentType::new("application", "octet-stream")) + ); let body = response.into_bytes().unwrap(); let tpk_ = Cert::from_bytes(&body).unwrap(); assert_eq!(tpk.fingerprint(), tpk_.fingerprint()); - assert_eq!(tpk.keys().map(|skb| skb.key().fingerprint()) - .collect::>(), - tpk_.keys().map(|skb| skb.key().fingerprint()) - .collect::>()); + assert_eq!( + tpk.keys() + .map(|skb| skb.key().fingerprint()) + .collect::>(), + tpk_.keys() + .map(|skb| skb.key().fingerprint()) + .collect::>() + ); assert_eq!(tpk_.userids().count(), nr_uids); } - fn check_verify_link(client: &Client, token: &str, address: &str, lang: &'static str) { let encoded = ::url::form_urlencoded::Serializer::new(String::new()) .append_pair("token", token) .append_pair("address", address) .finish(); - let response = client.post("/upload/request-verify") + let response = client + .post("/upload/request-verify") .header(ContentType::Form) .header(Header::new("Accept-Language", lang)) .body(encoded.as_bytes()) @@ -1207,7 +1320,8 @@ pub mod tests { fn check_verify_link_json(client: &Client, token: &str, address: &str) { let json = format!(r#"{{"token":"{}","addresses":["{}"]}}"#, token, address); - let response = client.post("/vks/v1/request-verify") + let response = client + .post("/vks/v1/request-verify") .header(ContentType::JSON) .body(json.as_bytes()) .dispatch(); @@ -1224,7 +1338,10 @@ pub mod tests { let response_second = client.post(&confirm_uri).dispatch(); assert_eq!(response_second.status(), Status::BadRequest); - assert!(response_second.into_string().unwrap().contains("already been verified")); + assert!(response_second + .into_string() + .unwrap() + .contains("already been verified")); } fn check_mails_and_confirm_deletion(client: &Client, filemail_path: &Path, address: &str) { @@ -1237,8 +1354,12 @@ pub mod tests { let mail_content = pop_mail(filemail_path).unwrap().unwrap(); let capture_re = regex::bytes::Regex::new(pattern).unwrap(); - let capture_content = capture_re.captures(mail_content.as_ref()).unwrap() - .get(1).unwrap().as_bytes(); + let capture_content = capture_re + .captures(mail_content.as_ref()) + .unwrap() + .get(1) + .unwrap() + .as_bytes(); String::from_utf8_lossy(capture_content).to_string() } @@ -1258,22 +1379,29 @@ pub mod tests { let pattern = "name=\"token\" value=\"([^\"]*)\""; let capture_re = regex::bytes::Regex::new(pattern).unwrap(); - let capture_content = capture_re .captures(response_body.as_bytes()).unwrap() - .get(1).unwrap().as_bytes(); + let capture_content = capture_re + .captures(response_body.as_bytes()) + .unwrap() + .get(1) + .unwrap() + .as_bytes(); let token = String::from_utf8_lossy(capture_content).to_string(); assert_eq!(status, Status::Ok); token } - fn vks_publish_submit_response<'a>(client: &'a Client, data: &[u8]) -> - LocalResponse<'a> { + fn vks_publish_submit_response<'a>(client: &'a Client, data: &[u8]) -> LocalResponse<'a> { let ct = ContentType::with_params( - "multipart", "form-data", - ("boundary", "---------------------------14733842173518794281682249499")); + "multipart", + "form-data", + ( + "boundary", + "---------------------------14733842173518794281682249499", + ), + ); - let header = - b"-----------------------------14733842173518794281682249499\r\n\ + let header = b"-----------------------------14733842173518794281682249499\r\n\ Content-Disposition: form-data; name=\"csrf\"\r\n\ \r\n\ \r\n\ @@ -1287,35 +1415,39 @@ pub mod tests { body.extend_from_slice(header); body.extend_from_slice(data); body.extend_from_slice(footer); - client.post("/upload/submit") + client + .post("/upload/submit") .header(ct) .body(&body[..]) .dispatch() } fn vks_publish_shortcut_get_token(client: &Client, data: &[u8]) -> String { - let response = client.put("/") - .body(data) - .dispatch(); + let response = client.put("/").body(data).dispatch(); assert_eq!(response.status(), Status::Ok); let response_body = response.into_string().unwrap(); assert!(response_body.contains("Key successfully uploaded")); let pattern = format!("{}/upload/([^ \t\n]*)", BASE_URI); let capture_re = regex::bytes::Regex::new(&pattern).unwrap(); - let capture_content = capture_re .captures(response_body.as_bytes()).unwrap() - .get(1).unwrap().as_bytes(); + let capture_content = capture_re + .captures(response_body.as_bytes()) + .unwrap() + .get(1) + .unwrap() + .as_bytes(); String::from_utf8_lossy(capture_content).to_string() } fn vks_publish_json_get_token(client: &Client, data: &[u8]) -> String { - let response = client.post("/vks/v1/upload") + let response = client + .post("/vks/v1/upload") .header(ContentType::JSON) .body(format!(r#"{{ "keytext": "{}" }}"#, base64::encode(data))) .dispatch(); let status = response.status(); let response_body = response.into_string().unwrap(); - let result: vks_api::json::UploadResult = serde_json::from_str(&response_body).unwrap(); + let result: vks_api::json::UploadResult = serde_json::from_str(&response_body).unwrap(); assert_eq!(status, Status::Ok); result.token @@ -1325,7 +1457,8 @@ pub mod tests { let encoded = ::url::form_urlencoded::Serializer::new(String::new()) .append_pair("search_term", search_term) .finish(); - let response = client.post("/manage") + let response = client + .post("/manage") .header(ContentType::Form) .body(encoded.as_bytes()) .dispatch(); @@ -1337,7 +1470,8 @@ pub mod tests { .append_pair("token", token) .append_pair("address", address) .finish(); - let response = client.post("/manage/unpublish") + let response = client + .post("/manage/unpublish") .header(ContentType::Form) .body(encoded.as_bytes()) .dispatch(); diff --git a/src/web/vks.rs b/src/web/vks.rs index adf09ff..fd63fa8 100644 --- a/src/web/vks.rs +++ b/src/web/vks.rs @@ -1,24 +1,26 @@ use crate::Result; -use crate::database::{Database, KeyDatabase, StatefulTokens, EmailAddressStatus, TpkStatus, ImportResult}; -use crate::database::types::{Fingerprint,Email}; -use crate::mail; use crate::counters; -use crate::tokens::{self, StatelessSerializable}; +use crate::database::types::{Email, Fingerprint}; +use crate::database::{ + Database, EmailAddressStatus, ImportResult, KeyDatabase, StatefulTokens, TpkStatus, +}; +use crate::mail; use crate::rate_limiter::RateLimiter; +use crate::tokens::{self, StatelessSerializable}; use crate::web::RequestOrigin; -use rocket_i18n::I18n; use gettext_macros::i18n; +use rocket_i18n::I18n; -use sequoia_openpgp::Cert; -use sequoia_openpgp::parse::{Parse, PacketParserBuilder, Dearmor}; -use sequoia_openpgp::cert::CertParser; use sequoia_openpgp::armor::ReaderMode; +use sequoia_openpgp::cert::CertParser; +use sequoia_openpgp::parse::{Dearmor, PacketParserBuilder, Parse}; +use sequoia_openpgp::Cert; -use std::io::Read; -use std::convert::TryFrom; use std::collections::HashMap; +use std::convert::TryFrom; +use std::io::Read; use self::response::*; @@ -38,7 +40,7 @@ pub mod request { pub mod response { use crate::database::types::Email; - #[derive(Debug,Serialize,Deserialize,PartialEq,Eq)] + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum EmailStatus { #[serde(rename = "unpublished")] Unpublished, @@ -57,12 +59,14 @@ pub mod response { token: String, key_fpr: String, is_revoked: bool, - status: HashMap, + status: HashMap, count_unparsed: usize, is_new_key: bool, primary_uid: Option, }, - OkMulti { key_fprs: Vec }, + OkMulti { + key_fprs: Vec, + }, Error(String), } @@ -84,15 +88,14 @@ pub mod response { } } -#[derive(Serialize,Deserialize)] +#[derive(Serialize, Deserialize)] struct VerifyTpkState { fpr: Fingerprint, addresses: Vec, requested: Vec, } -impl StatelessSerializable for VerifyTpkState { -} +impl StatelessSerializable for VerifyTpkState {} pub fn process_key( db: &KeyDatabase, @@ -103,9 +106,7 @@ pub fn process_key( ) -> response::UploadResponse { // First, parse all Certs and error out if one fails. let parser = match PacketParserBuilder::from_reader(reader) - .and_then(|ppb| { - ppb.dearmor(Dearmor::Auto(ReaderMode::VeryTolerant)).build() - }) + .and_then(|ppb| ppb.dearmor(Dearmor::Auto(ReaderMode::VeryTolerant)).build()) { Ok(ppr) => CertParser::from(ppr), Err(_) => return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed.")), @@ -122,7 +123,7 @@ pub fn process_key( )); } t - }, + } Err(_) => { return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed.")); } @@ -131,7 +132,13 @@ pub fn process_key( match tpks.len() { 0 => UploadResponse::err(i18n!(i18n.catalog, "No key uploaded.")), - 1 => process_key_single(db, i18n, tokens_stateless, rate_limiter, tpks.into_iter().next().unwrap()), + 1 => process_key_single( + db, + i18n, + tokens_stateless, + rate_limiter, + tpks.into_iter().next().unwrap(), + ), _ => process_key_multiple(db, tpks), } } @@ -147,14 +154,10 @@ fn log_db_merge(import_result: Result) -> Result { import_result } -fn process_key_multiple( - db: &KeyDatabase, - tpks: Vec, -) -> response::UploadResponse { +fn process_key_multiple(db: &KeyDatabase, tpks: Vec) -> response::UploadResponse { let key_fprs: Vec<_> = tpks .into_iter() - .flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint()) - .map(|fpr| (fpr, tpk))) + .flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint()).map(|fpr| (fpr, tpk))) .flat_map(|(fpr, tpk)| log_db_merge(db.merge(tpk)).map(|_| fpr.to_string())) .collect(); @@ -174,17 +177,21 @@ fn process_key_single( Ok(ImportResult::New(tpk_status)) => (tpk_status, true), Ok(ImportResult::Updated(tpk_status)) => (tpk_status, false), Ok(ImportResult::Unchanged(tpk_status)) => (tpk_status, false), - Err(_) => return UploadResponse::err(i18n!(i18n.catalog, "Error processing uploaded key.")), + Err(_) => { + return UploadResponse::err(i18n!(i18n.catalog, "Error processing uploaded key.")) + } }; let verify_state = { - let emails = tpk_status.email_status.iter() - .map(|(email,_)| email.clone()) + let emails = tpk_status + .email_status + .iter() + .map(|(email, _)| email.clone()) .collect(); VerifyTpkState { fpr: fp, addresses: emails, - requested: vec!(), + requested: vec![], } }; @@ -210,18 +217,19 @@ pub fn request_verify( }; if tpk_status.is_revoked { - return show_upload_verify( - rate_limiter, token, tpk_status, verify_state, false); + return show_upload_verify(rate_limiter, token, tpk_status, verify_state, false); } - let emails_requested: Vec<_> = addresses.into_iter() + let emails_requested: Vec<_> = addresses + .into_iter() .map(|address| address.parse::()) .flatten() .filter(|email| verify_state.addresses.contains(email)) - .filter(|email| tpk_status.email_status.iter() - .any(|(uid_email, status)| + .filter(|email| { + tpk_status.email_status.iter().any(|(uid_email, status)| { uid_email == email && *status == EmailAddressStatus::NotPublished - )) + }) + }) .collect(); for email in emails_requested { @@ -237,10 +245,7 @@ pub fn request_verify( ) .is_err() { - return UploadResponse::err(&format!( - "error sending email to {}", - &email - )); + return UploadResponse::err(&format!("error sending email to {}", &email)); } } @@ -252,12 +257,15 @@ fn check_tpk_state( token_stateless: &tokens::Service, i18n: &I18n, token: &str, -) -> Result<(VerifyTpkState,TpkStatus)> { - let verify_state = token_stateless.check::(token) - .map_err(|_| anyhow!(i18n!( - i18n.catalog, - "Upload session expired. Please try again." - )))?; +) -> Result<(VerifyTpkState, TpkStatus)> { + let verify_state = token_stateless + .check::(token) + .map_err(|_| { + anyhow!(i18n!( + i18n.catalog, + "Upload session expired. Please try again." + )) + })?; let tpk_status = db.get_tpk_status(&verify_state.fpr, &verify_state.addresses)?; Ok((verify_state, tpk_status)) } @@ -291,13 +299,12 @@ pub fn verify_confirm( ) -> response::PublishResponse { let (fingerprint, email) = match check_publish_token(db, token_service, token) { Ok(x) => x, - Err(_) => return PublishResponse::err( - i18n!(i18n.catalog, "Invalid verification link.")), + Err(_) => return PublishResponse::err(i18n!(i18n.catalog, "Invalid verification link.")), }; response::PublishResponse::Ok { fingerprint: fingerprint.to_string(), - email: email.to_string() + email: email.to_string(), } } @@ -305,7 +312,7 @@ fn check_publish_token( db: &KeyDatabase, token_service: &StatefulTokens, token: String, -) -> Result<(Fingerprint,Email)> { +) -> Result<(Fingerprint, Email)> { let payload = token_service.pop_token("verify", &token)?; let (fingerprint, email) = serde_json::from_str(&payload)?; @@ -335,28 +342,41 @@ fn show_upload_verify( }; } - let status: HashMap<_,_> = tpk_status.email_status + let status: HashMap<_, _> = tpk_status + .email_status .iter() - .map(|(email,status)| { - let is_pending = (*status == EmailAddressStatus::NotPublished) && - !rate_limiter.action_check(format!("verify-{}", &email)); + .map(|(email, status)| { + let is_pending = (*status == EmailAddressStatus::NotPublished) + && !rate_limiter.action_check(format!("verify-{}", &email)); if is_pending { (email.to_string(), EmailStatus::Pending) } else { - (email.to_string(), match status { - EmailAddressStatus::NotPublished => EmailStatus::Unpublished, - EmailAddressStatus::Published => EmailStatus::Published, - EmailAddressStatus::Revoked => EmailStatus::Revoked, - }) + ( + email.to_string(), + match status { + EmailAddressStatus::NotPublished => EmailStatus::Unpublished, + EmailAddressStatus::Published => EmailStatus::Published, + EmailAddressStatus::Revoked => EmailStatus::Revoked, + }, + ) } }) .collect(); - let primary_uid = tpk_status.email_status + let primary_uid = tpk_status + .email_status .get(0) .map(|(email, _)| email) .cloned(); let count_unparsed = tpk_status.unparsed_uids; - response::UploadResponse::Ok { token, key_fpr, count_unparsed, is_revoked: false, status, is_new_key, primary_uid } + response::UploadResponse::Ok { + token, + key_fpr, + count_unparsed, + is_revoked: false, + status, + is_new_key, + primary_uid, + } } diff --git a/src/web/vks_api.rs b/src/web/vks_api.rs index 5fbed6a..ac3478a 100644 --- a/src/web/vks_api.rs +++ b/src/web/vks_api.rs @@ -1,20 +1,21 @@ -use rocket::request::Request; use rocket::response::{self, Response, Responder}; -use rocket::http::{ContentType,Status}; +use rocket::http::{ContentType, Status}; +use rocket::request::Request; +use rocket::response::{self, Responder, Response}; use rocket::serde::json::Json; use rocket_i18n::{I18n, Translations}; use serde_json::json; use std::io::Cursor; -use crate::database::{KeyDatabase, StatefulTokens, Query}; use crate::database::types::{Email, Fingerprint, KeyID}; +use crate::database::{KeyDatabase, Query, StatefulTokens}; use crate::mail; -use crate::tokens; use crate::rate_limiter::RateLimiter; +use crate::tokens; use crate::web; -use crate::web::{RequestOrigin, MyResponse}; use crate::web::vks; use crate::web::vks::response::*; +use crate::web::{MyResponse, RequestOrigin}; use rocket::serde::json::Error as JsonError; @@ -34,18 +35,18 @@ pub mod json { pub keytext: String, } - #[derive(Serialize,Deserialize)] + #[derive(Serialize, Deserialize)] pub struct UploadResult { pub token: String, pub key_fpr: String, - pub status: HashMap, + pub status: HashMap, } } type JsonResult = Result; #[derive(Debug)] -pub struct JsonErrorResponse(Status,String); +pub struct JsonErrorResponse(Status, String); impl<'r> Responder<'r, 'static> for JsonErrorResponse { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { @@ -61,15 +62,26 @@ impl<'r> Responder<'r, 'static> for JsonErrorResponse { fn json_or_error(data: Result, JsonError>) -> Result, JsonErrorResponse> { match data { Ok(data) => Ok(data), - Err(JsonError::Io(_)) => Err(JsonErrorResponse(Status::InternalServerError, "i/o error!".to_owned())), + Err(JsonError::Io(_)) => Err(JsonErrorResponse( + Status::InternalServerError, + "i/o error!".to_owned(), + )), Err(JsonError::Parse(_, e)) => Err(JsonErrorResponse(Status::BadRequest, e.to_string())), } } fn upload_ok_json(response: UploadResponse) -> Result { match response { - UploadResponse::Ok { token, key_fpr, status, .. } => - Ok(json!(json::UploadResult { token, key_fpr, status })), + UploadResponse::Ok { + token, + key_fpr, + status, + .. + } => Ok(json!(json::UploadResult { + token, + key_fpr, + status + })), UploadResponse::OkMulti { key_fprs } => Ok(json!(key_fprs)), UploadResponse::Error(error) => Err(JsonErrorResponse(Status::BadRequest, error)), } @@ -91,28 +103,29 @@ pub fn upload_json( } #[post("/vks/v1/upload", rank = 2)] -pub fn upload_fallback( - origin: RequestOrigin, -) -> JsonErrorResponse { - let error_msg = format!("expected application/json data. see {}/about/api for api docs.", origin.get_base_uri()); +pub fn upload_fallback(origin: RequestOrigin) -> JsonErrorResponse { + let error_msg = format!( + "expected application/json data. see {}/about/api for api docs.", + origin.get_base_uri() + ); JsonErrorResponse(Status::BadRequest, error_msg) } -fn get_locale( - langs: &rocket::State, - locales: Vec, -) -> I18n { +fn get_locale(langs: &rocket::State, locales: Vec) -> I18n { locales .iter() .flat_map(|lang| lang.split(|c| c == '-' || c == ';' || c == '_').next()) .flat_map(|lang| langs.iter().find(|(trans, _)| trans == &lang)) .next() .or_else(|| langs.iter().find(|(trans, _)| trans == &"en")) - .map(|(lang, catalog)| I18n { catalog: catalog.clone(), lang }) + .map(|(lang, catalog)| I18n { + catalog: catalog.clone(), + lang, + }) .expect("Expected to have an english translation!") } -#[post("/vks/v1/request-verify", format = "json", data="")] +#[post("/vks/v1/request-verify", format = "json", data = "")] pub fn request_verify_json( db: &rocket::State, langs: &rocket::State, @@ -124,19 +137,32 @@ pub fn request_verify_json( data: Result, JsonError>, ) -> JsonResult { let data = json_or_error(data)?; - let json::VerifyRequest { token, addresses, locale } = data.into_inner(); + let json::VerifyRequest { + token, + addresses, + locale, + } = data.into_inner(); let i18n = get_locale(langs, locale.unwrap_or_default()); let result = vks::request_verify( - db, &origin, token_stateful, token_stateless, mail_service, - rate_limiter, &i18n, token, addresses); + db, + &origin, + token_stateful, + token_stateless, + mail_service, + rate_limiter, + &i18n, + token, + addresses, + ); upload_ok_json(result) } #[post("/vks/v1/request-verify", rank = 2)] -pub fn request_verify_fallback( - origin: RequestOrigin, -) -> JsonErrorResponse { - let error_msg = format!("expected application/json data. see {}/about/api for api docs.", origin.get_base_uri()); +pub fn request_verify_fallback(origin: RequestOrigin) -> JsonErrorResponse { + let error_msg = format!( + "expected application/json data. see {}/about/api for api docs.", + origin.get_base_uri() + ); JsonErrorResponse(Status::BadRequest, error_msg) } @@ -155,11 +181,7 @@ pub fn vks_v1_by_fingerprint( } #[get("/vks/v1/by-email/")] -pub fn vks_v1_by_email( - db: &rocket::State, - i18n: I18n, - email: String, -) -> MyResponse { +pub fn vks_v1_by_email(db: &rocket::State, i18n: I18n, email: String) -> MyResponse { let email = email.replace("%40", "@"); let query = match email.parse::() { Ok(email) => Query::ByEmail(email), @@ -170,11 +192,7 @@ pub fn vks_v1_by_email( } #[get("/vks/v1/by-keyid/")] -pub fn vks_v1_by_keyid( - db: &rocket::State, - i18n: I18n, - kid: String, -) -> MyResponse { +pub fn vks_v1_by_keyid(db: &rocket::State, i18n: I18n, kid: String) -> MyResponse { let query = match kid.parse::() { Ok(keyid) => Query::ByKeyID(keyid), Err(_) => return MyResponse::bad_request_plain("malformed key id"), diff --git a/src/web/vks_web.rs b/src/web/vks_web.rs index 682441a..0a0bcbb 100644 --- a/src/web/vks_web.rs +++ b/src/web/vks_web.rs @@ -4,21 +4,21 @@ use multipart::server::save::Entries; use multipart::server::save::SaveResult::*; use multipart::server::Multipart; +use gettext_macros::i18n; use rocket::data::ByteUnit; use rocket::form::Form; use rocket::form::ValueField; use rocket::http::ContentType; use rocket::Data; use rocket_i18n::I18n; -use gettext_macros::i18n; use url::percent_encoding::percent_decode; -use crate::database::{KeyDatabase, StatefulTokens, Query, Database}; -use crate::mail; -use crate::tokens; -use crate::web::{RequestOrigin, MyResponse}; -use crate::rate_limiter::RateLimiter; +use crate::database::{Database, KeyDatabase, Query, StatefulTokens}; use crate::i18n_helpers::describe_query_error; +use crate::mail; +use crate::rate_limiter::RateLimiter; +use crate::tokens; +use crate::web::{MyResponse, RequestOrigin}; use std::collections::HashMap; use std::io::Cursor; @@ -29,7 +29,7 @@ use crate::web::vks::response::*; const UPLOAD_LIMIT: ByteUnit = ByteUnit::Mebibyte(1); mod forms { - #[derive(FromForm,Deserialize)] + #[derive(FromForm, Deserialize)] pub struct VerifyRequest { pub token: String, pub address: String, @@ -90,12 +90,10 @@ mod template { pub address: String, pub requested: bool, } - } impl MyResponse { - fn upload_response_quick(response: UploadResponse, - i18n: I18n, origin: RequestOrigin) -> Self { + fn upload_response_quick(response: UploadResponse, i18n: I18n, origin: RequestOrigin) -> Self { match response { UploadResponse::Ok { token, .. } => { let uri = uri!(quick_upload_proceed(token)); @@ -105,23 +103,39 @@ impl MyResponse { uri ); MyResponse::plain(text) - }, - UploadResponse::OkMulti { key_fprs } => - MyResponse::plain(format!("Uploaded {} keys. For verification, please upload keys individually.\n", key_fprs.len())), - UploadResponse::Error(error) => MyResponse::bad_request( - "400-plain", anyhow!(error), i18n, origin), + } + UploadResponse::OkMulti { key_fprs } => MyResponse::plain(format!( + "Uploaded {} keys. For verification, please upload keys individually.\n", + key_fprs.len() + )), + UploadResponse::Error(error) => { + MyResponse::bad_request("400-plain", anyhow!(error), i18n, origin) + } } } - fn upload_response(response: UploadResponse, - i18n: I18n, origin: RequestOrigin) -> Self { + fn upload_response(response: UploadResponse, i18n: I18n, origin: RequestOrigin) -> Self { match response { - UploadResponse::Ok { token, key_fpr, is_revoked, count_unparsed, status, .. } => - Self::upload_ok(token, key_fpr, is_revoked, count_unparsed, status, i18n, origin), - UploadResponse::OkMulti { key_fprs } => - Self::upload_ok_multi(key_fprs, i18n, origin), - UploadResponse::Error(error) => MyResponse::bad_request( - "upload/upload", anyhow!(error), i18n, origin), + UploadResponse::Ok { + token, + key_fpr, + is_revoked, + count_unparsed, + status, + .. + } => Self::upload_ok( + token, + key_fpr, + is_revoked, + count_unparsed, + status, + i18n, + origin, + ), + UploadResponse::OkMulti { key_fprs } => Self::upload_ok_multi(key_fprs, i18n, origin), + UploadResponse::Error(error) => { + MyResponse::bad_request("upload/upload", anyhow!(error), i18n, origin) + } } } @@ -130,33 +144,35 @@ impl MyResponse { key_fpr: String, is_revoked: bool, count_unparsed: usize, - uid_status: HashMap, + uid_status: HashMap, i18n: I18n, origin: RequestOrigin, ) -> Self { let key_link = uri!(search(q = &key_fpr)).to_string(); - let count_revoked = uid_status.iter() - .filter(|(_,status)| **status == EmailStatus::Revoked) + let count_revoked = uid_status + .iter() + .filter(|(_, status)| **status == EmailStatus::Revoked) .count(); - let mut email_published: Vec<_> = uid_status.iter() - .filter(|(_,status)| **status == EmailStatus::Published) - .map(|(email,_)| email.to_string()) + let mut email_published: Vec<_> = uid_status + .iter() + .filter(|(_, status)| **status == EmailStatus::Published) + .map(|(email, _)| email.to_string()) .collect(); email_published.sort_unstable(); - let mut email_unpublished: Vec<_> = uid_status.into_iter() - .filter(|(_,status)| *status == EmailStatus::Unpublished || - *status == EmailStatus::Pending) - .map(|(email,status)| - template::UploadUidStatus { - address: email, - requested: status == EmailStatus::Pending, - }) + let mut email_unpublished: Vec<_> = uid_status + .into_iter() + .filter(|(_, status)| { + *status == EmailStatus::Unpublished || *status == EmailStatus::Pending + }) + .map(|(email, status)| template::UploadUidStatus { + address: email, + requested: status == EmailStatus::Pending, + }) .collect(); - email_unpublished - .sort_unstable_by(|fst,snd| fst.address.cmp(&snd.address)); + email_unpublished.sort_unstable_by(|fst, snd| fst.address.cmp(&snd.address)); let context = template::VerificationSent { is_revoked, @@ -173,9 +189,9 @@ impl MyResponse { MyResponse::ok("upload/upload-ok", context, i18n, origin) } - fn upload_ok_multi(key_fprs: Vec, - i18n: I18n, origin: RequestOrigin) -> Self { - let keys = key_fprs.into_iter() + fn upload_ok_multi(key_fprs: Vec, i18n: I18n, origin: RequestOrigin) -> Self { + let keys = key_fprs + .into_iter() .map(|fpr| { let key_link = uri!(search(q = &fpr)).to_string(); template::UploadOkKey { @@ -185,9 +201,7 @@ impl MyResponse { }) .collect(); - let context = template::UploadOkMultiple { - keys, - }; + let context = template::UploadOkMultiple { keys }; MyResponse::ok("upload/upload-ok-multiple", context, i18n, origin) } @@ -208,9 +222,7 @@ pub async fn upload_post_form_data( cont_type: &ContentType, data: Data<'_>, ) -> MyResponse { - match process_upload(db, tokens_stateless, rate_limiter, &i18n, data, cont_type) - .await - { + match process_upload(db, tokens_stateless, rate_limiter, &i18n, data, cont_type).await { Ok(response) => MyResponse::upload_response(response, i18n, origin), Err(err) => MyResponse::bad_request("upload/upload", err, i18n, origin), } @@ -224,8 +236,7 @@ pub async fn process_post_form_data( cont_type: &ContentType, data: Data<'_>, ) -> Result { - process_upload(db, tokens_stateless, rate_limiter, &i18n, data, cont_type) - .await + process_upload(db, tokens_stateless, rate_limiter, &i18n, data, cont_type).await } #[get("/search?")] @@ -251,14 +262,17 @@ fn key_to_response( let fp = if let Some(fp) = db.lookup_primary_fingerprint(&query) { fp } else if query.is_invalid() { - return MyResponse::bad_request("index", anyhow!(describe_query_error(&i18n, &query)), - i18n, origin); + return MyResponse::bad_request( + "index", + anyhow!(describe_query_error(&i18n, &query)), + i18n, + origin, + ); } else { - return MyResponse::not_found(None, describe_query_error(&i18n, &query), - i18n, origin); + return MyResponse::not_found(None, describe_query_error(&i18n, &query), i18n, origin); }; - let context = template::Search{ + let context = template::Search { query: query_string, fpr: fp.to_string(), }; @@ -266,7 +280,6 @@ fn key_to_response( MyResponse::ok("found", context, i18n, origin) } - #[put("/", data = "")] pub async fn quick_upload( db: &rocket::State, @@ -278,20 +291,14 @@ pub async fn quick_upload( ) -> MyResponse { let buf = match data.open(UPLOAD_LIMIT).into_bytes().await { Ok(buf) => buf.into_inner(), - Err(error) => - return MyResponse::bad_request("400-plain", anyhow!(error), - i18n, origin), + Err(error) => return MyResponse::bad_request("400-plain", anyhow!(error), i18n, origin), }; MyResponse::upload_response_quick( - vks::process_key( - db, - &i18n, - tokens_stateless, - rate_limiter, - Cursor::new(buf) - ), - i18n, origin) + vks::process_key(db, &i18n, tokens_stateless, rate_limiter, Cursor::new(buf)), + i18n, + origin, + ) } #[get("/upload/", rank = 2)] @@ -306,13 +313,24 @@ pub fn quick_upload_proceed( token: String, ) -> MyResponse { let result = vks::request_verify( - db, &origin, token_stateful, token_stateless, mail_service, - rate_limiter, &i18n, token, vec!()); + db, + &origin, + token_stateful, + token_stateless, + mail_service, + rate_limiter, + &i18n, + token, + vec![], + ); MyResponse::upload_response(result, i18n, origin) } - -#[post("/upload/submit", format = "application/x-www-form-urlencoded", data = "")] +#[post( + "/upload/submit", + format = "application/x-www-form-urlencoded", + data = "" +)] pub async fn upload_post_form( db: &rocket::State, origin: RequestOrigin, @@ -322,10 +340,8 @@ pub async fn upload_post_form( data: Data<'_>, ) -> MyResponse { match process_post_form(db, tokens_stateless, rate_limiter, &i18n, data).await { - Ok(response) => MyResponse::upload_response(response, - i18n, origin), - Err(err) => MyResponse::bad_request("upload/upload", err, - i18n, origin), + Ok(response) => MyResponse::upload_response(response, i18n, origin), + Err(err) => MyResponse::bad_request("upload/upload", err, i18n, origin), } } @@ -340,9 +356,9 @@ pub async fn process_post_form( let buf = data.open(UPLOAD_LIMIT).into_bytes().await?; for ValueField { name, value } in Form::values(&*String::from_utf8_lossy(&buf)) { - let decoded_value = percent_decode(value.as_bytes()).decode_utf8().map_err(|_| - anyhow!("`Content-Type: application/x-www-form-urlencoded` not valid") - )?; + let decoded_value = percent_decode(value.as_bytes()) + .decode_utf8() + .map_err(|_| anyhow!("`Content-Type: application/x-www-form-urlencoded` not valid"))?; if name.to_string().as_str() == "keytext" { return Ok(vks::process_key( @@ -350,7 +366,7 @@ pub async fn process_post_form( i18n, tokens_stateless, rate_limiter, - Cursor::new(decoded_value.as_bytes()) + Cursor::new(decoded_value.as_bytes()), )); } } @@ -358,7 +374,6 @@ pub async fn process_post_form( Err(anyhow!("No keytext found")) } - async fn process_upload( db: &KeyDatabase, tokens_stateless: &tokens::Service, @@ -371,21 +386,23 @@ async fn process_upload( let (_, boundary) = cont_type .params() .find(|&(k, _)| k == "boundary") - .ok_or_else(|| anyhow!("`Content-Type: multipart/form-data` \ - boundary param not provided"))?; + .ok_or_else(|| { + anyhow!( + "`Content-Type: multipart/form-data` \ + boundary param not provided" + ) + })?; // saves all fields, any field longer than 10kB goes to a temporary directory // Entries could implement FromData though that would give zero control over // how the files are saved; Multipart would be a good impl candidate though let data = Cursor::new(data.open(UPLOAD_LIMIT).into_bytes().await?.value); match Multipart::with_body(data, boundary).save().temp() { - Full(entries) => { - process_multipart(db, tokens_stateless, rate_limiter, i18n, entries) - } + Full(entries) => process_multipart(db, tokens_stateless, rate_limiter, i18n, entries), Partial(partial, _) => { process_multipart(db, tokens_stateless, rate_limiter, i18n, partial.entries) } - Error(err) => Err(err.into()) + Error(err) => Err(err.into()), } } @@ -399,14 +416,24 @@ fn process_multipart( match entries.fields.get("keytext") { Some(ent) if ent.len() == 1 => { let reader = ent[0].data.readable()?; - Ok(vks::process_key(db, i18n, tokens_stateless, rate_limiter, reader)) + Ok(vks::process_key( + db, + i18n, + tokens_stateless, + rate_limiter, + reader, + )) } Some(_) => Err(anyhow!("Multiple keytexts found")), None => Err(anyhow!("No keytext found")), } } -#[post("/upload/request-verify", format = "application/x-www-form-urlencoded", data="")] +#[post( + "/upload/request-verify", + format = "application/x-www-form-urlencoded", + data = "" +)] pub fn request_verify_form( db: &rocket::State, origin: RequestOrigin, @@ -419,12 +446,24 @@ pub fn request_verify_form( ) -> MyResponse { let forms::VerifyRequest { token, address } = request.into_inner(); let result = vks::request_verify( - db, &origin, token_stateful, token_stateless, mail_service, - rate_limiter, &i18n, token, vec!(address)); + db, + &origin, + token_stateful, + token_stateless, + mail_service, + rate_limiter, + &i18n, + token, + vec![address], + ); MyResponse::upload_response(result, i18n, origin) } -#[post("/upload/request-verify", format = "multipart/form-data", data="")] +#[post( + "/upload/request-verify", + format = "multipart/form-data", + data = "" +)] pub fn request_verify_form_data( db: &rocket::State, origin: RequestOrigin, @@ -437,8 +476,16 @@ pub fn request_verify_form_data( ) -> MyResponse { let forms::VerifyRequest { token, address } = request.into_inner(); let result = vks::request_verify( - db, &origin, token_stateful, token_stateless, mail_service, - rate_limiter, &i18n, token, vec!(address)); + db, + &origin, + token_stateful, + token_stateless, + mail_service, + rate_limiter, + &i18n, + token, + vec![address], + ); MyResponse::upload_response(result, i18n, origin) } @@ -463,12 +510,15 @@ pub fn verify_confirm( }; MyResponse::ok("upload/publish-result", context, i18n, origin) - }, + } PublishResponse::Error(error) => { let error_msg = if rate_limiter.action_check(rate_limit_id) { anyhow!(error) } else { - anyhow!(i18n!(i18n.catalog, "This address has already been verified.")) + anyhow!(i18n!( + i18n.catalog, + "This address has already been verified." + )) }; MyResponse::bad_request("400", error_msg, i18n, origin) } @@ -476,12 +526,11 @@ pub fn verify_confirm( } #[get("/verify/")] -pub fn verify_confirm_form( - origin: RequestOrigin, - i18n: I18n, - token: String, -) -> MyResponse { - MyResponse::ok("upload/verification-form", template::VerifyForm { - token - }, i18n, origin) +pub fn verify_confirm_form(origin: RequestOrigin, i18n: I18n, token: String) -> MyResponse { + MyResponse::ok( + "upload/verification-form", + template::VerifyForm { token }, + i18n, + origin, + ) } diff --git a/src/web/wkd.rs b/src/web/wkd.rs index f195b46..245f339 100644 --- a/src/web/wkd.rs +++ b/src/web/wkd.rs @@ -3,24 +3,16 @@ use crate::web::MyResponse; // WKD queries #[get("/.well-known/openpgpkey//hu/")] -pub fn wkd_query( - db: &rocket::State, - domain: String, - wkd_hash: String, -) -> MyResponse { +pub fn wkd_query(db: &rocket::State, domain: String, wkd_hash: String) -> MyResponse { match db.by_domain_and_hash_wkd(&domain, &wkd_hash) { Some(key) => MyResponse::wkd(key, &wkd_hash), - None => MyResponse::not_found_plain( - "No key found for this email address.", - ), + None => MyResponse::not_found_plain("No key found for this email address."), } } // Policy requests. // 200 response with an empty body. #[get("/.well-known/openpgpkey/<_domain>/policy")] -pub fn wkd_policy( - _domain: String, -) -> MyResponse { +pub fn wkd_policy(_domain: String) -> MyResponse { MyResponse::plain("".to_string()) }