diff options
author | Neal H. Walfield <neal@pep.foundation> | 2022-01-21 10:38:50 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2022-01-24 00:43:17 +0100 |
commit | 7f18fe6153bf032b694a52af37be181e0dd20d72 (patch) | |
tree | fa630db9d6234b2e78aeec26144108d5b5176c93 | |
parent | f8565a9689d2b73018ab13f80e12062309aa047b (diff) |
sq: Add a --time option to sq certify.
-rw-r--r-- | sq/src/commands/certify.rs | 21 | ||||
-rw-r--r-- | sq/src/sq-usage.rs | 14 | ||||
-rw-r--r-- | sq/src/sq_cli.rs | 15 | ||||
-rw-r--r-- | sq/tests/sq-certify.rs | 104 |
4 files changed, 140 insertions, 14 deletions
diff --git a/sq/src/commands/certify.rs b/sq/src/commands/certify.rs index 6a389064..36874648 100644 --- a/sq/src/commands/certify.rs +++ b/sq/src/commands/certify.rs @@ -1,5 +1,7 @@ use std::time::{SystemTime, Duration}; +use anyhow::Context; + use sequoia_openpgp as openpgp; use openpgp::Result; use openpgp::cert::prelude::*; @@ -24,7 +26,6 @@ pub fn certify(config: Config, m: &clap::ArgMatches) let certifier = Cert::from_file(certifier)?; let private_key_store = m.value_of("private-key-store"); let cert = Cert::from_file(cert)?; - let vc = cert.with_policy(&config.policy, None)?; let trust_depth: u8 = m.value_of("depth") .map(|s| s.parse()).unwrap_or(Ok(0))?; @@ -40,9 +41,20 @@ pub fn certify(config: Config, m: &clap::ArgMatches) let local = m.is_present("local"); let non_revocable = m.is_present("non-revocable"); + + let time = if let Some(t) = m.value_of("time") { + let time = SystemTime::from( + crate::parse_iso8601(t, chrono::NaiveTime::from_hms(0, 0, 0)) + .context(format!("Parsing --time {}", t))?); + Some(time) + } else { + None + }; + let expires = m.value_of("expires"); let expires_in = m.value_of("expires-in"); + let vc = cert.with_policy(&config.policy, time)?; // Find the matching User ID. let mut u = None; @@ -92,6 +104,11 @@ pub fn certify(config: Config, m: &clap::ArgMatches) builder = builder.set_revocable(false)?; } + // Creation time. + if let Some(time) = time { + builder = builder.set_signature_creation_time(time)?; + } + match (expires, expires_in) { (None, None) => // Default expiration. @@ -145,7 +162,7 @@ pub fn certify(config: Config, m: &clap::ArgMatches) let signers = get_certification_keys( &[certifier], &config.policy, private_key_store, - None)?; + time)?; assert_eq!(signers.len(), 1); let mut signer = signers.into_iter().next().unwrap(); diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index ec67191e..6ec1be5e 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -1006,6 +1006,20 @@ //! all intermediate introducers, and the certified certificate. //! Multiple regular expressions may be specified. In that case, at //! least one must match. +//! --time <TIME> +//! Sets the certification time to TIME. TIME is interpreted as an ISO +//! 8601 +//! timestamp. To set the certification time to June 9, 2011 at +//! midnight UTC, +//! you can do: +//! +//! $ sq certify --time 20130721 neal.pgp ada.pgp ada +//! +//! To include a time, add a T, the time and optionally the timezone +//! (the +//! default timezone is UTC): +//! +//! $ sq certify --time 20130721T0550+0200 neal.pgp ada.pgp ada //! //! ARGS: //! <CERTIFIER-KEY> diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index 4016c65b..0c3b23c9 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -1041,6 +1041,21 @@ $ sq certify juliet.pgp romeo.pgp \"<romeo@example.org>\" .arg(Arg::with_name("binary") .short("B").long("binary") .help("Emits binary data")) + .arg(Arg::with_name("time") + .long("time").value_name("TIME") + .help("Sets the certification time to TIME (as ISO 8601)") + .long_help("\ +Sets the certification time to TIME. TIME is interpreted as an ISO 8601 +timestamp. To set the certification time to June 9, 2011 at midnight UTC, +you can do: + +$ sq certify --time 20130721 neal.pgp ada.pgp ada + +To include a time, add a T, the time and optionally the timezone (the +default timezone is UTC): + +$ sq certify --time 20130721T0550+0200 neal.pgp ada.pgp ada +")) .arg(Arg::with_name("depth") .short("d").long("depth").value_name("TRUST_DEPTH") .help("Sets the trust depth") diff --git a/sq/tests/sq-certify.rs b/sq/tests/sq-certify.rs index 128b4ed8..b6a670d8 100644 --- a/sq/tests/sq-certify.rs +++ b/sq/tests/sq-certify.rs @@ -1,18 +1,23 @@ use std::fs::File; +use std::time; use std::time::Duration; use assert_cli::Assert; +use assert_cmd::Command; use tempfile::TempDir; use sequoia_openpgp as openpgp; use openpgp::Result; use openpgp::cert::prelude::*; +use openpgp::KeyHandle; use openpgp::packet::signature::subpacket::NotationData; use openpgp::packet::signature::subpacket::NotationDataFlags; use openpgp::parse::Parse; use openpgp::policy::StandardPolicy; use openpgp::serialize::Serialize; +const P: &StandardPolicy = &StandardPolicy::new(); + #[test] fn sq_certify() -> Result<()> { let tmp_dir = TempDir::new().unwrap(); @@ -41,10 +46,8 @@ fn sq_certify() -> Result<()> { "bob@example.org", ]) .stdout().satisfies(|output| { - let p = &StandardPolicy::new(); - let cert = Cert::from_bytes(output).unwrap(); - let vc = cert.with_policy(p, None).unwrap(); + let vc = cert.with_policy(P, None).unwrap(); for ua in vc.userids() { if ua.userid().value() == b"bob@example.org" { @@ -79,10 +82,8 @@ fn sq_certify() -> Result<()> { "--expires", "never" ]) .stdout().satisfies(|output| { - let p = &StandardPolicy::new(); - let cert = Cert::from_bytes(output).unwrap(); - let vc = cert.with_policy(p, None).unwrap(); + let vc = cert.with_policy(P, None).unwrap(); for ua in vc.userids() { if ua.userid().value() == b"bob@example.org" { @@ -122,10 +123,8 @@ fn sq_certify() -> Result<()> { "--expires-in", "1d", ]) .stdout().satisfies(|output| { - let p = &StandardPolicy::new(); - let cert = Cert::from_bytes(output).unwrap(); - let vc = cert.with_policy(p, None).unwrap(); + let vc = cert.with_policy(P, None).unwrap(); for ua in vc.userids() { if ua.userid().value() == b"bob@example.org" { @@ -174,16 +173,15 @@ fn sq_certify() -> Result<()> { "bob@example.org", ]) .stdout().satisfies(|output| { - let p = &mut StandardPolicy::new(); - let cert = Cert::from_bytes(output).unwrap(); // The standard policy will reject the // certification, because it has an unknown // critical notation. - let vc = cert.with_policy(p, None).unwrap(); + let vc = cert.with_policy(P, None).unwrap(); for ua in vc.userids() { if ua.userid().value() == b"bob@example.org" { + assert_eq!(ua.bundle().certifications().len(), 1); let certifications: Vec<_> = ua.certifications().collect(); assert_eq!(certifications.len(), 0); @@ -191,11 +189,15 @@ fn sq_certify() -> Result<()> { } // Accept the critical notation. + let p = &mut StandardPolicy::new(); p.good_critical_notations(&["foo"]); let vc = cert.with_policy(p, None).unwrap(); for ua in vc.userids() { if ua.userid().value() == b"bob@example.org" { + // There should be a single signature. + assert_eq!(ua.bundle().certifications().len(), 1); + let certifications: Vec<_> = ua.certifications().collect(); assert_eq!(certifications.len(), 1); @@ -243,3 +245,81 @@ fn sq_certify() -> Result<()> { Ok(()) } + +#[test] +fn sq_certify_creation_time() -> Result<()> +{ + // $ date +'%Y%m%dT%H%M%S%z'; date +'%s' + let iso8601 = "20220120T163236+0100"; + let t = 1642692756; + let t = time::UNIX_EPOCH + time::Duration::new(t, 0); + + let dir = TempDir::new()?; + + let gen = |userid: &str| { + let builder = CertBuilder::new() + .add_signing_subkey() + .set_creation_time(t) + .add_userid(userid); + builder.generate().map(|(key, _rev)| key) + }; + + // Alice certifies bob's key. + + let alice = "<alice@example.org>"; + let alice_key = gen(alice)?; + + let alice_pgp = dir.path().join("alice.pgp"); + { + let mut file = File::create(&alice_pgp)?; + alice_key.as_tsk().serialize(&mut file)?; + } + + let bob = "<bob@other.org>"; + let bob_key = gen(bob)?; + + let bob_pgp = dir.path().join("bob.pgp"); + { + let mut file = File::create(&bob_pgp)?; + bob_key.serialize(&mut file)?; + } + + // Build up the command line. + let mut cmd = Command::cargo_bin("sq")?; + cmd.args(["certify", + &alice_pgp.to_string_lossy(), + &bob_pgp.to_string_lossy(), bob, + "--time", iso8601 ]); + + 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, t)?; + + assert_eq!(vc.primary_key().creation_time(), t); + + 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()))); + + assert_eq!(certification.signature_creation_time(), Some(t)); + } else { + panic!("missing user id"); + } + + Ok(()) +} |