Use cert designators for sq key revoke.

- See #207.
This commit is contained in:
Justus Winter 2024-10-23 17:02:42 +02:00
parent 41c536de4a
commit 37e2b65c6f
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
6 changed files with 110 additions and 81 deletions

3
NEWS
View File

@ -121,6 +121,9 @@
- The argument `sq key expire --cert-file` has been renamed to
`--file`.
- The argument `sq key expire --file` now requires `--output`.
- The argument `sq key revoke --cert-file` has been renamed to
`--file`.
- The argument `sq key revoke --file` now requires `--output`.
* Changes in 0.38.0
** Notable changes

View File

@ -1,17 +1,13 @@
//! Command-line parser for `sq key revoke`.
use clap::Args;
use clap::ArgGroup;
use sequoia_openpgp as openpgp;
use openpgp::KeyHandle;
use crate::cli::types::ClapData;
use crate::cli::types::FileOrStdin;
use crate::cli::types::FileOrStdout;
use crate::cli::examples::*;
use crate::cli::key::KeyReasonForRevocation;
use crate::cli::types::cert_designator::*;
const REVOKE_EXAMPLES: Actions = Actions {
actions: &[
@ -67,56 +63,18 @@ instead of the current time.
",
after_help = REVOKE_EXAMPLES,
)]
#[clap(group(ArgGroup::new("cert_input").args(&["cert_file", "cert"]).required(true)))]
#[clap(group(ArgGroup::new("revoker_input").args(&["revoker_file", "revoker"])))]
pub struct Command {
#[clap(
long,
value_name = "FINGERPRINT|KEYID",
help = "The certificate to revoke",
)]
pub cert: Option<KeyHandle>,
#[clap(
long,
value_name = "CERT_FILE",
conflicts_with = "cert",
help = "The certificate to revoke",
long_help = "\
The certificate to revoke.
#[command(flatten)]
pub cert: CertDesignators<CertUserIDEmailFileArgs,
NoPrefix,
OneValueAndFileRequiresOutput,
KeyRevokeCertDoc>,
Read the certificate to revoke from FILE or stdin, if `-`. It is \
an error for the file to contain more than one certificate.",
)]
pub cert_file: Option<FileOrStdin>,
#[clap(
long,
value_name = "FINGERPRINT|KEYID",
help = "The certificate that issues the revocation",
long_help = "\
The certificate that issues the revocation.
Sign the revocation certificate using the specified key. By default, \
the certificate being revoked is used. Using this option, it is \
possible to create a third-party revocation.",
)]
pub revoker: Option<KeyHandle>,
#[clap(
long,
value_name = "KEY_FILE",
conflicts_with = "revoker",
help = "The certificate that issues the revocation",
long_help = "\
The certificate that issues the revocation.
Sign the revocation certificate using the specified key. By default, \
the certificate being revoked is used. Using this option, it is \
possible to create a third-party revocation.
Read the certificate from KEY_FILE or stdin, if `-`. It is an error \
for the file to contain more than one certificate.",
)]
pub revoker_file: Option<FileOrStdin>,
#[command(flatten)]
pub revoker: CertDesignators<CertUserIDEmailFileArgs,
RevokerPrefix,
OneOptionalValue,
KeyRevokeRevokerDoc>,
#[clap(
value_name = "REASON",
@ -185,3 +143,35 @@ modified certificate to stdout.",
)]
pub binary: bool,
}
/// Documentation for the cert designators for the key revoke.
pub struct KeyRevokeCertDoc {}
impl AdditionalDocs for KeyRevokeCertDoc {
fn help(arg: &'static str, help: &'static str) -> clap::builder::StyledStr {
match arg {
"file" =>
"Revoke the key read from PATH"
.into(),
_ => {
debug_assert!(help.starts_with("Use certificates"));
help.replace("Use certificates",
"Revoke the key")
},
}.into()
}
}
/// Documentation for the revoker designators for the key revoke.
pub struct KeyRevokeRevokerDoc {}
impl AdditionalDocs for KeyRevokeRevokerDoc {
fn help(_: &'static str, help: &'static str) -> clap::builder::StyledStr {
format!("{} to create the revocation certificate.
Sign the revocation certificate using the specified key. By default, \
the certificate being revoked is used. Using this option, it is \
possible to create a third-party revocation.",
help.replace("certificates", "key")).into()
}
}

View File

@ -75,6 +75,19 @@ impl ArgumentPrefix for SignerPrefix {
}
}
/// "--revoker", "--revoker-userid", "--revoker-file", etc.
pub type RevokerPrefix = ConcreteArgumentPrefix<typenum::U4>;
impl ArgumentPrefix for RevokerPrefix {
fn prefix() -> &'static str {
"revoker-"
}
fn name() -> &'static str {
"revoker"
}
}
/// Adds a `--file` argument.
pub type FileArg = typenum::U1;
@ -148,6 +161,10 @@ pub type OneValue = typenum::U1;
/// completely optional.
pub type OptionalValue = typenum::U2;
/// Combines OneValue and OptionalValue.
pub type OneOptionalValue
= <OneValue as BitOr<OptionalValue>>::Output;
/// Normally it is possible to designate multiple certificates. This
/// errors out if there is more than one value.
pub type FileRequiresOutput = typenum::U4;

