Port sq pki link retract to the user ID designator framework.
- Change `sq pki link retract` to use the user ID designator framework.
This commit is contained in:
parent
d2f762ed36
commit
50085f3c50
@ -432,25 +432,10 @@ to force the signature to be re-created anyway.",
|
||||
cert_designator::CertPrefix,
|
||||
cert_designator::OneValue>,
|
||||
|
||||
#[clap(
|
||||
long = "userid",
|
||||
value_name = "USERID",
|
||||
required = false,
|
||||
help = "A User ID to unlink from the certificate.",
|
||||
long_help = "A User ID to unlink from the certificate. This must match \
|
||||
a known User ID, although it need not be linked.",
|
||||
)]
|
||||
pub userid: Vec<String>,
|
||||
#[clap(
|
||||
long = "email",
|
||||
value_name = "email",
|
||||
required = false,
|
||||
help = "An email address to unlink from the certificate.",
|
||||
long_help = "An email address to unlink from the certificate. The email \
|
||||
address must match a User ID with the email, although, \
|
||||
it need not be linked.",
|
||||
)]
|
||||
pub email: Vec<String>,
|
||||
#[command(flatten)]
|
||||
pub userids: UserIDDesignators<
|
||||
userid_designator::UserIDEmailArgs,
|
||||
userid_designator::OptionalValue>,
|
||||
}
|
||||
|
||||
const RETRACT_EXAMPLES: Actions = Actions {
|
||||
|
@ -18,6 +18,10 @@ pub type AllUserIDsArg = typenum::U4;
|
||||
/// Adds a `--add-userid` argument.
|
||||
pub type AddUserIDArg = typenum::U8;
|
||||
|
||||
/// Enables --userid, and --email.
|
||||
pub type UserIDEmailArgs
|
||||
= <UserIDArg as std::ops::BitOr<EmailArg>>::Output;
|
||||
|
||||
/// Enables --userid, --email, and --add-userid.
|
||||
pub type MaybeSelfSignedUserIDEmailArgs
|
||||
= <<UserIDArg as std::ops::BitOr<EmailArg>>::Output
|
||||
|
@ -1,189 +1,19 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::Result;
|
||||
use openpgp::cert::prelude::*;
|
||||
use openpgp::packet::prelude::*;
|
||||
|
||||
use sequoia_cert_store as cert_store;
|
||||
use cert_store::Store;
|
||||
use cert_store::store::UserIDQueryParams;
|
||||
|
||||
use crate::Sq;
|
||||
use crate::commands::active_certification;
|
||||
use crate::commands::pki::TrustAmount;
|
||||
use crate::parse_notations;
|
||||
use crate::print_error_chain;
|
||||
|
||||
use crate::cli::pki::link;
|
||||
use crate::cli::types::Expiration;
|
||||
|
||||
/// Checks that the search terms provided to --userid, and --email
|
||||
/// match known User IDs.
|
||||
///
|
||||
/// If `self_signed` is true, then only self-signed User IDs are
|
||||
/// considered.
|
||||
///
|
||||
/// On success, returns the matching User IDs. This includes mapping
|
||||
/// email addresses to their matching User IDs. If an email address
|
||||
/// matches multiple User IDs, they are all returned.
|
||||
pub fn check_userids(sq: &Sq, cert: &Cert, self_signed: bool,
|
||||
userids: &Vec<String>, emails: &Vec<String>)
|
||||
-> Result<Vec<UserID>>
|
||||
{
|
||||
if userids.is_empty() && emails.is_empty() {
|
||||
// Nothing to do.
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let userids = userids.iter()
|
||||
.map(|u| UserID::from(&u[..]))
|
||||
.collect::<Vec<UserID>>();
|
||||
|
||||
let emails = emails.iter()
|
||||
.map(|email| {
|
||||
match UserIDQueryParams::is_email(email) {
|
||||
Ok(email) => {
|
||||
// We have the normalized email address.
|
||||
Ok(email)
|
||||
}
|
||||
Err(err) => {
|
||||
let err = anyhow::Error::from(err).context(format!(
|
||||
"{:?} is not a valid email address", email));
|
||||
print_error_chain(&err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<String>>>()?;
|
||||
|
||||
let self_signed_userids = || -> Result<Vec<UserID>> {
|
||||
let vc = cert.with_policy(sq.policy, sq.time)
|
||||
.with_context(|| {
|
||||
format!("{} is not valid according to the current policy",
|
||||
cert.fingerprint())
|
||||
})?;
|
||||
Ok(vc.userids().map(|ua| ua.userid().clone()).collect())
|
||||
};
|
||||
|
||||
let known_userids: Vec<UserID> = if self_signed {
|
||||
// Only consider User IDs that have a valid self signature.
|
||||
self_signed_userids()?
|
||||
} else {
|
||||
// Consider any known UserID.
|
||||
cert.userids().map(|ua| ua.userid().clone()).collect()
|
||||
};
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut error = None;
|
||||
|
||||
for userid in userids.into_iter() {
|
||||
if known_userids.iter().any(|known_userid| known_userid == &userid) {
|
||||
results.push(userid);
|
||||
} else {
|
||||
let err = anyhow::anyhow!(
|
||||
"{:?} does not match any self-signed User IDs. If you want \
|
||||
to use a User ID that is not endorsed by the key's owner, \
|
||||
use \"--petname\"",
|
||||
String::from_utf8_lossy(userid.value()));
|
||||
print_error_chain(&err);
|
||||
if error.is_none() {
|
||||
error = Some(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ! emails.is_empty() {
|
||||
let known_emails = known_userids.iter()
|
||||
.filter_map(|known_userid| {
|
||||
if let Ok(Some(email)) = known_userid.email_normalized() {
|
||||
Some((known_userid.clone(), email))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(UserID, String)>>();
|
||||
|
||||
for email in emails.into_iter() {
|
||||
let mut matches = known_emails.iter()
|
||||
.filter_map(|(known_userid, known_email)| {
|
||||
if known_email == &email {
|
||||
Some(known_userid.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<UserID>>();
|
||||
|
||||
if matches.is_empty() {
|
||||
let err = anyhow::anyhow!(
|
||||
"{:?} does not match any valid, self-signed email \
|
||||
addresses. If you want to use an email address that \
|
||||
is not endorsed by the key's owner, use \"--petname\"",
|
||||
email);
|
||||
print_error_chain(&err);
|
||||
if error.is_none() {
|
||||
error = Some(err);
|
||||
}
|
||||
} else {
|
||||
results.append(&mut matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(err) = error {
|
||||
// Some of the search terms did not match, print some
|
||||
// diagnostics, and bail.
|
||||
|
||||
if known_userids.is_empty() {
|
||||
if self_signed {
|
||||
wprintln!("{} has no self-signed User IDs.",
|
||||
cert.fingerprint());
|
||||
} else {
|
||||
wprintln!("{} has no known User IDs.",
|
||||
cert.fingerprint());
|
||||
}
|
||||
} else {
|
||||
if self_signed {
|
||||
wprintln!("{} has the following self-signed User IDs:",
|
||||
cert.fingerprint());
|
||||
} else {
|
||||
wprintln!("{} has the following known User IDs:",
|
||||
cert.fingerprint());
|
||||
}
|
||||
|
||||
// Show whether a User ID is self-signed or not, unless we
|
||||
// are only interested in self-signed User IDs, in which
|
||||
// don't bother; it's redundant.
|
||||
let self_signed_userids = if self_signed {
|
||||
vec![]
|
||||
} else {
|
||||
self_signed_userids().unwrap_or(vec![])
|
||||
};
|
||||
|
||||
for (i, userid) in known_userids.iter().enumerate() {
|
||||
wprintln!(
|
||||
" {}. {:?}{}",
|
||||
i + 1, String::from_utf8_lossy(userid.value()),
|
||||
if self_signed_userids.contains(userid) {
|
||||
" (self signed)"
|
||||
} else {
|
||||
""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Err(err)
|
||||
} else {
|
||||
results.sort();
|
||||
results.dedup();
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link(sq: Sq, c: link::Command) -> Result<()> {
|
||||
use link::Subcommands::*;
|
||||
match c.subcommand {
|
||||
@ -286,9 +116,8 @@ pub fn retract(sq: Sq, c: link::RetractCommand)
|
||||
let (cert, _from_file)
|
||||
= sq.resolve_cert(&c.cert, sequoia_wot::FULLY_TRUSTED)?;
|
||||
|
||||
let mut userids =
|
||||
check_userids(&sq, &cert, false, &c.userid, &c.email)
|
||||
.context("sq pki link retract: Invalid User IDs")?;
|
||||
let vc = cert.with_policy(sq.policy, Some(sq.time))?;
|
||||
let mut userids = c.userids.resolve(&vc)?;
|
||||
|
||||
let user_supplied_userids = if userids.is_empty() {
|
||||
// Nothing was specified. Retract all known User IDs.
|
||||
|
Loading…
Reference in New Issue
Block a user