From 011c426970e971ca3feabaecb2c8549a6df28eaa Mon Sep 17 00:00:00 2001 From: "Neal H. Walfield" Date: Wed, 28 Feb 2024 13:38:21 +0100 Subject: [PATCH] Change best_effort_primary_uid to prefer authenticated user IDs. - Change `best_effort_primary_uid` to prefer authenticated user IDs to user IDs that are only self signed. --- Cargo.lock | 4 +-- Cargo.toml | 2 +- src/commands/pki.rs | 2 +- src/sq.rs | 78 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c93b8f24..fafd77c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3370,9 +3370,9 @@ dependencies = [ [[package]] name = "sequoia-wot" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f383e37d18e0b9ae9e39ead83c581d5fc4f73e20bc1f4f7b76185d7136698b5" +checksum = "d16930db37050e74cfdae18654108e8f78eeeb3d659336223b657ccc9a3a6141" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 09058911..034370b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ itertools = ">=0.10, <0.13" once_cell = "1.17" sequoia-cert-store = "0.5.0" sequoia-keystore = { version = "0.2" } -sequoia-wot = "0.10" +sequoia-wot = "0.11" tempfile = "3.1" tokio = { version = "1.13.1" } rpassword = "7.0" diff --git a/src/commands/pki.rs b/src/commands/pki.rs index bc52f4da..2731878f 100644 --- a/src/commands/pki.rs +++ b/src/commands/pki.rs @@ -89,7 +89,7 @@ fn authenticate<'store, 'rstore>( where 'store: 'rstore, { // Build the network. - let mut cert_store = match config.cert_store() { + let cert_store = match config.cert_store() { Ok(Some(cert_store)) => cert_store, Ok(None) => { return Err(anyhow::anyhow!("Certificate store has been disabled")); diff --git a/src/sq.rs b/src/sq.rs index 13fea896..2f9e7184 100644 --- a/src/sq.rs +++ b/src/sq.rs @@ -200,7 +200,7 @@ fn serialize_keyring(mut output: &mut dyn io::Write, certs: Vec, /// Best-effort heuristic to compute the primary User ID of a given cert. /// /// The returned string is already sanitized, and safe for displaying. -pub fn best_effort_primary_uid<'u, T>(_config: Option<&Config>, +pub fn best_effort_primary_uid<'u, T>(config: Option<&Config>, cert: &'u Cert, policy: &'u dyn Policy, time: T) @@ -237,17 +237,65 @@ where if primary_uid.is_none() { if let Some(primary) = cert.userids().next() { primary_uid = Some(primary.userid()); - } else { - // Special case, there is no user id. - use std::sync::OnceLock; - static FALLBACK: OnceLock = OnceLock::new(); - primary_uid = - Some(FALLBACK.get_or_init(|| UserID::from(""))); } } - let primary_uid = primary_uid.expect("set at this point"); - Safe(primary_uid).to_string() + if let Some(primary_uid) = primary_uid { + let fpr = cert.fingerprint(); + + let mut candidate: (&UserID, usize) = (primary_uid, 0); + + #[allow(clippy::never_loop)] + loop { + // Don't fail if we can't query the user's web of trust. + let Some(config) = config else { break; }; + let Ok(q) = config.wot_query() else { break; }; + let q = q.build(); + let authenticate = move |userid: &UserID| { + let paths = q.authenticate(userid, &fpr, wot::FULLY_TRUSTED); + paths.amount() + }; + + // We're careful to *not* use a ValidCert so that we see all + // user IDs, even those that are not self signed. + + candidate = (primary_uid, authenticate(primary_uid)); + + for userid in cert.userids() { + let userid = userid.component(); + + if candidate.1 >= wot::FULLY_TRUSTED { + // Done. + break; + } + + if userid == primary_uid { + // We already considered this one. + continue; + } + + let amount = authenticate(&userid); + if amount > candidate.1 { + candidate = (userid, amount); + } + } + + break; + } + + let (uid, amount) = candidate; + let uid = Safe(uid); + if amount == 0 { + format!("{} (UNAUTHENTICATED)", uid) + } else if amount < wot::FULLY_TRUSTED { + format!("{} (partially authenticated, {}/{})", uid, amount, wot::FULLY_TRUSTED) + } else { + format!("{} (authenticated)", uid) + } + } else { + // Special case, there is no user id. + "".to_string() + } } // Decrypts a key, if possible. @@ -567,6 +615,18 @@ impl<'store: 'rstore, 'rstore> Config<'store, 'rstore> { } + /// Returns a web-of-trust query builder. + /// + /// The trust roots are already set appropriately. + fn wot_query(&self) -> Result>> + { + let cert_store = self.cert_store_or_else()?; + let network = wot::Network::new(cert_store)?; + let mut query = wot::QueryBuilder::new_owned(network.into()); + query.roots(wot::Roots::new(self.trust_roots())); + Ok(query) + } + /// Returns the key store's path. /// /// If the key store is disabled, returns `Ok(None)`.