View File

@ -1,7 +1,6 @@
use sequoia_openpgp as openpgp;
use openpgp::cert::CertRevocationBuilder;
use openpgp::packet::signature::subpacket::NotationData;
use openpgp::parse::Parse;
use openpgp::types::ReasonForRevocation;
use openpgp::Cert;
use openpgp::Packet;
@ -9,7 +8,6 @@ use openpgp::Result;
use crate::Sq;
use crate::cli::key::revoke::Command;
use crate::cli::types::FileOrStdout;
use crate::common::get_secret_signer;
use crate::common::NULL_POLICY;
use crate::common::RevocationOutput;
@ -87,32 +85,18 @@ impl RevocationOutput for CertificateRevocation
/// Revoke a certificate
pub fn certificate_revoke(
sq: Sq,
mut command: Command,
command: Command,
) -> Result<()> {
let cert = if let Some(file) = command.cert_file {
if command.output.is_none() {
// None means to write to the cert store. When reading
// from a file, we want to write to stdout by default.
command.output = Some(FileOrStdout::new(None));
}
let cert =
sq.resolve_cert_with_policy(&command.cert,
sequoia_wot::FULLY_TRUSTED,
NULL_POLICY,
sq.time)?.0;
let br = file.open()?;
Cert::from_buffered_reader(br)?
} else if let Some(kh) = command.cert {
sq.lookup_one_with_policy(&kh, None, true,
NULL_POLICY, sq.time)?
} else {
panic!("clap enforces --cert or --cert-file");
};
let revoker = if let Some(file) = command.revoker_file {
let br = file.open()?;
Some(Cert::from_buffered_reader(br)?)
} else if let Some(kh) = command.revoker {
Some(sq.lookup_one_with_policy(&kh, None, true,
NULL_POLICY, sq.time)?)
} else {
let revoker = if command.revoker.is_empty() {
None
} else {
Some(sq.resolve_cert(&command.revoker, sequoia_wot::FULLY_TRUSTED)?.0)
};
let notations = parse_notations(command.notation)?;

View File

@ -1701,6 +1701,22 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
-> Result<(Vec<Cert>, Vec<anyhow::Error>)>
where
Prefix: ArgumentPrefix
{
self.resolve_certs_with_policy(designators, trust_amount,
self.policy, self.time)
}
/// Like [`Sq::resolve_certs`] but with explicit policy.
pub fn resolve_certs_with_policy<Arguments, Prefix, Options, Doc>(
&self,
designators: &CertDesignators<Arguments, Prefix, Options, Doc>,
trust_amount: usize,
policy: &dyn Policy,
time: SystemTime,
)
-> Result<(Vec<Cert>, Vec<anyhow::Error>)>
where
Prefix: ArgumentPrefix,
{
tracer!(TRACE, "Sq::resolve_certs");
t!("{:?}", designators);
@ -1850,7 +1866,7 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
for ka in cert.keys().subkeys() {
if ka.key_handle().aliases(kh) {
match ka.with_policy(self.policy, None) {
match ka.with_policy(policy, time) {
Ok(_ka) => {
ret(designator,
Ok(Arc::new(cert.into())),
@ -2134,6 +2150,22 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
-> Result<(Cert, FileStdinOrKeyHandle)>
where
Prefix: ArgumentPrefix
{
self.resolve_cert_with_policy(designators, trust_amount,
self.policy, self.time)
}
/// Like [`Sq::resolve_cert`], but with explicit policy.
pub fn resolve_cert_with_policy<Arguments, Prefix, Options, Doc>(
&self,
designators: &CertDesignators<Arguments, Prefix, Options, Doc>,
trust_amount: usize,
policy: &dyn Policy,
time: SystemTime,
)
-> Result<(Cert, FileStdinOrKeyHandle)>
where
Prefix: ArgumentPrefix,
{
// Assuming this is only called with OneValue, then the
// following are not required.
@ -2147,7 +2179,9 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
Prefix::name());
}
let (certs, errors) = self.resolve_certs(designators, trust_amount)?;
let (certs, errors) =
self.resolve_certs_with_policy(designators, trust_amount,
policy, time)?;
if certs.len() > 1 {
wprintln!("{} is ambiguous. It resolves to multiple certificates.",
designators.designators[0].argument::<Prefix>());

View File

@ -594,7 +594,8 @@ impl Sq {
match &cert_handle {
FileOrKeyHandle::FileOrStdin(path) => {
cmd.arg("--cert-file").arg(path);
cmd.arg("--file").arg(path);
assert!(output_file.is_some());
}
FileOrKeyHandle::KeyHandle((_kh, s)) => {
cmd.arg("--cert").arg(&s);