Improve the error message shown when sq encrypt gets no recipients.
- If the user does not pass any recipients, or passwords to `sq encrypt`, a simple, custom error message is shown instead of the one generated by clap's error message machinery. - Add `--with-password` and `--with-password-file` to the cert designator framework, and switch `sq encrypt` to select them. - Fixes #405.
This commit is contained in:
parent
89cb9d6743
commit
4b4276c75d
@ -2,7 +2,6 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::ArgAction::Count;
|
||||
use clap::{ValueEnum, Parser};
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
@ -15,8 +14,7 @@ use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
use crate::cli::types::CertDesignators;
|
||||
use crate::cli::types::cert_designator::CertUserIDEmailFileArgs;
|
||||
use crate::cli::types::cert_designator::OptionalValue;
|
||||
use crate::cli::types::cert_designator::CertUserIDEmailFileWithPasswordArgs;
|
||||
use crate::cli::types::cert_designator::RecipientPrefix;
|
||||
|
||||
use crate::cli::examples;
|
||||
@ -99,9 +97,8 @@ pub struct Command {
|
||||
pub binary: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub recipients: CertDesignators<CertUserIDEmailFileArgs,
|
||||
RecipientPrefix,
|
||||
OptionalValue>,
|
||||
pub recipients: CertDesignators<CertUserIDEmailFileWithPasswordArgs,
|
||||
RecipientPrefix>,
|
||||
|
||||
#[clap(
|
||||
help = "Set the filename of the encrypted file as metadata",
|
||||
@ -153,40 +150,6 @@ pub struct Command {
|
||||
)]
|
||||
pub signer_key: Vec<KeyHandle>,
|
||||
|
||||
#[clap(
|
||||
long = "with-password",
|
||||
help = "Prompt to add a password to encrypt with",
|
||||
long_help =
|
||||
"Prompt to add a password to encrypt with. \
|
||||
When using this option, the user is asked to provide a password, \
|
||||
which is used to encrypt the message. \
|
||||
This option can be provided more than once to provide more than \
|
||||
one password. \
|
||||
The encrypted data can afterwards be decrypted with either one of \
|
||||
the recipient's keys, or one of the provided passwords.",
|
||||
action = Count,
|
||||
)]
|
||||
pub symmetric: u8,
|
||||
|
||||
#[clap(
|
||||
long = "with-password-file",
|
||||
value_name = "PASSWORD_FILE",
|
||||
help = "\
|
||||
File containing password to encrypt the secret key material",
|
||||
long_help = "\
|
||||
File containing password to encrypt the secret key material.
|
||||
|
||||
Note that the entire key file will be used as the password including \
|
||||
any surrounding whitespace like a trailing newline.
|
||||
|
||||
This option can be provided more than once to provide more than \
|
||||
one password. \
|
||||
The encrypted data can afterwards be decrypted with either one of \
|
||||
the recipient's keys, or one of the provided passwords.",
|
||||
)]
|
||||
pub symmetric_password_file: Vec<PathBuf>,
|
||||
|
||||
|
||||
#[clap(
|
||||
long = "encrypt-for",
|
||||
value_name = "PURPOSE",
|
||||
|
@ -119,8 +119,13 @@ pub type DomainArg = typenum::U32;
|
||||
/// Adds a `--grep` argument.
|
||||
pub type GrepArg = typenum::U64;
|
||||
|
||||
/// Adds `--with-password`, and `--with-password-file` arguments.
|
||||
///
|
||||
/// This is only used for `sq encrypt`.
|
||||
pub type WithPasswordArgs = typenum::U128;
|
||||
|
||||
/// Enables --file, --cert, --userid, --email, --domain, and --grep
|
||||
/// (i.e., all of them).
|
||||
/// (i.e., not --with-password, or --with-password-file).
|
||||
#[allow(dead_code)]
|
||||
pub type FileCertUserIDEmailDomainGrepArgs
|
||||
= <<<<<FileArg
|
||||
@ -131,7 +136,7 @@ pub type FileCertUserIDEmailDomainGrepArgs
|
||||
as std::ops::BitOr<GrepArg>>::Output;
|
||||
|
||||
/// Enables --file, --cert, --userid, --email, and --domain, (i.e.,
|
||||
/// not --grep).
|
||||
/// not --grep, --with-password, or --with-password-file).
|
||||
#[allow(dead_code)]
|
||||
pub type FileCertUserIDEmailDomainArgs
|
||||
= <<<<FileArg
|
||||
@ -141,7 +146,7 @@ pub type FileCertUserIDEmailDomainArgs
|
||||
as std::ops::BitOr<DomainArg>>::Output;
|
||||
|
||||
/// Enables --cert, --userid, --email, --domain, and --grep (i.e., not
|
||||
/// --file).
|
||||
/// --file, --with-password, or --with-password-file).
|
||||
pub type CertUserIDEmailDomainGrepArgs
|
||||
= <<<<CertArg as std::ops::BitOr<UserIDArg>>::Output
|
||||
as std::ops::BitOr<EmailArg>>::Output
|
||||
@ -149,14 +154,22 @@ pub type CertUserIDEmailDomainGrepArgs
|
||||
as std::ops::BitOr<GrepArg>>::Output;
|
||||
|
||||
/// Enables --cert, --userid, --email, and --file (i.e., not --domain,
|
||||
/// or --grep).
|
||||
/// --grep, --with-password, or --with-password-file).
|
||||
pub type CertUserIDEmailFileArgs
|
||||
= <<<CertArg as std::ops::BitOr<UserIDArg>>::Output
|
||||
as std::ops::BitOr<EmailArg>>::Output
|
||||
as std::ops::BitOr<FileArg>>::Output;
|
||||
|
||||
/// Enables --cert, --userid, --email, --file, --with-password and
|
||||
/// --with-password-file (i.e., not --domain, or --grep).
|
||||
pub type CertUserIDEmailFileWithPasswordArgs
|
||||
= <<<<CertArg as std::ops::BitOr<UserIDArg>>::Output
|
||||
as std::ops::BitOr<EmailArg>>::Output
|
||||
as std::ops::BitOr<FileArg>>::Output
|
||||
as std::ops::BitOr<WithPasswordArgs>>::Output;
|
||||
|
||||
/// Enables --cert, and --file (i.e., not --userid, --email, --domain,
|
||||
/// or --grep).
|
||||
/// --grep, --with-password, or --with-password-file).
|
||||
pub type CertFileArgs = <CertArg as std::ops::BitOr<FileArg>>::Output;
|
||||
|
||||
|
||||
@ -385,6 +398,11 @@ pub struct CertDesignators<Arguments, Prefix=NoPrefix, Options=NoOptions,
|
||||
/// The set of certificate designators.
|
||||
pub designators: Vec<CertDesignator>,
|
||||
|
||||
/// --with-password
|
||||
with_passwords: usize,
|
||||
/// --with-password-file
|
||||
with_password_files: Vec<PathBuf>,
|
||||
|
||||
arguments: std::marker::PhantomData<(Arguments, Prefix, Options,
|
||||
Doc)>,
|
||||
}
|
||||
@ -414,6 +432,16 @@ impl<Arguments, Prefix, Options, Doc> CertDesignators<Arguments, Prefix, Options
|
||||
pub fn iter(&self) -> impl Iterator<Item=&CertDesignator> {
|
||||
self.designators.iter()
|
||||
}
|
||||
|
||||
/// Returns the number of times `--with-password` was given.
|
||||
pub fn with_passwords(&self) -> usize {
|
||||
self.with_passwords
|
||||
}
|
||||
|
||||
/// Returns the `--with-password-file` arguments.
|
||||
pub fn with_password_files(&self) -> &[PathBuf] {
|
||||
&self.with_password_files[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<Arguments, Prefix, Options, Doc> clap::Args
|
||||
@ -433,6 +461,7 @@ where
|
||||
let email_arg = (arguments & EmailArg::to_usize()) > 0;
|
||||
let domain_arg = (arguments & DomainArg::to_usize()) > 0;
|
||||
let grep_arg = (arguments & GrepArg::to_usize()) > 0;
|
||||
let with_password_args = (arguments & WithPasswordArgs::to_usize()) > 0;
|
||||
|
||||
let options = Options::to_usize();
|
||||
let one_value = (options & OneValue::to_usize()) > 0;
|
||||
@ -612,6 +641,50 @@ where
|
||||
arg_group = arg_group.arg(full_name);
|
||||
}
|
||||
|
||||
if with_password_args {
|
||||
let full_name = "with-password";
|
||||
let arg = clap::Arg::new(full_name)
|
||||
.long(full_name)
|
||||
.action(clap::ArgAction::Count)
|
||||
.help(Doc::help(
|
||||
"with-password-file",
|
||||
"Prompt to add a password to encrypt with"))
|
||||
.long_help("\
|
||||
Prompt to add a password to encrypt with. \
|
||||
When using this option, the user is asked to provide a password, \
|
||||
which is used to encrypt the message. \
|
||||
This option can be provided more than once to provide more than \
|
||||
one password. \
|
||||
The encrypted data can afterwards be decrypted with either one of \
|
||||
the recipient's keys, or one of the provided passwords.");
|
||||
|
||||
cmd = cmd.arg(arg);
|
||||
arg_group = arg_group.arg(full_name);
|
||||
|
||||
let full_name = "with-password-file";
|
||||
let arg = clap::Arg::new(full_name)
|
||||
.long(full_name)
|
||||
.value_name("PATH")
|
||||
.value_parser(clap::value_parser!(PathBuf))
|
||||
.action(action.clone())
|
||||
.help(Doc::help(
|
||||
"with-password-file",
|
||||
"File containing password to encrypt the message"))
|
||||
.long_help("\
|
||||
File containing password to encrypt the message.
|
||||
|
||||
Note that the entire key file will be used as the password including \
|
||||
any surrounding whitespace like a trailing newline.
|
||||
|
||||
This option can be provided more than once to provide more than \
|
||||
one password. \
|
||||
The encrypted data can afterwards be decrypted with either one of \
|
||||
the recipient's keys, or one of the provided passwords.");
|
||||
|
||||
cmd = cmd.arg(arg);
|
||||
arg_group = arg_group.arg(full_name);
|
||||
}
|
||||
|
||||
cmd = cmd.group(arg_group);
|
||||
|
||||
cmd
|
||||
@ -643,6 +716,7 @@ where
|
||||
let email_arg = (arguments & EmailArg::to_usize()) > 0;
|
||||
let domain_arg = (arguments & DomainArg::to_usize()) > 0;
|
||||
let grep_arg = (arguments & GrepArg::to_usize()) > 0;
|
||||
let with_password_args = (arguments & WithPasswordArgs::to_usize()) > 0;
|
||||
|
||||
let mut designators = Vec::new();
|
||||
|
||||
@ -712,6 +786,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if with_password_args {
|
||||
self.with_passwords = matches.get_count("with-password") as usize;
|
||||
|
||||
if let Some(Some(paths))
|
||||
= matches.try_get_many::<PathBuf>("with-password-file").ok()
|
||||
{
|
||||
self.with_password_files.extend(paths.cloned());
|
||||
}
|
||||
}
|
||||
|
||||
self.designators = designators;
|
||||
Ok(())
|
||||
}
|
||||
@ -721,6 +805,8 @@ where
|
||||
{
|
||||
let mut designators = Self {
|
||||
designators: Vec::new(),
|
||||
with_passwords: 0,
|
||||
with_password_files: Vec::new(),
|
||||
arguments: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
@ -744,7 +830,8 @@ mod test {
|
||||
macro_rules! check {
|
||||
($t:ty,
|
||||
$cert:expr, $userid:expr, $email:expr,
|
||||
$domain:expr, $grep:expr, $file:expr) =>
|
||||
$domain:expr, $grep:expr, $file:expr,
|
||||
$with_password:expr) =>
|
||||
{{
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(name = "prog")]
|
||||
@ -886,21 +973,53 @@ mod test {
|
||||
} else {
|
||||
assert!(m.is_err());
|
||||
}
|
||||
|
||||
// Check if --with-password is recognized.
|
||||
let m = command.clone().try_get_matches_from(vec![
|
||||
"prog",
|
||||
"--with-password",
|
||||
"--with-password",
|
||||
]);
|
||||
if $with_password {
|
||||
let m = m.expect("valid arguments");
|
||||
let c = CLI::from_arg_matches(&m).expect("ok");
|
||||
assert_eq!(c.certs.with_passwords(), 2);
|
||||
} else {
|
||||
assert!(m.is_err());
|
||||
}
|
||||
|
||||
// Check if --with-password-file is recognized.
|
||||
let m = command.clone().try_get_matches_from(vec![
|
||||
"prog",
|
||||
"--with-password-file", "a",
|
||||
"--with-password-file", "b",
|
||||
]);
|
||||
if $with_password {
|
||||
let m = m.expect("valid arguments");
|
||||
let c = CLI::from_arg_matches(&m).expect("ok");
|
||||
assert_eq!(c.certs.with_password_files().len(), 2);
|
||||
} else {
|
||||
assert!(m.is_err());
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
check!(CertUserIDEmailDomainGrepArgs,
|
||||
true, true, true, true, true, false);
|
||||
true, true, true, true, true, false, false);
|
||||
check!(CertUserIDEmailFileArgs,
|
||||
true, true, true, false, false, true);
|
||||
true, true, true, false, false, true, false);
|
||||
check!(CertUserIDEmailFileWithPasswordArgs,
|
||||
true, true, true, false, false, true, true);
|
||||
// No Args.
|
||||
check!(typenum::U0,false, false, false, false, false, false);
|
||||
check!(CertArg, true, false, false, false, false, false);
|
||||
check!(UserIDArg, false, true, false, false, false, false);
|
||||
check!(EmailArg, false, false, true, false, false, false);
|
||||
check!(DomainArg, false, false, false, true, false, false);
|
||||
check!(GrepArg, false, false, false, false, true, false);
|
||||
check!(FileArg, false, false, false, false, false, true);
|
||||
check!(typenum::U0,false, false, false, false, false, false, false);
|
||||
check!(CertArg, true, false, false, false, false, false, false);
|
||||
check!(UserIDArg, false, true, false, false, false, false, false);
|
||||
check!(EmailArg, false, false, true, false, false, false, false);
|
||||
check!(DomainArg, false, false, false, true, false, false, false);
|
||||
check!(GrepArg, false, false, false, false, true, false, false);
|
||||
check!(FileArg, false, false, false, false, false, true, false);
|
||||
check!(WithPasswordArgs,
|
||||
false, false, false, false, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -67,8 +67,8 @@ pub fn dispatch(sq: Sq, command: cli::encrypt::Command) -> Result<()> {
|
||||
sq.policy,
|
||||
command.input,
|
||||
output,
|
||||
command.symmetric as usize,
|
||||
command.symmetric_password_file,
|
||||
command.recipients.with_passwords(),
|
||||
command.recipients.with_password_files(),
|
||||
&recipients,
|
||||
additional_secrets,
|
||||
signer_keys,
|
||||
@ -89,7 +89,7 @@ pub fn encrypt<'a, 'b: 'a>(
|
||||
input: FileOrStdin,
|
||||
message: Message<'a>,
|
||||
npasswords: usize,
|
||||
password_files: Vec<PathBuf>,
|
||||
password_files: &[PathBuf],
|
||||
recipients: &'b [openpgp::Cert],
|
||||
signers: Vec<openpgp::Cert>,
|
||||
signer_keys: &[KeyHandle],
|
||||
|
Loading…
Reference in New Issue
Block a user