summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2022-01-18 13:10:32 +0100
committerNeal H. Walfield <neal@pep.foundation>2022-01-20 14:28:32 +0100
commit2ba7985bef91c670a1375d60ff40da4f2c106134 (patch)
treec20340c40c7482a72db82ac281e2a7c5b7df508c
parent2e5c48a52db6284be98e9bc7b6541c98106ec580 (diff)
sq: Implement sq revoke userid.
- Generalize the existing code to handle revoking both certificates and User IDs.
-rw-r--r--sq/src/commands/revoke.rs134
-rw-r--r--sq/src/sq-usage.rs129
-rw-r--r--sq/src/sq_cli.rs111
-rw-r--r--sq/tests/sq-revoke.rs349
4 files changed, 640 insertions, 83 deletions
diff --git a/sq/src/commands/revoke.rs b/sq/src/commands/revoke.rs
index 47b9c1ab..0aabf3a6 100644
--- a/sq/src/commands/revoke.rs
+++ b/sq/src/commands/revoke.rs
@@ -7,7 +7,9 @@ use openpgp::cert::prelude::*;
use openpgp::Packet;
use openpgp::packet::signature::subpacket::NotationData;
use openpgp::packet::signature::subpacket::NotationDataFlags;
+use openpgp::packet::UserID;
use openpgp::parse::Parse;
+use openpgp::policy::NullPolicy;
use openpgp::Result;
use openpgp::serialize::Serialize;
use openpgp::types::ReasonForRevocation;
@@ -19,13 +21,17 @@ use crate::{
parse_iso8601,
};
+const NP: &NullPolicy = &NullPolicy::new();
+
pub fn dispatch(config: Config, m: &clap::ArgMatches) -> Result<()> {
enum Subcommand {
Certificate,
+ UserID,
}
let (subcommand, m) = match m.subcommand() {
("certificate", Some(m)) => (Subcommand::Certificate, m),
+ ("userid", Some(m)) => (Subcommand::UserID, m),
_ => unreachable!(),
};
@@ -69,6 +75,13 @@ pub fn dispatch(config: Config, m: &clap::ArgMatches) -> Result<()> {
_ => panic!("invalid values should be caught by clap"),
}
}
+ Subcommand::UserID => {
+ match &*reason {
+ "retired" => ReasonForRevocation::UIDRetired,
+ "unspecified" => ReasonForRevocation::Unspecified,
+ _ => panic!("invalid values should be caught by clap"),
+ }
+ }
};
let message: &str = m.value_of("message").expect("required");
@@ -102,6 +115,22 @@ pub fn dispatch(config: Config, m: &clap::ArgMatches) -> Result<()> {
config,
private_key_store,
cert,
+ None,
+ secret,
+ binary,
+ time,
+ reason,
+ message,
+ &notations)?;
+ }
+ Subcommand::UserID => {
+ let userid = m.value_of("userid").expect("required");
+
+ revoke(
+ config,
+ private_key_store,
+ cert,
+ Some(userid),
secret,
binary,
time,
@@ -117,6 +146,7 @@ pub fn dispatch(config: Config, m: &clap::ArgMatches) -> Result<()> {
fn revoke(config: Config,
private_key_store: Option<&str>,
cert: openpgp::Cert,
+ userid: Option<&str>,
secret: Option<openpgp::Cert>,
binary: bool,
time: Option<SystemTime>,
@@ -157,29 +187,91 @@ key material"));
let first_party = secret.fingerprint() == cert.fingerprint();
- let mut rev = CertRevocationBuilder::new()
- .set_reason_for_revocation(reason, message.as_bytes())?;
- if let Some(time) = time {
- rev = rev.set_signature_creation_time(time)?;
- }
- for (critical, notation) in notations {
- rev = rev.add_notation(notation.name(),
- notation.value(),
- Some(notation.flags().clone()),
- *critical)?;
- }
- let rev = rev.build(&mut signer, &cert, None)?;
- let rev = Packet::Signature(rev);
+ let rev: Packet = if let Some(userid) = userid {
+ // Create a revocation for a User ID.
+
+ // Unless force is specified, we require the User ID to have a
+ // valid self signature under the Null policy. We use the
+ // Null policy and not the standard policy, because it is
+ // still useful to revoke a User ID whose self signature is no
+ // longer valid. For instance, the binding signature may use
+ // SHA-1.
+ if ! config.force {
+ let vc = cert.with_policy(NP, None)?;
+ let present = vc.userids().any(|u| {
+ if let Ok(u) = String::from_utf8(u.value().to_vec()) {
+ if u == userid {
+ return true;
+ }
+ }
+ false
+ });
+
+ if ! present {
+ eprintln!("User ID: '{}' not found.\nValid User IDs:",
+ userid);
+ let mut have_valid = false;
+ for ua in vc.userids() {
+ if let Ok(u) = std::str::from_utf8(ua.userid().value()) {
+ have_valid = true;
+ eprintln!(" - {}", u);
+ }
+ }
+ if ! have_valid {
+ eprintln!(" - Certificate has no valid User IDs.");
+ }
+ return Err(anyhow::anyhow!("\
+The certificate does not contain the specified User ID. To create
+a revocation certificate for that User ID anyways, specify '--force'"));
+ }
+ }
+
+ let mut rev = UserIDRevocationBuilder::new()
+ .set_reason_for_revocation(reason, message.as_bytes())?;
+ if let Some(time) = time {
+ rev = rev.set_signature_creation_time(time)?;
+ }
+ for (critical, notation) in notations {
+ rev = rev.add_notation(notation.name(),
+ notation.value(),
+ Some(notation.flags().clone()),
+ *critical)?;
+ }
+ let rev = rev.build(&mut signer, &cert, &UserID::from(userid), None)?;
+ Packet::Signature(rev)
+ } else {
+ // Create a revocation for the certificate.
+ let mut rev = CertRevocationBuilder::new()
+ .set_reason_for_revocation(reason, message.as_bytes())?;
+ if let Some(time) = time {
+ rev = rev.set_signature_creation_time(time)?;
+ }
+ for (critical, notation) in notations {
+ rev = rev.add_notation(notation.name(),
+ notation.value(),
+ Some(notation.flags().clone()),
+ *critical)?;
+ }
+ let rev = rev.build(&mut signer, &cert, None)?;
+ Packet::Signature(rev)
+ };
- let packets: Vec<Packet> = if first_party {
+ let mut stub = None;
+ let packets: Vec<Packet> = if first_party && userid.is_none() {
vec![ rev ]
} else {
- cert_stub(cert.clone(), &config.policy, time, None)
- // If we fail to minimize the the certificate, just use as
- // it.
- .unwrap_or_else(|_err| cert.clone())
- // Now add the revocation certificate.
- .insert_packets(rev)?
+ let s = match cert_stub(cert.clone(), &config.policy, time,
+ userid.map(UserID::from).as_ref())
+ {
+ Ok(stub) => stub,
+ // We failed to create a stub. Just use the original
+ // certificate as is.
+ Err(_) => cert.clone(),
+ };
+
+ stub = Some(s.clone());
+
+ s.insert_packets(rev)?
.into_packets()
.collect()
};
@@ -227,7 +319,7 @@ key material"));
}
};
- let headers = cert.armor_headers();
+ let headers = stub.unwrap_or(cert).armor_headers();
let headers: Vec<_> = preface
.iter()
.map(|s| ("Comment", s.as_str()))
diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs
index 0951f6f5..4b3ca85d 100644
--- a/sq/src/sq-usage.rs
+++ b/sq/src/sq-usage.rs
@@ -1659,6 +1659,7 @@
//!
//! SUBCOMMANDS:
//! certificate Revoke a certificate
+//! userid Revoke a User ID
//! help Prints this message or the help of the given
//! subcommand(s)
//!
@@ -1667,6 +1668,10 @@
//! # Revoke a certificate.
//! $ sq revoke certificate --time 20220101 --certificate juliet.pgp \
//! compromised "My parents went through my things, and found my backup."
+//!
+//! # Revoke a User ID.
+//! $ sq revoke userid --time 20220101 --certificate juliet.pgp \
+//! "Juliet <juliet@capuleti.it>" retired "I've left the family."
//! ```
//!
//! ### Subcommand revoke certificate
@@ -1782,6 +1787,110 @@
//! that in the future."
//! ```
//!
+//! ### Subcommand revoke userid
+//!
+//! ```text
+//!
+//! Revokes a User ID
+//!
+//! Creates a revocation certificate for a User ID.
+//!
+//! If "--revocation-key" is provided, then that key is used to create
+//! the signature. If that key is different from the certificate being
+//! revoked, this creates a third-party revocation. This is normally only
+//! useful if the owner of the certificate designated the key to be a
+//! designated revoker.
+//!
+//! If "--revocation-key" is not provided, then the certificate must
+//! include a certification-capable key.
+//!
+//! USAGE:
+//! sq revoke userid [FLAGS] [OPTIONS] <USERID> <REASON> <MESSAGE>
+//!
+//! FLAGS:
+//! -B, --binary
+//! Emits binary data
+//!
+//! -h, --help
+//! Prints help information
+//!
+//! -V, --version
+//! Prints version information
+//!
+//!
+//! OPTIONS:
+//! --certificate <FILE>
+//!
+//! Reads the certificate to revoke from FILE or stdin, if omitted. It
+//! is
+//! an error for the file to contain more than one certificate.
+//! --notation <NAME> <VALUE>
+//!
+//! Adds a notation to the certification. A user-defined notation's
+//! name
+//! must be of the form "name@a.domain.you.control.org". If the
+//! notation's name starts with a !, then the notation is marked as
+//! being
+//! critical. If a consumer of a signature doesn't understand a
+//! critical
+//! notation, then it will ignore the signature. The notation is marked
+//! as being human readable.
+//! --private-key-store <KEY_STORE>
+//! Provides parameters for private key store
+//!
+//! --revocation-key <FILE>
+//!
+//! Signs the revocation certificate using KEY. If the key is different
+//! from the certificate, this creates a third-party revocation. If
+//! this
+//! option is not provided, and the certificate includes secret key
+//! material,
+//! then that key is used to sign the revocation certificate.
+//! -t, --time <TIME>
+//!
+//! Chooses keys valid at the specified time and sets the revocation
+//! certificate's creation time
+//!
+//! ARGS:
+//! <USERID>
+//!
+//!
+//! The User ID to revoke. By default, this must exactly match a
+//! self-signed User ID. Use --force to generate a revocation
+//! certificate
+//! for a User ID, which is not self signed.
+//! <REASON>
+//!
+//! The reason for the revocation. This must be either: retired, or
+//! unspecified:
+//!
+//! - retired means that this User ID is no longer valid. This is
+//! appropriate when someone leaves an organisation, and the
+//! organisation does not have their secret key material. For
+//! instance, if someone was part of Debian and retires, they would
+//! use this to indicate that a Debian-specific User ID is no longer
+//! valid.
+//!
+//! - unspecified means that a different reason applies.
+//!
+//! If the reason happened in the past, you should specify that using
+//! the
+//! --time argument. This allows OpenPGP implementations to more
+//! accurately reason about objects whose validity depends on the
+//! validity
+//! of a User ID. [possible values: retired, unspecified]
+//! <MESSAGE>
+//!
+//! A short, explanatory text that is shown to a viewer of the
+//! revocation
+//! certificate. It explains why the certificate has been revoked. For
+//! instance, if Alice has created a new key, she would generate a
+//! 'superceded' revocation certificate for her old key, and might
+//! include
+//! the message "I've created a new certificate, FINGERPRINT, please use
+//! that in the future."
+//! ```
+//!
//! ### Subcommand revoke EXAMPLES:
//!
//! ```text
@@ -1811,6 +1920,26 @@
//!
//! For more information try --help
//! ```
+//!
+//! ### Subcommand revoke #
+//!
+//! ```text
+//!
+//! USAGE:
+//! sq revoke <SUBCOMMAND>
+//!
+//! For more information try --help
+//! ```
+//!
+//! ### Subcommand revoke "Juliet
+//!
+//! ```text
+//!
+//! USAGE:
+//! sq revoke <SUBCOMMAND>
+//!
+//! For more information try --help
+//! ```
#![doc(html_favicon_url = "https://docs.sequoia-pgp.org/favicon.png")]
#![doc(html_logo_url = "https://docs.sequoia-pgp.org/logo.svg")]
diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs
index 600bef3a..dad29929 100644
--- a/sq/src/sq_cli.rs
+++ b/sq/src/sq_cli.rs
@@ -1334,6 +1334,10 @@ certificate to a keyserver.
# Revoke a certificate.
$ sq revoke certificate --time 20220101 --certificate juliet.pgp \\
compromised \"My parents went through my things, and found my backup.\"
+
+# Revoke a User ID.
+$ sq revoke userid --time 20220101 --certificate juliet.pgp \\
+ \"Juliet <juliet@capuleti.it>\" retired \"I've left the family.\"
")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(SubCommand::with_name("certificate")
@@ -1445,6 +1449,113 @@ as being human readable."))
.arg(Arg::with_name("binary")
.short("B").long("binary")
.help("Emits binary data"))
+ )
+ .subcommand(SubCommand::with_name("userid")
+ .display_order(110)
+ .about("Revoke a User ID")
+ .long_about("
+Revokes a User ID
+
+Creates a revocation certificate for a User ID.
+
+If \"--revocation-key\" is provided, then that key is used to create
+the signature. If that key is different from the certificate being
+revoked, this creates a third-party revocation. This is normally only
+useful if the owner of the certificate designated the key to be a
+designated revoker.
+
+If \"--revocation-key\" is not provided, then the certificate must
+include a certification-capable key.")
+
+ .arg(Arg::with_name("input")
+ .value_name("FILE")
+ .long("certificate")
+ .alias("cert")
+ .help("
+The certificate contain the User ID to revoke")
+ .long_help("
+Reads the certificate to revoke from FILE or stdin, if omitted. It is
+an error for the file to contain more than one certificate.")
+ )
+ .arg(Arg::with_name("secret-key-file")
+ .long("revocation-key").value_name("FILE")
+ .help("Signs the revocation certificate using KEY")
+ .long_help("
+Signs the revocation certificate using KEY. If the key is different
+from the certificate, this creates a third-party revocation. If this
+option is not provided, and the certificate includes secret key material,
+then that key is used to sign the revocation certificate.")
+ )
+ .arg(Arg::with_name("private-key-store")
+ .long("private-key-store").value_name("KEY_STORE")
+ .help("Provides parameters for private key store")
+ )
+ .arg(Arg::with_name("userid")
+ .value_name("USERID")
+ .required(true)
+ .help("The User ID to revoke")
+ .long_help("
+
+The User ID to revoke. By default, this must exactly match a
+self-signed User ID. Use --force to generate a revocation certificate
+for a User ID, which is not self signed.")
+ )
+ .arg(Arg::with_name("reason")
+ .value_name("REASON")
+ .required(true)
+ .possible_values(&["retired",
+ "unspecified"])
+ .help("The reason for the revocation")
+ .long_help("
+The reason for the revocation. This must be either: retired, or
+unspecified:
+
+ - retired means that this User ID is no longer valid. This is
+ appropriate when someone leaves an organisation, and the
+ organisation does not have their secret key material. For
+ instance, if someone was part of Debian and retires, they would
+ use this to indicate that a Debian-specific User ID is no longer
+ valid.
+
+ - unspecified means that a different reason applies.
+
+If the reason happened in the past, you should specify that using the
+--time argument. This allows OpenPGP implementations to more
+accurately reason about objects whose validity depends on the validity
+of a User ID.")
+ )
+ .arg(Arg::with_name("message")
+ .value_name("MESSAGE")
+ .required(true)
+ .help("A short, explanatory text")
+ .long_help("
+A short, explanatory text that is shown to a viewer of the revocation
+certificate. It explains why the certificate has been revoked. For
+instance, if Alice has created a new key, she would generate a
+'superceded' revocation certificate for her old key, and might include
+the message \"I've created a new certificate, FINGERPRINT, please use
+that in the future.\"")
+ )
+ .arg(Arg::with_name("time")
+ .short("t").long("time").value_name("TIME")
+ .help("
+Chooses keys valid at the specified time and sets the revocation
+certificate's creation time"))
+ .arg(Arg::with_name("notation")
+ .value_names(&["NAME", "VALUE"])
+ .long("notation")
+ .multiple(true).number_of_values(2)
+ .help("Adds a notation to the certification.")
+ .long_help("
+Adds a notation to the certification. A user-defined notation's name
+must be of the form \"name@a.domain.you.control.org\". If the
+notation's name starts with a !, then the notation is marked as being
+critical. If a consumer of a signature doesn't understand a critical
+notation, then it will ignore the signature. The notation is marked
+as being human readable."))
+ .arg(Arg::with_name("binary")
+ .short("B").long("binary")
+ .help("Emits binary data"))
)
);
diff --git a/sq/tests/sq-revoke.rs b/sq/tests/sq-revoke.rs
index 649ff746..3cbf0c33 100644
--- a/sq/tests/sq-revoke.rs
+++ b/sq/tests/sq-revoke.rs
@@ -12,6 +12,7 @@ use openpgp::Result;
use openpgp::cert::prelude::*;
use openpgp::parse::Parse;
use openpgp::Packet;
+use openpgp::packet::UserID;
use openpgp::PacketPile;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::Serialize;
@@ -24,10 +25,17 @@ const TRACE: bool = false;
mod integration {
use super::*;
- const _P: StandardPolicy = StandardPolicy::new();
- const P: &StandardPolicy = &_P;
+ const P: &StandardPolicy = &StandardPolicy::new();
- fn t(reason: ReasonForRevocation,
+ const ALICE: &str = "<alice@example.org>";
+
+ // USERIDS is a vector of User IDs. If it is empty, then a
+ // default User ID will be used and the *certificate* will be
+ // revoked. If it contains at least one entry, then each entry
+ // will be added as a User ID, and the last User ID will be
+ // revoked.
+ fn t(mut userids: Vec<&str>,
+ reason: ReasonForRevocation,
reason_message: &str,
stdin: bool,
third_party: bool,
@@ -40,21 +48,28 @@ mod integration {
t - Duration::nanoseconds(t.timestamp_subsec_nanos() as i64)
});
- // We're going to revoke alice's certificate. If we're doing
- // it via a third-party revocation, then bob is the revoker.
- // Otherwise, it's alice.
- let (alice, _) =
- CertBuilder::general_purpose(None, Some("alice@example.org"))
- .set_creation_time(
- time.map(|t| (t - Duration::hours(1)).into()))
- .generate()?;
+ let gen = |userids: &[&str]| {
+ let mut builder = CertBuilder::new()
+ .set_creation_time(
+ time.map(|t| (t - Duration::hours(1)).into()));
+ for &u in userids {
+ builder = builder.add_userid(u);
+ }
+ builder.generate().map(|(key, _rev)| key)
+ };
- let (bob, _) =
- CertBuilder::general_purpose(None, Some("bob@example.org"))
- .set_creation_time(
- time.map(|t| (t - Duration::hours(1)).into()))
- .generate()?;
+ let userid = if userids.len() == 0 {
+ userids = vec![ ALICE ];
+ None
+ } else {
+ Some(userids[userids.len() - 1])
+ };
+ // We're going to revoke alice's certificate or a User ID. If
+ // we're doing it via a third-party revocation, then bob is
+ // the revoker. Otherwise, it's alice.
+ let alice = gen(&userids)?;
+ let bob = gen(&[ "<revoker@some.org>" ])?;
let mut cert = Vec::new();
alice.serialize(&mut cert)?;
@@ -69,18 +84,30 @@ mod integration {
// Build up the command line.
let mut cmd = Command::cargo_bin("sq")?;
- cmd.args([
- "revoke",
- "certificate",
- match reason {
- ReasonForRevocation::KeyCompromised => "compromised",
- ReasonForRevocation::KeyRetired => "retired",
- ReasonForRevocation::KeySuperseded => "superseded",
- ReasonForRevocation::Unspecified => "unspecified",
- _ => panic!("Invalid reason: {}", reason),
- },
- reason_message
- ]);
+ cmd.arg("revoke");
+ if let Some(userid) = userid {
+ cmd.args([
+ "userid", userid,
+ match reason {
+ ReasonForRevocation::UIDRetired => "retired",
+ ReasonForRevocation::Unspecified => "unspecified",
+ _ => panic!("Invalid reason: {}", reason),
+ },
+ reason_message
+ ]);
+ } else {
+ cmd.args([
+ "certificate",
+ match reason {
+ ReasonForRevocation::KeyCompromised => "compromised",
+ ReasonForRevocation::KeyRetired => "retired",
+ ReasonForRevocation::KeySuperseded => "superseded",
+ ReasonForRevocation::Unspecified => "unspecified",
+ _ => panic!("Invalid reason: {}", reason),
+ },
+ reason_message
+ ]);
+ }
let _tmp_dir = match (third_party, stdin) {
(true, true) => {
@@ -177,35 +204,102 @@ mod integration {
}
// Get the revocation certificate.
+ {
+ let vc = alice.with_policy(P, time.map(Into::into)).unwrap();
+ assert!(matches!(vc.revocation_status(),
+ RevocationStatus::NotAsFarAsWeKnow));
+
+ if let Some(userid) = userid {
+ let mut found = false;
+ for u in vc.userids() {
+ if u.value() == userid.as_bytes() {
+ assert!(matches!(u.revocation_status(),
+ RevocationStatus::NotAsFarAsWeKnow));
+
+ found = true;
+ break;
+ }
+ }
+ assert!(found, "User ID {} not found on certificate", userid);
+ }
+ }
- assert!(matches!(
- alice.with_policy(P, time.map(Into::into)).unwrap()
- .revocation_status(),
- RevocationStatus::NotAsFarAsWeKnow));
-
- let sig = if third_party {
+ let sig = if third_party || userid.is_some() {
// We should get a certificate stub.
let result = Cert::from_bytes(&*stdout)?;
- let status = result.with_policy(P, time.map(Into::into))?
- .revocation_status();
- if let RevocationStatus::CouldBe(sigs) = status {
- assert_eq!(sigs.len(), 1);
- let sig = sigs.into_iter().next().unwrap();
- // Bob issued the revocation.
- assert_eq!(sig.get_issuers().into_iter().next(),
- Some(bob.fingerprint().into()));
+ let vc = result.with_policy(P, time.map(Into::into))?;
- // Verify the revocation.
- sig.clone()
- .verify_primary_key_revocation(
- &bob.primary_key(),
- &alice.primary_key())
- .context("revocation is not valid")?;
+ // Make sure the certificate stub only contains the
+ // revoked User ID (the rest should be striped).
+ assert_eq!(vc.userids().count(), 1);
- sig.clone()
+ // Get the revocation status of the revoked object.
+ let status = if let Some(userid) = userid {
+ let mut status = None;
+ for u in vc.userids() {
+ if u.value() == userid.as_bytes() {
+ status = Some(u.revocation_status());
+ break;
+ }
+ }
+ if let Some(status) = status {
+ status
+ } else {
+ panic!("Revoked user ID {} not found on revoked certificate",
+ userid);
+ }
+ } else {
+ vc.revocation_status()
+ };
+
+ // Make sure the revocation status is sane.
+ if third_party {
+ if let RevocationStatus::CouldBe(sigs) = status {
+ assert_eq!(sigs.len(), 1);
+ let sig = sigs.into_iter().next().unwrap();
+
+ // Bob issued the revocation.
+ assert_eq!(sig.get_issuers().into_iter().next(),
+ Some(bob.fingerprint().into()));
+
+ // Verify the revocation.
+ if let Some(userid) = userid {
+ sig.clone()
+ .verify_userid_revocation(
+ &bob.primary_key(),
+ &alice.primary_key(),
+ &UserID::from(userid))
+ .context("revocation is not valid")?;
+ } else {
+ sig.clone()
+ .verify_primary_key_revocation(
+ &bob.primary_key(),
+ &alice.primary_key())
+ .context("revocation is not valid")?;
+ }
+
+ sig.clone()
+ } else {
+ panic!("Unexpected revocation status: {:?}", status);
+ }
} else {
- panic!("Unexpected revocation status: {:?}", status);
+ assert!(userid.is_some());
+ if let RevocationStatus::Revoked(sigs) = status {
+ assert_eq!(sigs.len(), 1);
+ let sig = sigs.into_iter().next().unwrap();
+
+ // Alice issued the revocation.
+ assert_eq!(sig.get_issuers().into_iter().next(),
+ Some(alice.fingerprint().into()));
+
+ // Since it is a self-siganture, sig has already
+ // been validated.
+
+ sig.clone()
+ } else {
+ panic!("Unexpected revocation status: {:?}", status);
+ }
}
} else {
// We should get just a single signature packet.
@@ -234,7 +328,11 @@ mod integration {