diff --git a/src/cli/key/subkey.rs b/src/cli/key/subkey.rs index a0ebd8dd..826ced39 100644 --- a/src/cli/key/subkey.rs +++ b/src/cli/key/subkey.rs @@ -12,7 +12,6 @@ use crate::cli::types::FileOrStdout; use crate::cli::types::Time; use crate::cli::key::{ - CipherSuite, EncryptPurpose, KeyReasonForRevocation, }; @@ -26,6 +25,8 @@ use crate::cli::types::CertDesignators; use crate::cli::types::KeyDesignators; use crate::cli::types::cert_designator; +pub mod add; + #[derive(Debug, Subcommand)] #[clap( name = "subkey", @@ -40,7 +41,7 @@ and revoke them.", )] #[non_exhaustive] pub enum Command { - Add(AddCommand), + Add(add::Command), Export(ExportCommand), Delete(DeleteCommand), Password(PasswordCommand), @@ -49,176 +50,6 @@ pub enum Command { Bind(BindCommand), } -const SUBKEY_ADD_EXAMPLES: Actions = Actions { - actions: &[ - Action::Setup(Setup { - command: &[ - "sq", "key", "import", - "alice-secret.pgp", - ], - }), - Action::Example(Example { - comment: "\ -Add a new signing-capable subkey to Alice's key.", - command: &[ - "sq", "key", "subkey", "add", - "--without-password", - "--can-sign", - "--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0", - ], - }), - ] -}; -test_examples!(sq_key_subkey_add, SUBKEY_ADD_EXAMPLES); - -#[derive(Debug, Args)] -#[clap( - about = "Add a new subkey to a certificate", - long_about = "\ -Add a new subkey to a certificate. - -A subkey has one or more capabilities. - -`--can-sign` sets the signing capability, and means that the key may \ -be used for signing. `--can-authenticate` sets the authentication \ -capability, and means that the key may be used for authentication \ -(e.g., as an SSH key). `--can-certify` sets the certificate \ -capability, and means that the key may be used to make third-party \ -certifications. These capabilities may be combined. - -`--can-encrypt=storage` sets the storage encryption capability, and \ -means that the key may be used for storage \ -encryption. `--can-encrypt=transport` sets the transport encryption \ -capability, and means that the key may be used for transport \ -encryption. `--can-encrypt=universal` sets both the storage and the \ -transport encryption capability, and means that the key may be used \ -for both storage and transport encryption. The encryption \ -capabilities must not be combined with the signing or authentication \ -capability. - -Normally, `sq` prompts the user for a password to use to encrypt the \ -secret key material. The password for the new subkey may be different \ -from the other keys. When using `--without-password`, `sq` doesn't \ -prompt for a password, and doesn't password-protect the subkey. - -By default a new subkey doesn't expire on its own. However, its \ -validity period is limited by that of the certificate. Using the \ -`--expiration` argument allows setting a different expiration time. - -`sq key subkey add` respects the reference time set by the top-level \ -`--time` argument. It sets the creation time of the subkey to the specified \ -time. -", - after_help = SUBKEY_ADD_EXAMPLES, -)] -#[clap(group(ArgGroup::new("authentication-group").args(&["can_authenticate", "can_encrypt"])))] -#[clap(group(ArgGroup::new("sign-group").args(&["can_sign", "can_encrypt"])))] -#[clap(group(ArgGroup::new("required-group").args(&["can_authenticate", "can_sign", "can_encrypt"]).required(true)))] -pub struct AddCommand { - #[command(flatten)] - pub cert: CertDesignators< - cert_designator::CertUserIDEmailFileArgs, - cert_designator::NoPrefix, - cert_designator::OneValueAndFileRequiresOutput, - SubkeyAddDoc>, - - #[clap( - long, - value_name = "CIPHER-SUITE", - default_value_t = CipherSuite::Cv25519, - help = "Select the cryptographic algorithms for the subkey", - value_enum, - )] - pub cipher_suite: CipherSuite, - - #[command(flatten)] - pub expiration: ExpirationArg, - - #[clap( - long, - help = "Add a signing-capable subkey", - )] - pub can_sign: bool, - #[clap( - long, - help = "Add an authentication-capable subkey", - )] - pub can_authenticate: bool, - #[clap( - long, - value_name = "PURPOSE", - help = "Add an encryption-capable subkey [default: universal]", - long_help = "\ -Add an encryption-capable subkey. - -Encryption-capable subkeys can be marked as suitable for transport \ -encryption, storage encryption, or both, i.e., universal. [default: \ -universal]", - value_enum, - )] - pub can_encrypt: Option, - - #[clap( - long = "new-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.", - conflicts_with = "without_password", - )] - pub new_password_file: Option, - - #[clap( - long, - help = "Don't protect the subkey's secret key material with a password", - )] - pub without_password: bool, - - #[clap( - long, - value_name = FileOrStdout::VALUE_NAME, - help = "Write to the specified FILE", - long_help = "\ -Write to the specified FILE. - -If not specified, and the certificate was read from the certificate \ -store, imports the modified certificate into the key store. If not \ -specified, and the certificate was read from a file, writes the \ -modified certificate to stdout.", - )] - pub output: Option, - #[clap( - long, - help = "Emit binary data", - )] - pub binary: bool, -} - - -/// Documentation for the cert designators for the key subkey add -/// command. -pub struct SubkeyAddDoc {} - -impl cert_designator::AdditionalDocs for SubkeyAddDoc { - fn help(arg: &'static str, help: &'static str) -> clap::builder::StyledStr { - match arg { - "file" => - "Add a subkey to the key read from PATH" - .into(), - _ => { - debug_assert!(help.starts_with("Use certificates")); - help.replace("Use certificates", - "Add a subkey to the key") - .into() - }, - } - } -} - const SUBKEY_EXPORT_EXAMPLES: Actions = Actions { actions: &[ Action::Setup(Setup { diff --git a/src/cli/key/subkey/add.rs b/src/cli/key/subkey/add.rs new file mode 100644 index 00000000..73987ee0 --- /dev/null +++ b/src/cli/key/subkey/add.rs @@ -0,0 +1,185 @@ +use std::path::PathBuf; + +use clap::Args; +use clap::ArgGroup; + +use crate::cli::examples; +use examples::Action; +use examples::Actions; +use examples::Example; +use examples::Setup; + +use crate::cli::key::CipherSuite; +use crate::cli::types::CertDesignators; +use crate::cli::types::ClapData; +use crate::cli::types::EncryptPurpose; +use crate::cli::types::ExpirationArg; +use crate::cli::types::FileOrStdout; +use crate::cli::types::cert_designator; + +pub struct AdditionalDocs {} + +impl cert_designator::AdditionalDocs for AdditionalDocs { + fn help(arg: &'static str, help: &'static str) -> clap::builder::StyledStr { + match arg { + "file" => + "Add a subkey to the key read from PATH" + .into(), + _ => { + debug_assert!(help.starts_with("Use certificates")); + help.replace("Use certificates", + "Add a subkey to the key") + .into() + }, + } + } +} + +#[derive(Debug, Args)] +#[clap( + about = "Add a new subkey to a certificate", + long_about = "\ +Add a new subkey to a certificate. + +A subkey has one or more capabilities. + +`--can-sign` sets the signing capability, and means that the key may \ +be used for signing. `--can-authenticate` sets the authentication \ +capability, and means that the key may be used for authentication \ +(e.g., as an SSH key). `--can-certify` sets the certificate \ +capability, and means that the key may be used to make third-party \ +certifications. These capabilities may be combined. + +`--can-encrypt=storage` sets the storage encryption capability, and \ +means that the key may be used for storage \ +encryption. `--can-encrypt=transport` sets the transport encryption \ +capability, and means that the key may be used for transport \ +encryption. `--can-encrypt=universal` sets both the storage and the \ +transport encryption capability, and means that the key may be used \ +for both storage and transport encryption. The encryption \ +capabilities must not be combined with the signing or authentication \ +capability. + +Normally, `sq` prompts the user for a password to use to encrypt the \ +secret key material. The password for the new subkey may be different \ +from the other keys. When using `--without-password`, `sq` doesn't \ +prompt for a password, and doesn't password-protect the subkey. + +By default a new subkey doesn't expire on its own. However, its \ +validity period is limited by that of the certificate. Using the \ +`--expiration` argument allows setting a different expiration time. + +`sq key subkey add` respects the reference time set by the top-level \ +`--time` argument. It sets the creation time of the subkey to the specified \ +time. +", + after_help = EXAMPLES, +)] +#[clap(group(ArgGroup::new("authentication-group").args(&["can_authenticate", "can_encrypt"])))] +#[clap(group(ArgGroup::new("sign-group").args(&["can_sign", "can_encrypt"])))] +#[clap(group(ArgGroup::new("required-group").args(&["can_authenticate", "can_sign", "can_encrypt"]).required(true)))] +pub struct Command { + #[command(flatten)] + pub cert: CertDesignators< + cert_designator::CertUserIDEmailFileArgs, + cert_designator::NoPrefix, + cert_designator::OneValueAndFileRequiresOutput, + AdditionalDocs>, + + #[clap( + long, + value_name = "CIPHER-SUITE", + default_value_t = CipherSuite::Cv25519, + help = "Select the cryptographic algorithms for the subkey", + value_enum, + )] + pub cipher_suite: CipherSuite, + + #[command(flatten)] + pub expiration: ExpirationArg, + + #[clap( + long, + help = "Add a signing-capable subkey", + )] + pub can_sign: bool, + #[clap( + long, + help = "Add an authentication-capable subkey", + )] + pub can_authenticate: bool, + #[clap( + long, + value_name = "PURPOSE", + help = "Add an encryption-capable subkey [default: universal]", + long_help = "\ +Add an encryption-capable subkey. + +Encryption-capable subkeys can be marked as suitable for transport \ +encryption, storage encryption, or both, i.e., universal. [default: \ +universal]", + value_enum, + )] + pub can_encrypt: Option, + + #[clap( + long = "new-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.", + conflicts_with = "without_password", + )] + pub new_password_file: Option, + + #[clap( + long, + help = "Don't protect the subkey's secret key material with a password", + )] + pub without_password: bool, + + #[clap( + long, + value_name = FileOrStdout::VALUE_NAME, + help = "Write to the specified FILE", + long_help = "\ +Write to the specified FILE. + +If not specified, and the certificate was read from the certificate \ +store, imports the modified certificate into the key store. If not \ +specified, and the certificate was read from a file, writes the \ +modified certificate to stdout.", + )] + pub output: Option, + #[clap( + long, + help = "Emit binary data", + )] + pub binary: bool, +} + +const EXAMPLES: Actions = Actions { + actions: &[ + Action::Setup(Setup { + command: &[ + "sq", "key", "import", + "alice-secret.pgp", + ], + }), + Action::Example(Example { + comment: "\ +Add a new signing-capable subkey to Alice's key.", + command: &[ + "sq", "key", "subkey", "add", + "--without-password", + "--can-sign", + "--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0", + ], + }), + ] +}; +test_examples!(sq_key_subkey_add, EXAMPLES); diff --git a/src/commands/key/subkey.rs b/src/commands/key/subkey.rs index 53af1283..0783fc77 100644 --- a/src/commands/key/subkey.rs +++ b/src/commands/key/subkey.rs @@ -19,8 +19,8 @@ use openpgp::Packet; use openpgp::Result; use crate::Sq; -use crate::cli::key::subkey::AddCommand; use crate::cli::key::subkey::Command; +use crate::cli::key::subkey::add::Command as AddCommand; use crate::cli::key::subkey::DeleteCommand; use crate::cli::key::subkey::ExpireCommand; use crate::cli::key::subkey::ExportCommand;