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.
This commit is contained in:
Neal H. Walfield 2024-02-28 13:38:21 +01:00
parent 92e8c5c1d5
commit 011c426970
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
4 changed files with 73 additions and 13 deletions

4
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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"));

View File

@ -200,7 +200,7 @@ fn serialize_keyring(mut output: &mut dyn io::Write, certs: Vec<Cert>,
/// 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<UserID> = OnceLock::new();
primary_uid =
Some(FALLBACK.get_or_init(|| UserID::from("<unknown>")));
}
}
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.
"<unknown>".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<wot::QueryBuilder<&WotStore<'store, 'rstore>>>
{
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)`.