diff --git a/NEWS b/NEWS index 606be31d..6133da6a 100644 --- a/NEWS +++ b/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 diff --git a/src/cli/config.rs b/src/cli/config.rs index 95edc681..c17a6eb0 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -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) diff --git a/src/cli/encrypt.rs b/src/cli/encrypt.rs index 29552800..6c2ffb21 100644 --- a/src/cli/encrypt.rs +++ b/src/cli/encrypt.rs @@ -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, #[clap( diff --git a/src/cli/types/cert_designator.rs b/src/cli/types/cert_designator.rs index ae47ff73..dc609032 100644 --- a/src/cli/types/cert_designator.rs +++ b/src/cli/types/cert_designator.rs @@ -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>::Output as std::ops::BitOr>::Output; +/// Enables --cert, --userid, --email, --file, --self, --with-password +/// and --with-password-file (i.e., not --domain, --grep, or +/// --special). +pub type CertUserIDEmailFileSelfWithPasswordArgs = + >::Output; + /// Enables --cert, and --file (i.e., not --userid, --email, --domain, /// --grep, --with-password, --with-password-file, or --special). pub type CertFileArgs = >::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] diff --git a/src/config.rs b/src/config.rs index bc0ed35a..fa47bc04 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, policy_path: Option, policy_inline: Option>, cipher_suite: Option, @@ -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 +[encrypt] +#for-self = [\"fingerprint of your key\"] + [key.generate] #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::()?); + } + + 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), diff --git a/src/sq.rs b/src/sq.rs index 1ddc3407..4e6e7c3b 100644 --- a/src/sq.rs +++ b/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); + } + }, } }