Use key designators for sq key subkey {delete,password}.

- Port `sq key subkey delete` and `sq key subkey password` to the
    key designator framework.
This commit is contained in:
Neal H. Walfield 2024-11-06 14:41:34 +01:00
parent f139b50f24
commit 4d5b807f61
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
9 changed files with 154 additions and 172 deletions

View File

@ -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<KeyHandle>,
#[command(flatten)]
pub keys: KeyDesignators<
key_designator::DefaultOptions,
KeyAdditionalDocs>,
#[clap(
long,

View File

@ -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<KeyHandle>,
#[command(flatten)]
pub keys: KeyDesignators<
key_designator::DefaultOptions,
KeyAdditionalDocs>,
#[clap(
long,

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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<KeyHandle>)
-> Result<(Cert, Vec<(Key<key::PublicParts, key::UnspecifiedRole>,
bool,
Option<Vec<keystore::Key>>)>)>
pub fn get_keys<CA, CP, CO, CD, KO, KD>(
sq: &Sq,
cert: &CertDesignators<CA, CP, CO, CD>,
keys: Option<&KeyDesignators<KO, KD>>)
-> Result<(Cert,
FileStdinOrKeyHandle,
Vec<(Key<key::PublicParts, key::UnspecifiedRole>,
bool,
Option<Vec<keystore::Key>>)>)>
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::<Vec<_>>()
};
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))
}

View File

@ -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<KeyHandle>,
output: Option<FileOrStdout>,
binary: bool)
pub fn delete<CA, CP, CO, CD, KO, KD>(
sq: Sq,
cert: CertDesignators<CA, CP, CO, CD>,
keys: Option<KeyDesignators<KO, KD>>,
output: Option<FileOrStdout>,
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() {

View File

@ -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<KeyHandle>,
clear_password: bool,
new_password_file: Option<&Path>,
output: Option<FileOrStdout>,
binary: bool)
pub fn password<CA, CP, CO, CD, KO, KD>(
sq: Sq,
cert: CertDesignators<CA, CP, CO, CD>,
keys: Option<KeyDesignators<KO, KD>>,
clear_password: bool,
new_password_file: Option<&Path>,
output: Option<FileOrStdout>,
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.