Align sq key subkey expire and sq key subkey revoke.
- Make the latter take a named --key argument that can be given multiple times. - Fixes #329.
This commit is contained in:
parent
719f282544
commit
2c4ecbb41e
@ -534,6 +534,7 @@ pub struct ExpireCommand {
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
value_name = "FINGERPRINT|KEYID",
|
||||
help = "Change the expiration of this subkey",
|
||||
required = true,
|
||||
)]
|
||||
@ -585,13 +586,27 @@ const SUBKEY_REVOKE_EXAMPLES: Actions = Actions {
|
||||
"alice-secret.pgp",
|
||||
],
|
||||
}),
|
||||
|
||||
Action::Example(Example {
|
||||
comment: "\
|
||||
Revoke Alice's signing subkey.",
|
||||
command: &[
|
||||
"sq", "key", "subkey", "revoke",
|
||||
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
|
||||
"42020B87D51877E5AF8D272124F3955B0B8DECC8",
|
||||
"--key", "42020B87D51877E5AF8D272124F3955B0B8DECC8",
|
||||
"retired",
|
||||
"Subkey rotation.",
|
||||
],
|
||||
}),
|
||||
|
||||
Action::Example(Example {
|
||||
comment: "\
|
||||
Revoke Alice's signing subkey and encryption subkeys.",
|
||||
command: &[
|
||||
"sq", "key", "subkey", "revoke",
|
||||
"--cert", "EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
|
||||
"--key", "42020B87D51877E5AF8D272124F3955B0B8DECC8",
|
||||
"--key", "74DCDEAF17D9B995679EB52BA6E65EA2C8497728",
|
||||
"retired",
|
||||
"Subkey rotation.",
|
||||
],
|
||||
@ -675,10 +690,12 @@ for the file to contain more than one certificate.",
|
||||
pub revoker_file: Option<FileOrStdin>,
|
||||
|
||||
#[clap(
|
||||
long = "key",
|
||||
value_name = "FINGERPRINT|KEYID",
|
||||
help = "The subkey to revoke",
|
||||
help = "Revoke this subkey",
|
||||
required = true,
|
||||
)]
|
||||
pub subkey: KeyHandle,
|
||||
pub keys: Vec<KeyHandle>,
|
||||
|
||||
#[clap(
|
||||
value_name = "REASON",
|
||||
|
@ -6,7 +6,7 @@ use chrono::Utc;
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::cert::KeyBuilder;
|
||||
use openpgp::cert::SubkeyRevocationBuilder;
|
||||
use openpgp::packet::{Key, key};
|
||||
use openpgp::packet::{Key, Signature, key};
|
||||
use openpgp::packet::signature::subpacket::NotationData;
|
||||
use openpgp::parse::Parse;
|
||||
use openpgp::serialize::Serialize;
|
||||
@ -116,27 +116,26 @@ fn subkey_expire(sq: Sq, command: ExpireCommand)
|
||||
struct SubkeyRevocation {
|
||||
cert: Cert,
|
||||
revoker: Cert,
|
||||
revocation_packet: Packet,
|
||||
subkey: Key<key::PublicParts, key::SubordinateRole>,
|
||||
revocations: Vec<(Key<key::PublicParts, key::SubordinateRole>, Signature)>,
|
||||
}
|
||||
|
||||
impl SubkeyRevocation {
|
||||
/// Create a new SubkeyRevocation
|
||||
pub fn new(
|
||||
sq: &Sq,
|
||||
keyhandle: &KeyHandle,
|
||||
keyhandles: &[KeyHandle],
|
||||
cert: Cert,
|
||||
revoker: Option<Cert>,
|
||||
reason: ReasonForRevocation,
|
||||
message: &str,
|
||||
notations: &[(bool, NotationData)],
|
||||
) -> Result<Self> {
|
||||
let valid_cert = cert.with_policy(NULL_POLICY, None)?;
|
||||
let (revoker, mut signer)
|
||||
= get_secret_signer(sq, &cert, revoker.as_ref())?;
|
||||
|
||||
let (subkey, revocation_packet) = {
|
||||
let valid_cert = cert.with_policy(NULL_POLICY, None)?;
|
||||
|
||||
let mut revocations = Vec::new();
|
||||
for keyhandle in keyhandles {
|
||||
let keys = valid_cert.keys().subkeys()
|
||||
.key_handle(keyhandle.clone())
|
||||
.map(|skb| skb.key().clone())
|
||||
@ -156,7 +155,7 @@ impl SubkeyRevocation {
|
||||
)?;
|
||||
}
|
||||
let rev = rev.build(&mut signer, &cert, &subkey, None)?;
|
||||
(subkey.into(), Packet::Signature(rev))
|
||||
revocations.push((subkey, rev));
|
||||
} else if keys.len() > 1 {
|
||||
wprintln!("Key ID {} does not uniquely identify a subkey, \
|
||||
please use a fingerprint instead.\nValid subkeys:",
|
||||
@ -198,26 +197,31 @@ impl SubkeyRevocation {
|
||||
Ok(SubkeyRevocation {
|
||||
cert,
|
||||
revoker,
|
||||
revocation_packet,
|
||||
subkey,
|
||||
revocations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RevocationOutput for SubkeyRevocation {
|
||||
fn cert(&self) -> Result<Cert> {
|
||||
let cert = Cert::from_packets(vec![
|
||||
self.cert.primary_key().key().clone().into(),
|
||||
self.subkey.clone().into(),
|
||||
self.revocation_packet.clone(),
|
||||
].into_iter())?;
|
||||
|
||||
Ok(cert)
|
||||
Cert::from_packets(
|
||||
std::iter::once(
|
||||
Packet::from(self.cert.primary_key().key().clone()))
|
||||
.chain(self.revocations.iter().flat_map(
|
||||
|(k, s)| [k.clone().into(), s.clone().into()].into_iter()))
|
||||
)
|
||||
}
|
||||
|
||||
fn comment(&self) -> String {
|
||||
if self.revocations.len() == 1 {
|
||||
format!("Includes a revocation certificate to revoke the subkey {}",
|
||||
self.subkey.fingerprint())
|
||||
self.revocations[0].0.fingerprint())
|
||||
} else {
|
||||
let fingerprints: Vec<_> = self.revocations.iter()
|
||||
.map(|k| k.0.fingerprint().to_string()).collect();
|
||||
format!("Includes revocation certificates to revoke the subkeys {}",
|
||||
fingerprints.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
fn revoker(&self) -> &Cert {
|
||||
@ -352,7 +356,7 @@ pub fn subkey_revoke(
|
||||
|
||||
let revocation = SubkeyRevocation::new(
|
||||
&sq,
|
||||
&command.subkey,
|
||||
&command.keys,
|
||||
cert,
|
||||
revoker,
|
||||
command.reason.into(),
|
||||
|
@ -251,7 +251,7 @@ fn sq_key_subkey_revoke() -> Result<()> {
|
||||
"key",
|
||||
"subkey",
|
||||
"revoke",
|
||||
&subkey_fingerprint.to_string(),
|
||||
"--key", &subkey_fingerprint.to_string(),
|
||||
reason_str,
|
||||
message,
|
||||
]);
|
||||
@ -276,7 +276,8 @@ fn sq_key_subkey_revoke() -> Result<()> {
|
||||
}
|
||||
let output = cmd.output()?;
|
||||
if !output.status.success() {
|
||||
panic!("sq exited with non-zero status code: {:?}", output.stderr);
|
||||
panic!("sq exited with non-zero status code: {}",
|
||||
String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
// whether we found a revocation signature
|
||||
@ -347,6 +348,42 @@ fn sq_key_subkey_revoke() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sq_key_subkey_revoke_multiple() -> Result<()> {
|
||||
let sq = Sq::new();
|
||||
|
||||
let (cert, cert_path, _rev_path)
|
||||
= sq.key_generate(&["--email", "alice@example.org"], &[]);
|
||||
assert!(cert.keys().subkeys().count() > 0);
|
||||
sq.key_import(cert_path);
|
||||
|
||||
let mut cmd = sq.command();
|
||||
cmd.args([
|
||||
"key", "subkey", "revoke",
|
||||
"--cert", &cert.fingerprint().to_string(),
|
||||
]);
|
||||
|
||||
for subkey in cert.keys().subkeys() {
|
||||
cmd.args(["--key", &subkey.key().fingerprint().to_string(),]);
|
||||
}
|
||||
|
||||
cmd.args(["retired", "rotation"]);
|
||||
sq.run(cmd, Some(true));
|
||||
|
||||
let revoked = sq.cert_export(cert.key_handle());
|
||||
assert_eq!(cert.keys().subkeys().count(), revoked.keys().subkeys().count());
|
||||
|
||||
let vrevoked = revoked.with_policy(STANDARD_POLICY, sq.now())?;
|
||||
for subkey in vrevoked.keys().subkeys() {
|
||||
use sequoia_openpgp::cert::amalgamation::ValidAmalgamation;
|
||||
assert!(matches!(subkey.revocation_status(),
|
||||
RevocationStatus::Revoked(_)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn sq_key_subkey_revoke_thirdparty() -> Result<()> {
|
||||
let sq = Sq::new();
|
||||
@ -467,7 +504,7 @@ fn sq_key_subkey_revoke_thirdparty() -> Result<()> {
|
||||
"key",
|
||||
"subkey",
|
||||
"revoke",
|
||||
&subkey_fingerprint.to_string(),
|
||||
"--key", &subkey_fingerprint.to_string(),
|
||||
reason_str,
|
||||
message,
|
||||
]);
|
||||
|
Loading…
Reference in New Issue
Block a user