Change sq key list to display more user IDs.
- Currently, `sq key list` only displays a single best user ID for each certificate. - Instead, display all user IDs that can be authenticated, or are self-signed. Also indicate the degree to which they can be authenticated, and whether the user ID has been revoked. - Fixes #360.
This commit is contained in:
parent
60b369274b
commit
149254b756
@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
collections::{BTreeMap, BTreeSet, HashSet},
|
||||
fmt,
|
||||
time::SystemTime,
|
||||
};
|
||||
@ -11,7 +11,7 @@ use sequoia_openpgp::{
|
||||
KeyHandle,
|
||||
cert::amalgamation::ValidAmalgamation,
|
||||
cert::amalgamation::ValidateAmalgamation,
|
||||
packet::{Key, key},
|
||||
packet::{Key, key, UserID},
|
||||
types::RevocationStatus,
|
||||
};
|
||||
|
||||
@ -20,6 +20,7 @@ use keystore::Protection;
|
||||
|
||||
use crate::cli;
|
||||
use crate::Convert;
|
||||
use crate::PreferredUserID;
|
||||
use crate::Sq;
|
||||
use crate::Result;
|
||||
use crate::Time;
|
||||
@ -54,14 +55,6 @@ impl Association {
|
||||
Association::Bare(k) => k,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the best user ID, if any.
|
||||
pub fn best_userid(&self, sq: &Sq) -> String {
|
||||
match self {
|
||||
Association::Bound(c) => sq.best_userid(c, true).to_string(),
|
||||
Association::Bare(_) => "bare key".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Association {
|
||||
@ -382,6 +375,22 @@ pub fn list(sq: Sq, mut cmd: cli::key::list::Command) -> Result<()> {
|
||||
}
|
||||
|
||||
// Now display the keys grouped by OpenPGP certificates.
|
||||
|
||||
let q = sq.wot_query();
|
||||
let q = q.map(|q| q.build()).ok();
|
||||
let authenticate = |cert: &Cert, userid: &UserID|
|
||||
-> (usize, PreferredUserID)
|
||||
{
|
||||
if let Some(q) = q.as_ref() {
|
||||
let paths = q.authenticate(
|
||||
userid, &cert.fingerprint(), sequoia_wot::FULLY_TRUSTED);
|
||||
let amount = paths.amount();
|
||||
(amount, PreferredUserID::from_userid(userid.clone(), amount))
|
||||
} else {
|
||||
(0, PreferredUserID::from_userid(userid.clone(), 0))
|
||||
}
|
||||
};
|
||||
|
||||
for (association, keys) in the_keys.iter() {
|
||||
if let Some(c) = &certs {
|
||||
// Skip the keys the user is not interested in.
|
||||
@ -398,7 +407,69 @@ pub fn list(sq: Sq, mut cmd: cli::key::list::Command) -> Result<()> {
|
||||
// Emit metadata.
|
||||
wprintln!(initial_indent = " - ", "{}",
|
||||
association.key().fingerprint());
|
||||
wprintln!(initial_indent = " - ", "{}", association.best_userid(&sq));
|
||||
|
||||
// Show the user IDs that can be authenticated or are self signed.
|
||||
if let Some(cert) = association.cert() {
|
||||
let self_signed: HashSet<UserID> = if let Ok(vc)
|
||||
= cert.with_policy(sq.policy, sq.time)
|
||||
{
|
||||
HashSet::from_iter(vc.userids().map(|ua| ua.userid()).cloned())
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let mut userids = Vec::with_capacity(cert.userids().count());
|
||||
for ua in cert.userids() {
|
||||
let revoked = if let RevocationStatus::Revoked(_)
|
||||
= ua.revocation_status(sq.policy, sq.time)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let self_signed = self_signed.contains(&ua.userid());
|
||||
|
||||
let (amount, userid) = authenticate(cert, ua.userid());
|
||||
if amount > 0 || self_signed {
|
||||
userids.push((revoked, amount, self_signed, userid));
|
||||
}
|
||||
}
|
||||
|
||||
if userids.is_empty() {
|
||||
wprintln!(initial_indent = " - ", "no user IDs");
|
||||
} else {
|
||||
let userid_count = userids.len();
|
||||
if userid_count > 1 {
|
||||
wprintln!(initial_indent = " - ", "user IDs:");
|
||||
}
|
||||
|
||||
userids.sort_by(
|
||||
|(a_revoked, a_amount, a_self_signed, a_userid),
|
||||
(b_revoked, b_amount, b_self_signed, b_userid) |
|
||||
{
|
||||
a_revoked.cmp(b_revoked)
|
||||
.then(a_amount.cmp(b_amount).reverse())
|
||||
.then(a_self_signed.cmp(b_self_signed))
|
||||
.then(a_userid.cmp(b_userid))
|
||||
});
|
||||
for (revoked, amount, self_signed, userid)
|
||||
in userids.into_iter()
|
||||
{
|
||||
if amount > 0 || self_signed {
|
||||
if userid_count == 1 {
|
||||
wprintln!(initial_indent = " - ", "user ID: {}{}",
|
||||
userid,
|
||||
if revoked { " revoked" } else { "" });
|
||||
} else {
|
||||
wprintln!(initial_indent = " - ", "{}{}",
|
||||
userid,
|
||||
if revoked { " revoked" } else { "" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wprintln!(initial_indent = " - ", "created {}",
|
||||
association.key().creation_time().convert());
|
||||
|
||||
|
@ -30,6 +30,7 @@ pub const NULL_POLICY: &NullPolicy = &NullPolicy::new();
|
||||
/// Something like a User ID.
|
||||
///
|
||||
/// This is used to avoid unnecessary allocations.
|
||||
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
|
||||
enum UserIDLike {
|
||||
UserID(UserID),
|
||||
String(String),
|
||||
@ -44,6 +45,7 @@ enum UserIDLike {
|
||||
/// ```text
|
||||
/// format!("{:.70}", userid);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct PreferredUserID {
|
||||
userid: UserIDLike,
|
||||
trust_amount: usize,
|
||||
|
@ -355,7 +355,8 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
|
||||
/// Returns a web-of-trust query builder.
|
||||
///
|
||||
/// The trust roots are already set appropriately.
|
||||
fn wot_query(&self) -> Result<wot::NetworkBuilder<&WotStore<'store, 'rstore>>>
|
||||
pub fn wot_query(&self)
|
||||
-> Result<wot::NetworkBuilder<&WotStore<'store, 'rstore>>>
|
||||
{
|
||||
let cert_store = self.cert_store_or_else()?;
|
||||
let network = wot::NetworkBuilder::rooted(cert_store,
|
||||
|
Loading…
Reference in New Issue
Block a user