summaryrefslogtreecommitdiffstats
path: root/sq
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2022-02-03 10:55:32 +0100
committerNeal H. Walfield <neal@pep.foundation>2022-06-14 14:21:50 +0200
commitd68fba31b8fad07b925d73224289a4ddd34a8f71 (patch)
treefb34b75732de09f347ffd48637c2cf1ef72476b9 /sq
parent36270e24d2761a40e92668ab0d083afae322da4c (diff)
sq: Allow creating a certification with an expired or revoke key
- Add two new options to `sq certify`: `allow-not-alive-certifier` and `allow-revoked-certifier`. - If present, don't fail if the certifying key is not alive or revoked, respectively.
Diffstat (limited to 'sq')
-rw-r--r--sq/src/commands/certify.rs11
-rw-r--r--sq/src/commands/mod.rs32
-rw-r--r--sq/src/commands/revoke.rs14
-rw-r--r--sq/src/commands/sign.rs9
-rw-r--r--sq/src/sq-usage.rs8
-rw-r--r--sq/src/sq_cli.rs17
-rw-r--r--sq/tests/sq-certify.rs179
7 files changed, 249 insertions, 21 deletions
diff --git a/sq/src/commands/certify.rs b/sq/src/commands/certify.rs
index 36874648..7ca6bac0 100644
--- a/sq/src/commands/certify.rs
+++ b/sq/src/commands/certify.rs
@@ -15,6 +15,7 @@ use crate::Config;
use crate::parse_duration;
use crate::SECONDS_IN_YEAR;
use crate::commands::get_certification_keys;
+use crate::commands::GetKeysOptions;
pub fn certify(config: Config, m: &clap::ArgMatches)
-> Result<()>
@@ -157,12 +158,20 @@ pub fn certify(config: Config, m: &clap::ArgMatches)
}
}
+ let mut options = Vec::new();
+ if m.is_present("allow-not-alive-certifier") {
+ options.push(GetKeysOptions::AllowNotAlive);
+ }
+ if m.is_present("allow-revoked-certifier") {
+ options.push(GetKeysOptions::AllowRevoked);
+ }
// Sign it.
let signers = get_certification_keys(
&[certifier], &config.policy,
private_key_store,
- time)?;
+ time,
+ Some(&options))?;
assert_eq!(signers.len(), 1);
let mut signer = signers.into_iter().next().unwrap();
diff --git a/sq/src/commands/mod.rs b/sq/src/commands/mod.rs
index 1b92a366..a87076b1 100644
--- a/sq/src/commands/mod.rs
+++ b/sq/src/commands/mod.rs
@@ -55,16 +55,27 @@ pub mod keyring;
pub mod net;
pub mod certify;
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum GetKeysOptions {
+ AllowNotAlive,
+ AllowRevoked,
+}
+
/// Returns suitable signing keys from a given list of Certs.
fn get_keys<C>(certs: &[C], p: &dyn Policy,
private_key_store: Option<&str>,
timestamp: Option<SystemTime>,
- flags: KeyFlags)
+ flags: KeyFlags,
+ options: Option<&[GetKeysOptions]>)
-> Result<Vec<Box<dyn crypto::Signer + Send + Sync>>>
where C: Borrow<Cert>
{
let mut bad = Vec::new();
+ let options = options.unwrap_or(&[][..]);
+ let allow_not_alive = options.contains(&GetKeysOptions::AllowNotAlive);
+ let allow_revoked = options.contains(&GetKeysOptions::AllowRevoked);
+
let mut keys: Vec<Box<dyn crypto::Signer + Send + Sync>> = Vec::new();
'next_cert: for tsk in certs {
let tsk = tsk.borrow();
@@ -78,8 +89,9 @@ fn get_keys<C>(certs: &[C], p: &dyn Policy,
for ka in vc.keys().key_flags(flags.clone()) {
let bad_ = [
- matches!(ka.alive(), Err(_)),
- matches!(ka.revocation_status(), RevocationStatus::Revoked(_)),
+ ! allow_not_alive && matches!(ka.alive(), Err(_)),
+ ! allow_revoked && matches!(ka.revocation_status(),
+ RevocationStatus::Revoked(_)),
! ka.pk_algo().is_supported(),
];
if bad_.iter().any(|x| *x) {
@@ -174,12 +186,13 @@ fn get_keys<C>(certs: &[C], p: &dyn Policy,
/// appropriate key, then this returns an error.
fn get_signing_keys<C>(certs: &[C], p: &dyn Policy,
private_key_store: Option<&str>,
- timestamp: Option<SystemTime>)
+ timestamp: Option<SystemTime>,
+ options: Option<&[GetKeysOptions]>)
-> Result<Vec<Box<dyn crypto::Signer + Send + Sync>>>
where C: Borrow<Cert>
{
get_keys(certs, p, private_key_store, timestamp,
- KeyFlags::empty().set_signing())
+ KeyFlags::empty().set_signing(), options)
}
/// Returns suitable certification keys from a given list of Certs.
@@ -188,12 +201,13 @@ fn get_signing_keys<C>(certs: &[C], p: &dyn Policy,
/// appropriate key, then this returns an error.
fn get_certification_keys<C>(certs: &[C], p: &dyn Policy,
private_key_store: Option<&str>,
- timestamp: Option<SystemTime>)
+ timestamp: Option<SystemTime>,
+ options: Option<&[GetKeysOptions]>)
-> Result<Vec<Box<dyn crypto::Signer + Send + Sync>>>
where C: std::borrow::Borrow<Cert>
{
get_keys(certs, p, private_key_store, timestamp,
- KeyFlags::empty().set_certification())
+ KeyFlags::empty().set_certification(), options)
}
// Returns the smallest valid certificate.
@@ -283,8 +297,8 @@ pub fn encrypt(opts: EncryptOpts) -> Result<()> {
"Neither recipient nor password given"));
}
- let mut signers = get_signing_keys(&opts.signers, opts.policy,
- opts.private_key_store, opts.time)?;
+ let mut signers = get_signing_keys(
+ &opts.signers, opts.policy, opts.private_key_store, opts.time, None)?;
// Build a vector of recipients to hand to Encryptor.
let mut recipient_subkeys: Vec<Recipient> = Vec::new();
diff --git a/sq/src/commands/revoke.rs b/sq/src/commands/revoke.rs
index 3e020ea0..fe96f4f9 100644
--- a/sq/src/commands/revoke.rs
+++ b/sq/src/commands/revoke.rs
@@ -169,10 +169,9 @@ fn revoke(config: Config,
let mut output = config.create_or_stdout_safe(None)?;
let (secret, mut signer) = if let Some(secret) = secret.as_ref() {
- if let Ok(keys) = super::get_certification_keys(&[ secret ],
- &config.policy,
- private_key_store,
- time) {
+ if let Ok(keys) = super::get_certification_keys(
+ &[ secret ], &config.policy, private_key_store, time, None)
+ {
assert_eq!(keys.len(), 1);
(secret, keys.into_iter().next().expect("have one"))
} else {
@@ -190,10 +189,9 @@ does not contain a certification key with secret key material"));
}
}
} else {
- if let Ok(keys) = super::get_certification_keys(&[ &cert ],
- &config.policy,
- private_key_store,
- time) {
+ if let Ok(keys) = super::get_certification_keys(
+ &[ &cert ], &config.policy, private_key_store, time, None)
+ {
assert_eq!(keys.len(), 1);
(&cert, keys.into_iter().next().expect("have one"))
} else {
diff --git a/sq/src/commands/sign.rs b/sq/src/commands/sign.rs
index ff81846f..4a6a10ab 100644
--- a/sq/src/commands/sign.rs
+++ b/sq/src/commands/sign.rs
@@ -91,7 +91,8 @@ fn sign_data(opts: SignOpts) -> Result<()> {
(config.create_or_stdout_safe(output_path)?, Vec::new(), None)
};
- let mut keypairs = super::get_signing_keys(&secrets, &config.policy, private_key_store, time)?;
+ let mut keypairs = super::get_signing_keys(
+ &secrets, &config.policy, private_key_store, time, None)?;
if keypairs.is_empty() {
return Err(anyhow::anyhow!("No signing keys found"));
}
@@ -184,7 +185,8 @@ fn sign_message_(opts: SignOpts, output: &mut (dyn io::Write + Sync + Send)) ->
time,
notations, ..
} = opts;
- let mut keypairs = super::get_signing_keys(&secrets, &config.policy, private_key_store, time)?;
+ let mut keypairs = super::get_signing_keys(
+ &secrets, &config.policy, private_key_store, time, None)?;
if keypairs.is_empty() {
return Err(anyhow::anyhow!("No signing keys found"));
}
@@ -390,7 +392,8 @@ pub fn clearsign(config: Config,
notations: &[(bool, NotationData)])
-> Result<()>
{
- let mut keypairs = super::get_signing_keys(&secrets, &config.policy, private_key_store, time)?;
+ let mut keypairs = super::get_signing_keys(
+ &secrets, &config.policy, private_key_store, time, None)?;
if keypairs.is_empty() {
return Err(anyhow::anyhow!("No signing keys found"));
}
diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs
index 133cd370..72a51fe3 100644
--- a/sq/src/sq-usage.rs
+++ b/sq/src/sq-usage.rs
@@ -948,6 +948,14 @@
//! of trust. 60 is usually used for partially trusted. The default is
//! 120.
//!
+//! --allow-not-alive-certifier
+//! Allows the key to make a certification even if the current time is
+//! prior to its creation time or the current time is at or after its
+//! expiration time.
+//!
+//! --allow-revoked-certifier
+//! Don't fail if the certificate making the certification is revoked.
+//!
//! -B, --binary
//! Emits binary data
//!
diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs
index 272beb6e..d753604c 100644
--- a/sq/src/sq_cli.rs
+++ b/sq/src/sq_cli.rs
@@ -1221,6 +1221,23 @@ $ sq certify --time 20130721T0550+0200 neal.pgp ada.pgp ada
)]
pub expires_in: Option<String>,
#[clap(
+ long = "allow-not-alive-certifier",
+ help = "Don't fail if the certificate making the \
+ certification is not alive.",
+ long_help =
+ "Allows the key to make a certification even if \
+ the current time is prior to its creation time \
+ or the current time is at or after its expiration \
+ time.",
+ )]
+ pub allow_not_alive_certifier: bool,
+ #[clap(
+ long = "allow-revoked-certifier",
+ help = "Don't fail if the certificate making the \
+ certification is revoked.",
+ )]
+ pub allow_revoked_certifier: bool,
+ #[clap(
long = "private-key-store",
value_name = "KEY_STORE",
help = "Provides parameters for private key store",
diff --git a/sq/tests/sq-certify.rs b/sq/tests/sq-certify.rs
index 4cbd0a9c..b1ed3f3a 100644
--- a/sq/tests/sq-certify.rs
+++ b/sq/tests/sq-certify.rs
@@ -318,3 +318,182 @@ fn sq_certify_creation_time() -> Result<()>
Ok(())
}
+
+#[test]
+fn sq_certify_with_expired_key() -> Result<()>
+{
+ let seconds_in_day = 24 * 60 * 60;
+
+ let validity = time::Duration::new(30 * seconds_in_day, 0);
+ let creation_time = time::SystemTime::now() - 2 * validity;
+
+ let dir = TempDir::new()?;
+
+ // Alice's expired key certifies bob's not expired key.
+
+ let alice = "<alice@example.org>";
+ let alice_key = CertBuilder::new()
+ .add_signing_subkey()
+ .set_creation_time(creation_time)
+ .set_validity_period(validity)
+ .add_userid(alice)
+ .generate()
+ .map(|(key, _rev)| key)?;
+
+ let alice_pgp = dir.path().join("alice.pgp");
+ {
+ let mut file = File::create(&alice_pgp)?;
+ alice_key.as_tsk().serialize(&mut file)?;
+ }
+
+ // Bob's key has the same creation time, but it does not expire.
+ let bob = "<bob@other.org>";
+ let bob_key = CertBuilder::new()
+ .add_signing_subkey()
+ .set_creation_time(creation_time)
+ .add_userid(bob)
+ .generate()
+ .map(|(key, _rev)| key)?;
+
+ let bob_pgp = dir.path().join("bob.pgp");
+ {
+ let mut file = File::create(&bob_pgp)?;
+ bob_key.serialize(&mut file)?;
+ }
+
+ // Make sure using an expired key fails by default.
+ let mut cmd = Command::cargo_bin("sq")?;
+ cmd.args(["certify",
+ &alice_pgp.to_string_lossy(),
+ &bob_pgp.to_string_lossy(), bob ]);
+ cmd.assert().failure();
+
+
+ // Try again.
+ let mut cmd = Command::cargo_bin("sq")?;
+ cmd.args(["certify",
+ "--allow-not-alive-certifier",
+ &alice_pgp.to_string_lossy(),
+ &bob_pgp.to_string_lossy(), bob ]);
+
+ let assertion = cmd.assert().try_success()?;
+ let stdout = String::from_utf8_lossy(&assertion.get_output().stdout);
+
+ let cert = Cert::from_bytes(&*stdout)?;
+
+ let vc = cert.with_policy(P, None)?;
+
+ assert!(
+ creation_time.duration_since(vc.primary_key().creation_time()).unwrap()
+ < time::Duration::new(1, 0));
+
+ let mut userid = None;
+ for u in vc.userids() {
+ if u.userid().value() == bob.as_bytes() {
+ userid = Some(u);
+ break;
+ }
+ }
+
+ if let Some(userid) = userid {
+ let certifications: Vec<_> = userid.certifications().collect();
+ assert_eq!(certifications.len(), 1);
+ let certification = certifications.into_iter().next().unwrap();
+
+ assert_eq!(certification.get_issuers().into_iter().next(),
+ Some(KeyHandle::from(alice_key.fingerprint())));
+ } else {
+ panic!("missing user id");
+ }
+
+ Ok(())
+}
+
+#[test]
+fn sq_certify_with_revoked_key() -> Result<()>
+{
+ let seconds_in_day = 24 * 60 * 60;
+
+ let creation_time =
+ time::SystemTime::now() - time::Duration::new(seconds_in_day, 0);
+
+ let dir = TempDir::new()?;
+
+ // Alice's revoked key certifies bob's not expired key.
+
+ let alice = "<alice@example.org>";
+ let (alice_key, revocation) = CertBuilder::new()
+ .add_signing_subkey()
+ .set_creation_time(creation_time)
+ .add_userid(alice)
+ .generate()?;
+ let alice_key = alice_key.insert_packets(revocation)?;
+
+ let alice_pgp = dir.path().join("alice.pgp");
+ {
+ let mut file = File::create(&alice_pgp)?;
+ alice_key.as_tsk().serialize(&mut file)?;
+ }
+
+ // Bob's key has the same creation time, but it does not expire.
+ let bob = "<bob@other.org>";
+ let bob_key = CertBuilder::new()
+ .add_signing_subkey()
+ .set_creation_time(creation_time)
+ .add_userid(bob)
+ .generate()
+ .map(|(key, _rev)| key)?;
+
+ let bob_pgp = dir.path().join("bob.pgp");
+ {
+ let mut file = File::create(&bob_pgp)?;
+ bob_key.serialize(&mut file)?;
+ }
+
+ // Make sure using an expired key fails by default.
+ let mut cmd = Command::cargo_bin("sq")?;
+ cmd.args(["certify",
+ &alice_pgp.to_string_lossy(),
+ &bob_pgp.to_string_lossy(), bob ]);
+ cmd.assert().failure();
+
+
+ // Try again.
+ let mut cmd = Command::cargo_bin("sq")?;
+ cmd.args(["certify",
+ "--allow-revoked-certifier",
+ &alice_pgp.to_string_lossy(),
+ &bob_pgp.to_string_lossy(), bob ]);
+
+ let assertion = cmd.assert().try_success()?;
+ let stdout = String::from_utf8_lossy(&assertion.get_output().stdout);
+
+ let cert = Cert::from_bytes(&*stdout)?;
+
+ let vc = cert.with_policy(P, None)?;
+
+ assert!(
+ creation_time.duration_since(vc.primary_key().creation_time()).unwrap()
+ < time::Duration::new(1, 0));
+
+ let mut userid = None;
+ for u in vc.userids() {
+ if u.userid().value() == bob.as_bytes() {
+ userid = Some(u);
+ break;
+ }
+ }
+
+ if let Some(userid) = userid {
+ let certifications: Vec<_> = userid.certifications().collect();
+ assert_eq!(certifications.len(), 1);
+ let certification = certifications.into_iter().next().unwrap();
+
+ assert_eq!(certification.get_issuers().into_iter().next(),
+ Some(KeyHandle::from(alice_key.fingerprint())));
+ } else {
+ panic!("missing user id");
+ }
+
+ Ok(())
+}