Implement sq encrypt --for-self
.
- This adds a mechanism to add a list of certificates presumably owned by the user to the recipients using the `--for-self` flag. This makes sure the encrypted message can be decrypted again. - Fixes #461.
This commit is contained in:
parent
abafa552f0
commit
b88367ce36
2
NEWS
2
NEWS
@ -4,6 +4,8 @@
|
||||
|
||||
* Changes in 0.41.0
|
||||
** New functionality
|
||||
- `sq encrypt --for-self` now adds the certs configured under
|
||||
`encrypt.for-self` to the list of recipients.
|
||||
** Notable changes
|
||||
- `sq pki link add`, `sq pki link authorize`, and `sq pki link
|
||||
retract` gain a new parameter, `--cert-special`, which allows
|
||||
|
@ -63,13 +63,18 @@ pub type Augmentations = BTreeMap<&'static str, String>;
|
||||
|
||||
/// Includes values from the config file in help messages.
|
||||
pub fn augment_help(key: &'static str, text: &str) -> String {
|
||||
if let Some(a) = AUGMENTATIONS.get().and_then(|a| a.get(key)) {
|
||||
if let Some(a) = get_augmentation(key) {
|
||||
format!("{}\n\n[config: {}] (overrides default)", text, a)
|
||||
} else {
|
||||
text.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of an augmentation, if any.
|
||||
pub fn get_augmentation(key: &'static str) -> Option<&str> {
|
||||
AUGMENTATIONS.get().and_then(|a| a.get(key).map(|v| v.as_str()))
|
||||
}
|
||||
|
||||
/// Includes values from the config file in help messages.
|
||||
pub fn set_augmentations(augmentations: Augmentations) {
|
||||
AUGMENTATIONS.set(augmentations)
|
||||
|
@ -13,6 +13,9 @@ use crate::cli::types::cert_designator::*;
|
||||
use crate::cli::examples;
|
||||
use examples::*;
|
||||
|
||||
/// Key for the help augmentation.
|
||||
pub const ENCRYPT_FOR_SELF: &str = "encrypt.for-self";
|
||||
|
||||
const ENCRYPT_EXAMPLES: Actions = Actions {
|
||||
actions: &[
|
||||
Action::Setup(Setup {
|
||||
@ -83,7 +86,7 @@ pub struct Command {
|
||||
pub binary: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub recipients: CertDesignators<CertUserIDEmailFileWithPasswordArgs,
|
||||
pub recipients: CertDesignators<CertUserIDEmailFileSelfWithPasswordArgs,
|
||||
RecipientPrefix>,
|
||||
|
||||
#[clap(
|
||||
|
@ -11,6 +11,8 @@ use sequoia_openpgp as openpgp;
|
||||
use openpgp::KeyHandle;
|
||||
use openpgp::packet::UserID;
|
||||
|
||||
use crate::cli::config;
|
||||
use crate::cli::encrypt::ENCRYPT_FOR_SELF;
|
||||
use crate::cli::types::SpecialName;
|
||||
|
||||
/// The prefix for the designators.
|
||||
@ -129,6 +131,9 @@ pub type WithPasswordArgs = typenum::U128;
|
||||
/// Adds a `--special` argument.
|
||||
pub type SpecialArg = typenum::U256;
|
||||
|
||||
/// Adds a `--self` argument.
|
||||
pub type SelfArg = typenum::U512;
|
||||
|
||||
/// Enables --file, --cert, --userid, --email, --domain, and --grep
|
||||
/// (i.e., not --with-password, --with-password-file, --special).
|
||||
#[allow(dead_code)]
|
||||
@ -179,6 +184,12 @@ pub type CertUserIDEmailFileWithPasswordArgs
|
||||
as std::ops::BitOr<FileArg>>::Output
|
||||
as std::ops::BitOr<WithPasswordArgs>>::Output;
|
||||
|
||||
/// Enables --cert, --userid, --email, --file, --self, --with-password
|
||||
/// and --with-password-file (i.e., not --domain, --grep, or
|
||||
/// --special).
|
||||
pub type CertUserIDEmailFileSelfWithPasswordArgs =
|
||||
<CertUserIDEmailFileWithPasswordArgs as std::ops::BitOr<SelfArg>>::Output;
|
||||
|
||||
/// Enables --cert, and --file (i.e., not --userid, --email, --domain,
|
||||
/// --grep, --with-password, --with-password-file, or --special).
|
||||
pub type CertFileArgs = <CertArg as std::ops::BitOr<FileArg>>::Output;
|
||||
@ -334,6 +345,15 @@ pub enum CertDesignator {
|
||||
///
|
||||
/// `--special`.
|
||||
Special(SpecialName),
|
||||
|
||||
/// Use the configured set of certificates presumably belonging to
|
||||
/// oneself.
|
||||
///
|
||||
/// This is used to add ones own certificates as encryption
|
||||
/// recipients.
|
||||
///
|
||||
/// `--self`.
|
||||
Self_,
|
||||
}
|
||||
|
||||
impl CertDesignator {
|
||||
@ -362,6 +382,7 @@ impl CertDesignator {
|
||||
Domain(_domain) => format!("--{}domain", prefix),
|
||||
Grep(_pattern) => format!("--{}grep", prefix),
|
||||
Special(_special) => format!("--{}special", prefix),
|
||||
Self_ => format!("--{}self", prefix),
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,6 +403,7 @@ impl CertDesignator {
|
||||
Domain(domain) => format!("{} {:?}", argument_name, domain),
|
||||
Grep(pattern) => format!("{} {:?}", argument_name, pattern),
|
||||
Special(special) => format!("{} {:?}", argument_name, special),
|
||||
Self_ => argument_name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -504,6 +526,7 @@ where
|
||||
let grep_arg = (arguments & GrepArg::to_usize()) > 0;
|
||||
let with_password_args = (arguments & WithPasswordArgs::to_usize()) > 0;
|
||||
let special_arg = (arguments & SpecialArg::to_usize()) > 0;
|
||||
let self_arg = (arguments & SelfArg::to_usize()) > 0;
|
||||
|
||||
let options = Options::to_usize();
|
||||
let one_value = (options & OneValue::to_usize()) > 0;
|
||||
@ -698,6 +721,35 @@ where
|
||||
arg_group = arg_group.arg(full_name);
|
||||
}
|
||||
|
||||
if self_arg {
|
||||
let full_name = full_name("self");
|
||||
let long_help = format!(
|
||||
"Encrypt the message for yourself
|
||||
|
||||
This adds the certificates listed in the configuration file under \
|
||||
`{}` to the list of recipients. \
|
||||
This can be used to make sure that you yourself can decrypt the message.
|
||||
|
||||
{}
|
||||
",
|
||||
ENCRYPT_FOR_SELF,
|
||||
if let Some(certs) = config::get_augmentation(ENCRYPT_FOR_SELF) {
|
||||
format!("The following certs will be added: {}.", certs)
|
||||
} else {
|
||||
"Currently, the list of certificates to be added is empty."
|
||||
.into()
|
||||
});
|
||||
cmd = cmd.arg(
|
||||
clap::Arg::new(&full_name)
|
||||
.long(&full_name)
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
.help(Doc::help(
|
||||
"self",
|
||||
"Encrypt the message for yourself"))
|
||||
.long_help(long_help));
|
||||
arg_group = arg_group.arg(full_name);
|
||||
}
|
||||
|
||||
if with_password_args {
|
||||
let full_name = "with-password";
|
||||
let arg = clap::Arg::new(full_name)
|
||||
@ -775,6 +827,7 @@ where
|
||||
let grep_arg = (arguments & GrepArg::to_usize()) > 0;
|
||||
let with_password_args = (arguments & WithPasswordArgs::to_usize()) > 0;
|
||||
let special_arg = (arguments & SpecialArg::to_usize()) > 0;
|
||||
let self_arg = (arguments & SelfArg::to_usize()) > 0;
|
||||
|
||||
let mut designators = Vec::new();
|
||||
|
||||
@ -863,6 +916,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if self_arg && matches.get_flag(&format!("{}self", prefix)) {
|
||||
designators.push(CertDesignator::Self_);
|
||||
}
|
||||
|
||||
// eprintln!("{:?}", designators);
|
||||
|
||||
self.designators = designators;
|
||||
@ -900,6 +957,7 @@ mod test {
|
||||
($t:ty,
|
||||
$cert:expr, $userid:expr, $email:expr,
|
||||
$domain:expr, $grep:expr, $file:expr,
|
||||
$self: expr,
|
||||
$special:expr,
|
||||
$with_password:expr) =>
|
||||
{{
|
||||
@ -1045,6 +1103,20 @@ mod test {
|
||||
}
|
||||
|
||||
|
||||
// Check if --self is recognized.
|
||||
let m = command.clone().try_get_matches_from(vec![
|
||||
"prog",
|
||||
"--self",
|
||||
]);
|
||||
if $self {
|
||||
let m = m.expect("valid arguments");
|
||||
let c = CLI::from_arg_matches(&m).expect("ok");
|
||||
assert_eq!(c.certs.designators.len(), 1);
|
||||
} else {
|
||||
assert!(m.is_err());
|
||||
}
|
||||
|
||||
|
||||
// Check if --special is recognized.
|
||||
let m = command.clone().try_get_matches_from(vec![
|
||||
"prog",
|
||||
@ -1091,22 +1163,25 @@ mod test {
|
||||
}
|
||||
|
||||
check!(CertUserIDEmailDomainGrepArgs,
|
||||
true, true, true, true, true, false, false, false);
|
||||
true, true, true, true, true, false, false, false, false);
|
||||
check!(CertUserIDEmailFileArgs,
|
||||
true, true, true, false, false, true, false, false);
|
||||
true, true, true, false, false, true, false, false, false);
|
||||
check!(CertUserIDEmailFileWithPasswordArgs,
|
||||
true, true, true, false, false, true, false, true);
|
||||
true, true, true, false, false, true, false, false, true);
|
||||
check!(CertUserIDEmailFileSelfWithPasswordArgs,
|
||||
true, true, true, false, false, true, true, false, true);
|
||||
// No Args.
|
||||
check!(typenum::U0,false, false, false, false, false, false, false, false);
|
||||
check!(CertArg, true, false, false, false, false, false, false, false);
|
||||
check!(UserIDArg, false, true, false, false, false, false, false, false);
|
||||
check!(EmailArg, false, false, true, false, false, false, false, false);
|
||||
check!(DomainArg, false, false, false, true, false, false, false, false);
|
||||
check!(GrepArg, false, false, false, false, true, false, false, false);
|
||||
check!(FileArg, false, false, false, false, false, true, false, false);
|
||||
check!(SpecialArg, false, false, false, false, false, false, true, false);
|
||||
check!(typenum::U0,false, false, false, false, false, false, false, false, false);
|
||||
check!(CertArg, true, false, false, false, false, false, false, false, false);
|
||||
check!(UserIDArg, false, true, false, false, false, false, false, false, false);
|
||||
check!(EmailArg, false, false, true, false, false, false, false, false, false);
|
||||
check!(DomainArg, false, false, false, true, false, false, false, false, false);
|
||||
check!(GrepArg, false, false, false, false, true, false, false, false, false);
|
||||
check!(FileArg, false, false, false, false, false, true, false, false, false);
|
||||
check!(SelfArg, false, false, false, false, false, false, true, false, false);
|
||||
check!(SpecialArg, false, false, false, false, false, false, false, true, false);
|
||||
check!(WithPasswordArgs,
|
||||
false, false, false, false, false, false, false, true);
|
||||
false, false, false, false, false, false, false, false, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -19,7 +19,10 @@ use toml_edit::{
|
||||
Value,
|
||||
};
|
||||
|
||||
use sequoia_openpgp::policy::StandardPolicy;
|
||||
use sequoia_openpgp::{
|
||||
Fingerprint,
|
||||
policy::StandardPolicy,
|
||||
};
|
||||
use sequoia_net::reqwest::Url;
|
||||
use sequoia_directories::{Component, Home};
|
||||
use sequoia_policy_config::ConfiguredStandardPolicy;
|
||||
@ -36,6 +39,7 @@ use crate::{
|
||||
/// It is available as `Sq::config`, with suitable accessors that
|
||||
/// handle the precedence of the various sources.
|
||||
pub struct Config {
|
||||
encrypt_for_self: Vec<Fingerprint>,
|
||||
policy_path: Option<PathBuf>,
|
||||
policy_inline: Option<Vec<u8>>,
|
||||
cipher_suite: Option<sequoia_openpgp::cert::CipherSuite>,
|
||||
@ -45,6 +49,7 @@ pub struct Config {
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
encrypt_for_self: vec![],
|
||||
policy_path: None,
|
||||
policy_inline: None,
|
||||
cipher_suite: None,
|
||||
@ -54,6 +59,12 @@ impl Default for Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Returns the certificates that should be added to the list of
|
||||
/// recipients if `encrypt --for-self` is given.
|
||||
pub fn encrypt_for_self(&self) -> &[Fingerprint] {
|
||||
&self.encrypt_for_self
|
||||
}
|
||||
|
||||
/// Returns the cryptographic policy.
|
||||
///
|
||||
/// We read in the default policy configuration, the configuration
|
||||
@ -137,6 +148,9 @@ impl ConfigFile {
|
||||
# Configuration template for sq <SQ-VERSION>
|
||||
<SQ-CONFIG-PATH-HINT>
|
||||
|
||||
[encrypt]
|
||||
#for-self = [\"fingerprint of your key\"]
|
||||
|
||||
[key.generate]
|
||||
#cipher-suite = <DEFAULT-CIPHER-SUITE>
|
||||
|
||||
@ -525,11 +539,59 @@ type Schema = &'static [(&'static str, Applicator)];
|
||||
|
||||
/// Schema for the toplevel.
|
||||
const TOP_LEVEL_SCHEMA: Schema = &[
|
||||
("encrypt", apply_encrypt),
|
||||
("key", apply_key),
|
||||
("network", apply_network),
|
||||
("policy", apply_policy),
|
||||
];
|
||||
|
||||
/// Schema for the `encrypt` section.
|
||||
const ENCRYPT_SCHEMA: Schema = &[
|
||||
("for-self", apply_encrypt_for_self),
|
||||
];
|
||||
|
||||
/// Validates the `encrypt` section.
|
||||
fn apply_encrypt(config: &mut Option<&mut Config>, cli: &mut Option<&mut Augmentations>,
|
||||
path: &str, item: &Item)
|
||||
-> Result<()>
|
||||
{
|
||||
let section = item.as_table_like()
|
||||
.ok_or_else(|| Error::bad_item_type(path, item, "table"))?;
|
||||
apply_schema(config, cli, Some(path), section.iter(), ENCRYPT_SCHEMA)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the `encrypt.for-self` value.
|
||||
fn apply_encrypt_for_self(config: &mut Option<&mut Config>,
|
||||
cli: &mut Option<&mut Augmentations>,
|
||||
path: &str, item: &Item)
|
||||
-> Result<()>
|
||||
{
|
||||
let list = item.as_array()
|
||||
.ok_or_else(|| Error::bad_item_type(path, item, "array"))?;
|
||||
|
||||
let mut strs = Vec::new();
|
||||
let mut values = Vec::new();
|
||||
for (i, server) in list.iter().enumerate() {
|
||||
let s = server.as_str()
|
||||
.ok_or_else(|| Error::bad_value_type(&format!("{}.{}", path, i),
|
||||
server, "string"))?;
|
||||
|
||||
strs.push(s);
|
||||
values.push(s.parse::<Fingerprint>()?);
|
||||
}
|
||||
|
||||
if let Some(cli) = cli {
|
||||
cli.insert("encrypt.for-self", strs.join(" "));
|
||||
}
|
||||
|
||||
if let Some(config) = config {
|
||||
config.encrypt_for_self = values;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Schema for the `key` section.
|
||||
const KEY_SCHEMA: Schema = &[
|
||||
("generate", apply_key_generate),
|
||||
|
18
src/sq.rs
18
src/sq.rs
@ -48,6 +48,7 @@ use wot::store::Store as _;
|
||||
use sequoia_keystore as keystore;
|
||||
use keystore::Protection;
|
||||
|
||||
use crate::cli;
|
||||
use crate::cli::types::CertDesignators;
|
||||
use crate::cli::types::FileStdinOrKeyHandle;
|
||||
use crate::cli::types::KeyDesignators;
|
||||
@ -2079,7 +2080,24 @@ impl<'store: 'rstore, 'rstore> Sq<'store, 'rstore> {
|
||||
name)
|
||||
}),
|
||||
true);
|
||||
},
|
||||
|
||||
cert_designator::CertDesignator::Self_ => {
|
||||
if self.config.encrypt_for_self().is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"`--for-self` is given but the list of \
|
||||
certificates in `{}` is empty",
|
||||
cli::encrypt::ENCRYPT_FOR_SELF));
|
||||
}
|
||||
|
||||
for fp in self.config.encrypt_for_self() {
|
||||
let cert = self.resolve_cert(
|
||||
&openpgp::KeyHandle::from(fp.clone()).into(), 0)?.0;
|
||||
ret(designator,
|
||||
Ok(Arc::new(cert.into())),
|
||||
true);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user