diff --git a/src/cli/key/subkey/delete.rs b/src/cli/key/subkey/delete.rs index ba358c41..15b6c401 100644 --- a/src/cli/key/subkey/delete.rs +++ b/src/cli/key/subkey/delete.rs @@ -1,8 +1,5 @@ use clap::Args; -use sequoia_openpgp as openpgp; -use openpgp::KeyHandle; - use crate::cli::examples; use examples::Action; use examples::Actions; @@ -12,11 +9,13 @@ use examples::Setup; use crate::cli::types::CertDesignators; use crate::cli::types::ClapData; use crate::cli::types::FileOrStdout; +use crate::cli::types::KeyDesignators; use crate::cli::types::cert_designator; +use crate::cli::types::key_designator; -pub struct AdditionalDocs {} +pub struct CertAdditionalDocs {} -impl cert_designator::AdditionalDocs for AdditionalDocs { +impl cert_designator::AdditionalDocs for CertAdditionalDocs { fn help(arg: &'static str, help: &'static str) -> clap::builder::StyledStr { match arg { "file" => @@ -31,6 +30,16 @@ impl cert_designator::AdditionalDocs for AdditionalDocs { } } +pub struct KeyAdditionalDocs {} + +impl key_designator::AdditionalDocs for KeyAdditionalDocs { + fn help(_arg: &'static str, _help: &'static str) + -> clap::builder::StyledStr + { + "Delete the specified key's secret key material".into() + } +} + #[derive(Debug, Args)] #[clap( name = "delete", @@ -41,6 +50,9 @@ Delete a certificate's secret key material. Unlike `sq key delete`, which deletes all the secret key material, this \ command only deletes the specified secret key material. +If the secret key material is managed by multiple devices, it is \ +deleted from all of them. + Although the secret key material is deleted, the public keys are \ retained. If you don't want the keys to be used anymore you should \ revoke the keys using `sq key subkey revoke`. @@ -53,22 +65,12 @@ pub struct Command { cert_designator::CertUserIDEmailFileArgs, cert_designator::NoPrefix, cert_designator::OneValueAndFileRequiresOutput, - AdditionalDocs>, + CertAdditionalDocs>, - #[clap( - long, - value_name = "FINGERPRINT|KEYID", - required = true, - help = "The keys to delete", - long_help = "\ -The keys to delete. - -The specified keys may be either the primary key or subkeys. - -If the secret key material is managed by multiple devices, it is \ -deleted from all of them.", - )] - pub key: Vec, + #[command(flatten)] + pub keys: KeyDesignators< + key_designator::DefaultOptions, + KeyAdditionalDocs>, #[clap( long, diff --git a/src/cli/key/subkey/password.rs b/src/cli/key/subkey/password.rs index 0e5ac8f6..78841ab4 100644 --- a/src/cli/key/subkey/password.rs +++ b/src/cli/key/subkey/password.rs @@ -2,9 +2,6 @@ use std::path::PathBuf; use clap::Args; -use sequoia_openpgp as openpgp; -use openpgp::KeyHandle; - use crate::cli::examples; use examples::Action; use examples::Actions; @@ -14,11 +11,13 @@ use examples::Setup; use crate::cli::types::CertDesignators; use crate::cli::types::ClapData; use crate::cli::types::FileOrStdout; +use crate::cli::types::KeyDesignators; use crate::cli::types::cert_designator; +use crate::cli::types::key_designator; -pub struct AdditionalDocs {} +pub struct CertAdditionalDocs {} -impl cert_designator::AdditionalDocs for AdditionalDocs { +impl cert_designator::AdditionalDocs for CertAdditionalDocs { fn help(arg: &'static str, help: &'static str) -> clap::builder::StyledStr { match arg { "file" => @@ -36,6 +35,17 @@ impl cert_designator::AdditionalDocs for AdditionalDocs { } } +pub struct KeyAdditionalDocs {} + +impl key_designator::AdditionalDocs for KeyAdditionalDocs { + fn help(_arg: &'static str, _help: &'static str) + -> clap::builder::StyledStr + { + "Change the password protecting the specified key's secret key \ + material".into() + } +} + #[derive(Debug, Args)] #[clap( name = "password", @@ -62,18 +72,12 @@ pub struct Command { cert_designator::CertUserIDEmailFileArgs, cert_designator::NoPrefix, cert_designator::OneValueAndFileRequiresOutput, - AdditionalDocs>, + CertAdditionalDocs>, - #[clap( - long, - help = "Change the password of the specified key", - long_help = "\ -Change the password of the specified key. - -The key may be either the primary key or a subkey.", - required = true, - )] - pub key: Vec, + #[command(flatten)] + pub keys: KeyDesignators< + key_designator::DefaultOptions, + KeyAdditionalDocs>, #[clap( long, diff --git a/src/commands/key/delete.rs b/src/commands/key/delete.rs index d51dfa80..cfcafa23 100644 --- a/src/commands/key/delete.rs +++ b/src/commands/key/delete.rs @@ -1,15 +1,14 @@ //! Changes key expiration. +use crate::Result; use crate::Sq; +use crate::cli::types::KeyDesignators; use crate::cli; use crate::common::key::delete; -use crate::Result; pub fn dispatch(sq: Sq, command: cli::key::delete::Command) -> Result<()> { - let handle = - sq.resolve_cert(&command.cert, sequoia_wot::FULLY_TRUSTED)?.1; - - delete::delete(sq, handle, Vec::new(), command.output, command.binary) + delete::delete(sq, command.cert, KeyDesignators::none(), + command.output, command.binary) } diff --git a/src/commands/key/password.rs b/src/commands/key/password.rs index a42e6358..ee135b03 100644 --- a/src/commands/key/password.rs +++ b/src/commands/key/password.rs @@ -1,17 +1,15 @@ //! Changes a key's password. +use crate::Result; use crate::Sq; +use crate::cli::types::KeyDesignators; use crate::cli; use crate::common::key::password; -use crate::Result; pub fn dispatch(sq: Sq, command: cli::key::password::Command) -> Result<()> { - let handle = - sq.resolve_cert(&command.cert, sequoia_wot::FULLY_TRUSTED)?.1; - - password::password(sq, handle, vec![], + password::password(sq, command.cert, KeyDesignators::none(), command.clear_password, command.new_password_file.as_deref(), command.output, command.binary) diff --git a/src/commands/key/subkey/delete.rs b/src/commands/key/subkey/delete.rs index d3065ab1..1001a5a0 100644 --- a/src/commands/key/subkey/delete.rs +++ b/src/commands/key/subkey/delete.rs @@ -5,10 +5,8 @@ use crate::common::key::delete; pub fn dispatch(sq: Sq, command: crate::cli::key::subkey::delete::Command) -> Result<()> { - let handle = - sq.resolve_cert(&command.cert, sequoia_wot::FULLY_TRUSTED)?.1; + assert!(! command.keys.is_empty()); - assert!(! command.key.is_empty()); - - delete(sq, handle, command.key, command.output, command.binary) + delete(sq, command.cert, Some(command.keys), + command.output, command.binary) } diff --git a/src/commands/key/subkey/password.rs b/src/commands/key/subkey/password.rs index 89417d7a..0824a348 100644 --- a/src/commands/key/subkey/password.rs +++ b/src/commands/key/subkey/password.rs @@ -5,12 +5,9 @@ use crate::common::key::password; pub fn dispatch(sq: Sq, command: crate::cli::key::subkey::password::Command) -> Result<()> { - let handle = - sq.resolve_cert(&command.cert, sequoia_wot::FULLY_TRUSTED)?.1; + assert!(! command.keys.is_empty()); - assert!(! command.key.is_empty()); - - password(sq, handle, command.key, + password(sq, command.cert, Some(command.keys), command.clear_password, command.new_password_file.as_deref(), command.output, command.binary) } diff --git a/src/common/key.rs b/src/common/key.rs index c8106cd8..7fd75a42 100644 --- a/src/common/key.rs +++ b/src/common/key.rs @@ -1,16 +1,19 @@ +use anyhow::Context; + use sequoia_openpgp as openpgp; use openpgp::Cert; -use openpgp::KeyHandle; use openpgp::Result; use openpgp::cert::amalgamation::key::PrimaryKey; -use openpgp::parse::Parse; use openpgp::packet::key; use openpgp::packet::Key; use sequoia_keystore as keystore; -use crate::cli::types::FileStdinOrKeyHandle; use crate::Sq; +use crate::cli::types::CertDesignators; +use crate::cli::types::FileStdinOrKeyHandle; +use crate::cli::types::KeyDesignators; +use crate::cli::types::cert_designator; mod expire; @@ -34,25 +37,35 @@ pub use password::password; /// secret key material. /// /// The returned keys are not unlocked. -pub fn get_keys<'a>(sq: &'a Sq, - cert_handle: FileStdinOrKeyHandle, - keys: Vec) - -> Result<(Cert, Vec<(Key, - bool, - Option>)>)> +pub fn get_keys( + sq: &Sq, + cert: &CertDesignators, + keys: Option<&KeyDesignators>) + -> Result<(Cert, + FileStdinOrKeyHandle, + Vec<(Key, + bool, + Option>)>)> +where CP: cert_designator::ArgumentPrefix, { let mut ks = None; - let cert = match cert_handle { - FileStdinOrKeyHandle::FileOrStdin(ref file) => { - let input = file.open()?; - let cert = Cert::from_buffered_reader(input)?; + assert_eq!(cert.len(), 1); + if let Some(keys) = keys { + assert!(keys.len() > 0); + } + let (cert, cert_source) + = sq.resolve_cert(&cert, sequoia_wot::FULLY_TRUSTED)?; + + let cert = match cert_source { + FileStdinOrKeyHandle::FileOrStdin(ref file) => { // If it is not a TSK, there is nothing to do. if ! cert.is_tsk() { return Err(anyhow::anyhow!( - "{} does not contain any secret key material.", - cert.fingerprint())); + "{} (read from {}) does not contain any secret \ + key material.", + cert.fingerprint(), file)); } cert @@ -63,94 +76,57 @@ pub fn get_keys<'a>(sq: &'a Sq, } }; + let vc = Cert::with_policy(&cert, sq.policy, sq.time) + .with_context(|| { + format!("The certificate {} is not valid under the \ + current policy.", + cert.fingerprint()) + })?; + + let kas = if let Some(keys) = keys { + sq.resolve_keys(&vc, &cert_source, &keys, true)? + } else { + vc.keys().collect::>() + }; + let mut ks = ks.map(|ks| ks.lock().unwrap()); - let list: Vec<(Key<_, _>, bool, Option<_>)> = if keys.is_empty() { - // Get all secret key material. - let list: Vec<_> - = cert.keys().filter_map(|ka| { - if let Some(ks) = ks.as_mut() { - let remote_keys = ks.find_key(ka.key_handle()).ok()?; - if remote_keys.is_empty() { - None - } else { - Some((ka.key().clone(), ka.primary(), Some(remote_keys))) - } - } else { - if ka.has_secret() { - Some((ka.key().clone(), ka.primary(), None)) - } else { - None - } - } - }).collect(); + let mut list: Vec<(Key<_, _>, bool, Option<_>)> = Vec::new(); - // Make the primary last so that if something goes wrong it - // is still possible to generate a revocation certificate. - list.into_iter().rev().collect() - } else { - // Get only the specified secret key material. - let mut list = Vec::new(); + let mut no_secret_key_material_count = 0; + for ka in kas.into_iter() { + let (no_secret_key_material, remote_keys) + = if let Some(ks) = ks.as_mut() + { + let remote_keys = ks.find_key(ka.key_handle())?; + (remote_keys.is_empty(), Some(remote_keys)) + } else { + (! ka.has_secret(), None) + }; - let mut not_found_key_count = 0; - let mut no_secret_key_material_count = 0; - for key in keys.into_iter() { - let ka = if let Some(ka) - = cert.keys().find(|ka| ka.fingerprint().aliases(&key)) - { - ka - } else { - wprintln!("{} does not contain {}", - cert.fingerprint(), key); - not_found_key_count += 1; - continue; - }; - - let (no_secret_key_material, remote_keys) - = if let Some(ks) = ks.as_mut() - { - let remote_keys = ks.find_key(ka.key_handle())?; - (remote_keys.is_empty(), Some(remote_keys)) - } else { - (! ka.has_secret(), None) - }; - - if no_secret_key_material { - wprintln!("{} does not contain any secret key material", - key); - no_secret_key_material_count += 1; - continue; - } - - list.push((ka.key().clone(), ka.primary(), remote_keys)); + if no_secret_key_material { + wprintln!("{} does not contain any secret key material", + ka.fingerprint()); + no_secret_key_material_count += 1; + continue; } - if not_found_key_count > 1 { - // Plural. - return Err(anyhow::anyhow!( - "{} keys not found", not_found_key_count)); - } else if not_found_key_count > 0 { - // Singular. - return Err(anyhow::anyhow!( - "{} key not found", not_found_key_count)); - } + list.push((ka.key().clone(), ka.primary(), remote_keys)); + } - if no_secret_key_material_count > 1 { - // Plural. - return Err(anyhow::anyhow!( - "{} of the specified keys don't have secret key material", - no_secret_key_material_count)); - } else if no_secret_key_material_count > 0 { - // Singular. - return Err(anyhow::anyhow!( - "{} of the specified keys doesn't have secret key material", - no_secret_key_material_count)); - } - - list - }; + if no_secret_key_material_count > 1 { + // Plural. + return Err(anyhow::anyhow!( + "{} of the specified keys don't have secret key material", + no_secret_key_material_count)); + } else if no_secret_key_material_count > 0 { + // Singular. + return Err(anyhow::anyhow!( + "{} of the specified keys doesn't have secret key material", + no_secret_key_material_count)); + } assert!(! list.is_empty()); - Ok((cert, list)) + Ok((cert, cert_source, list)) } diff --git a/src/common/key/delete.rs b/src/common/key/delete.rs index b1d3fafe..ccb541b5 100644 --- a/src/common/key/delete.rs +++ b/src/common/key/delete.rs @@ -2,28 +2,32 @@ use anyhow::Context; use sequoia_openpgp as openpgp; -use openpgp::KeyHandle; use openpgp::Result; use openpgp::Packet; use openpgp::serialize::Serialize; +use crate::Sq; +use crate::cli::types::CertDesignators; use crate::cli::types::FileOrStdout; use crate::cli::types::FileStdinOrKeyHandle; -use crate::Sq; +use crate::cli::types::KeyDesignators; +use crate::cli::types::cert_designator; use super::get_keys; -pub fn delete(sq: Sq, - cert_handle: FileStdinOrKeyHandle, - keys: Vec, - output: Option, - binary: bool) +pub fn delete( + sq: Sq, + cert: CertDesignators, + keys: Option>, + output: Option, + binary: bool) -> Result<()> +where CP: cert_designator::ArgumentPrefix, { - let ks = matches!(cert_handle, FileStdinOrKeyHandle::KeyHandle(_)); - - let (cert, to_delete) = get_keys(&sq, cert_handle, keys)?; + let (cert, cert_source, to_delete) + = get_keys(&sq, &cert, keys.as_ref())?; + let ks = matches!(cert_source, FileStdinOrKeyHandle::KeyHandle(_)); if ks { // Delete the secret key material from the key store. for (key, _primary, remote_keys) in to_delete.into_iter() { diff --git a/src/common/key/password.rs b/src/common/key/password.rs index ba68fd76..b7ab2dfa 100644 --- a/src/common/key/password.rs +++ b/src/common/key/password.rs @@ -4,7 +4,6 @@ use anyhow::Context; use sequoia_openpgp as openpgp; use openpgp::crypto::Password; -use openpgp::KeyHandle; use openpgp::serialize::Serialize; use openpgp::Packet; use openpgp::Result; @@ -14,18 +13,23 @@ use keystore::Protection; use crate::common; use crate::Sq; +use crate::cli::types::CertDesignators; use crate::cli::types::FileOrStdout; use crate::cli::types::FileStdinOrKeyHandle; +use crate::cli::types::KeyDesignators; +use crate::cli::types::cert_designator; use crate::common::password; -pub fn password(sq: Sq, - cert_handle: FileStdinOrKeyHandle, - keys: Vec, - clear_password: bool, - new_password_file: Option<&Path>, - output: Option, - binary: bool) +pub fn password( + sq: Sq, + cert: CertDesignators, + keys: Option>, + clear_password: bool, + new_password_file: Option<&Path>, + output: Option, + binary: bool) -> Result<()> +where CP: cert_designator::ArgumentPrefix, { let mut new_password_ = None; // Some(password) => new password @@ -44,11 +48,11 @@ pub fn password(sq: Sq, Ok(new_password_.clone().unwrap()) }; - let ks = matches!(cert_handle, FileStdinOrKeyHandle::KeyHandle(_)); - - let (cert, mut list) = super::get_keys(&sq, cert_handle, keys)?; + let (cert, cert_source, mut list) + = super::get_keys(&sq, &cert, keys.as_ref())?; let uid = sq.best_userid(&cert, true); + let ks = matches!(cert_source, FileStdinOrKeyHandle::KeyHandle(_)); if ks { // Change the password of the secret key material on the key // store.