diff options
author | Neal H. Walfield <neal@pep.foundation> | 2022-02-03 10:55:32 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2022-06-14 14:21:50 +0200 |
commit | d68fba31b8fad07b925d73224289a4ddd34a8f71 (patch) | |
tree | fb34b75732de09f347ffd48637c2cf1ef72476b9 /sq | |
parent | 36270e24d2761a40e92668ab0d083afae322da4c (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.rs | 11 | ||||
-rw-r--r-- | sq/src/commands/mod.rs | 32 | ||||
-rw-r--r-- | sq/src/commands/revoke.rs | 14 | ||||
-rw-r--r-- | sq/src/commands/sign.rs | 9 | ||||
-rw-r--r-- | sq/src/sq-usage.rs | 8 | ||||
-rw-r--r-- | sq/src/sq_cli.rs | 17 | ||||
-rw-r--r-- | sq/tests/sq-certify.rs | 179 |
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(()) +} |