From 6cf2acc8938b8e23d4fd605ff6d59bc6b19aaa73 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Wed, 4 Dec 2024 15:32:16 +0100 Subject: [PATCH] Add `sq key generate --profile` in preparation for RFC9580. - See #463. --- src/cli/key/generate.rs | 25 +++++++++++++++++ src/cli/types.rs | 2 ++ src/cli/types/profile.rs | 9 ++++++ src/commands/key.rs | 1 + src/commands/key/generate.rs | 4 +++ src/config.rs | 54 ++++++++++++++++++++++++++++++++++++ 6 files changed, 95 insertions(+) create mode 100644 src/cli/types/profile.rs diff --git a/src/cli/key/generate.rs b/src/cli/key/generate.rs index 27e93ff2..27a9954b 100644 --- a/src/cli/key/generate.rs +++ b/src/cli/key/generate.rs @@ -13,6 +13,7 @@ use crate::cli::types::EncryptPurpose; use crate::cli::types::Expiration; use crate::cli::types::ExpirationArg; use crate::cli::types::FileOrStdout; +use crate::cli::types::Profile; use crate::cli::examples::*; use crate::cli::key::CipherSuite; @@ -146,6 +147,30 @@ Canonical user IDs are of the form `Name (Comment) \ #[clap(skip)] pub cipher_suite_source: Option, + #[clap( + long = "profile", + value_name = "PROFILE", + default_value_t = Default::default(), + help = "Select the OpenPGP standard for the key", + long_help = config::augment_help( + "key.generate.profile", + "Select the OpenPGP standard for the key + +As OpenPGP evolves, new versions will become available. This option \ +selects the version of OpenPGP to use for the newly generated key. + +Currently, sq supports only one version: RFC4880. Consequently, this \ +is the default. However, there is already a newer version of the \ +standard: RFC9580. And, the default will change in a future version of \ +sq."), + value_enum, + )] + pub profile: Profile, + + /// Workaround for https://github.com/clap-rs/clap/issues/3846 + #[clap(skip)] + pub profile_source: Option, + #[clap( long = "new-password-file", value_name = "PASSWORD_FILE", diff --git a/src/cli/types.rs b/src/cli/types.rs index 23b4b6dc..8dc6aaa8 100644 --- a/src/cli/types.rs +++ b/src/cli/types.rs @@ -39,6 +39,8 @@ pub use userid_designator::UserIDDesignators; pub mod expiration; pub use expiration::Expiration; pub use expiration::ExpirationArg; +pub mod profile; +pub use profile::Profile; pub mod special_names; pub use special_names::SpecialName; pub mod time; diff --git a/src/cli/types/profile.rs b/src/cli/types/profile.rs new file mode 100644 index 00000000..8c508853 --- /dev/null +++ b/src/cli/types/profile.rs @@ -0,0 +1,9 @@ +//! OpenPGP profiles. + +/// Profiles select versions of the OpenPGP standard. +#[derive(clap::ValueEnum, Default, Debug, Clone)] +pub enum Profile { + /// RFC4880, published in 2007, defines "v4" OpenPGP. + #[default] + RFC4880, +} diff --git a/src/commands/key.rs b/src/commands/key.rs index dd3d5778..b729a8ab 100644 --- a/src/commands/key.rs +++ b/src/commands/key.rs @@ -31,6 +31,7 @@ pub fn dispatch(sq: Sq, command: cli::key::Command, matches: &ArgMatches) List(c) => list(sq, c)?, Generate(mut c) => { c.cipher_suite_source = matches.value_source("cipher_suite"); + c.profile_source = matches.value_source("profile"); generate(sq, c)? }, Import(c) => import(sq, c)?, diff --git a/src/commands/key/generate.rs b/src/commands/key/generate.rs index 3d1b4cad..5674f00f 100644 --- a/src/commands/key/generate.rs +++ b/src/commands/key/generate.rs @@ -96,6 +96,10 @@ pub fn generate( sq.config.cipher_suite(&command.cipher_suite, command.cipher_suite_source)); + // Profile. XXX: Currently, this is not actionable. + let _profile = sq.config.key_generate_profile( + &command.profile, command.profile_source); + // Primary key capabilities. builder = builder.set_primary_key_flags( key_flags_template.clone().set_certification()); diff --git a/src/config.rs b/src/config.rs index 3d993c30..dc16ade3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -63,8 +63,13 @@ pub struct Config { policy_path: Option, policy_inline: Option>, + + /// The default cipher suite for newly generated keys. cipher_suite: Option, + /// The default profile for newly generated keys. + key_generate_profile: Option, + /// The set of keyservers to use. key_servers: Option>, @@ -94,6 +99,7 @@ impl Default for Config { policy_path: None, policy_inline: None, cipher_suite: None, + key_generate_profile: None, key_servers: None, network_search_iterations: 3, network_search_wkd: true, @@ -253,6 +259,25 @@ impl Config { } } + /// Returns the profile for generating new keys. + /// + /// Handles the precedence of the various sources: + /// + /// - If the flag is given, use the given value. + /// - If the command line flag is not given, then + /// - use the value from the configuration file (if any), + /// - or use the default value. + pub fn key_generate_profile(&self, cli: &cli::types::Profile, + source: Option) + -> cli::types::Profile + { + match source.expect("set by the cli parser") { + ValueSource::DefaultValue => + self.key_generate_profile.as_ref().unwrap_or(cli), + _ => cli, + }.clone() + } + /// Returns the key servers to query or publish. /// /// Handles the precedence of the various sources: @@ -329,6 +354,7 @@ impl ConfigFile { [key.generate] #cipher-suite = +#profile = [network] #keyservers = @@ -357,6 +383,7 @@ impl ConfigFile { "", "", "", + "", "", "", "", @@ -391,6 +418,8 @@ impl ConfigFile { &cli::THIRD_PARTY_CERTIFICATION_VALIDITY_IN_YEARS.to_string(), &format!("{:?}", cli::key::CipherSuite::default(). to_possible_value().unwrap().get_name()), + &format!("{:?}", cli::types::Profile::default() + .to_possible_value().unwrap().get_name()), &format!("{:?}", cli::network::keyserver::DEFAULT_KEYSERVERS), &format!("{:?}", { sequoia_keystore::sequoia_ipc::Context::configure().build() @@ -1016,6 +1045,7 @@ fn apply_key(config: &mut Option<&mut Config>, cli: &mut Option<&mut Augmentatio /// Schema for the `key.generate` section. const KEY_GENERATE_SCHEMA: Schema = &[ ("cipher-suite", apply_key_generate_cipher_suite), + ("profile", apply_key_generate_profile), ]; /// Validates the `key.generate` section. @@ -1054,6 +1084,30 @@ fn apply_key_generate_cipher_suite(config: &mut Option<&mut Config>, Ok(()) } +/// Validates the `key.generate.profile` value. +fn apply_key_generate_profile(config: &mut Option<&mut Config>, + cli: &mut Option<&mut Augmentations>, + path: &str, item: &Item) + -> Result<()> +{ + let s = item.as_str() + .ok_or_else(|| Error::bad_item_type(path, item, "string"))?; + let v = cli::types::Profile::from_str(s, false) + .map_err(|e| anyhow::anyhow!("{}", e))?; + + if let Some(config) = config { + config.key_generate_profile = Some(v.clone()); + } + + if let Some(cli) = cli { + cli.insert( + "key.generate.profile", + v.to_possible_value().expect("just validated").get_name().into()); + } + + Ok(()) +} + /// Schema for the `network` section. const NETWORK_SCHEMA: Schema = &[ ("keyservers", apply_network_keyservers),