summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2022-01-21 10:38:50 +0100
committerNeal H. Walfield <neal@pep.foundation>2022-01-24 00:43:17 +0100
commit7f18fe6153bf032b694a52af37be181e0dd20d72 (patch)
treefa630db9d6234b2e78aeec26144108d5b5176c93
parentf8565a9689d2b73018ab13f80e12062309aa047b (diff)
sq: Add a --time option to sq certify.
-rw-r--r--sq/src/commands/certify.rs21
-rw-r--r--sq/src/sq-usage.rs14
-rw-r--r--sq/src/sq_cli.rs15
-rw-r--r--sq/tests/sq-certify.rs104
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(())
+}