summaryrefslogtreecommitdiffstats
path: root/tool
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2020-01-17 16:01:33 +0100
committerNeal H. Walfield <neal@pep.foundation>2020-01-17 16:17:51 +0100
commit3dc2d24bc3920d0216ff7f19e01e4e073a292a24 (patch)
tree551997319c32bc9c684401a72301dfe41d85bd6c /tool
parent7cc22083ffc9874c39c957464bde9662e2fe8317 (diff)
tool: Add an option to specify the signing time.
- Add the option `--time` to the `sign` and `encrypt` subcommands to allow the user to set the signature's creation time. - Use the value of this option to select the signing keys.
Diffstat (limited to 'tool')
-rw-r--r--tool/src/commands/mod.rs14
-rw-r--r--tool/src/commands/sign.rs23
-rw-r--r--tool/src/sq-usage.rs4
-rw-r--r--tool/src/sq.rs92
-rw-r--r--tool/src/sq_cli.rs15
5 files changed, 132 insertions, 16 deletions
diff --git a/tool/src/commands/mod.rs b/tool/src/commands/mod.rs
index 1dc7b908..5b640496 100644
--- a/tool/src/commands/mod.rs
+++ b/tool/src/commands/mod.rs
@@ -3,6 +3,7 @@ use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{self, Write};
+use std::time::SystemTime;
use rpassword;
extern crate sequoia_openpgp as openpgp;
@@ -43,12 +44,12 @@ pub use self::inspect::inspect;
pub mod key;
/// Returns suitable signing keys from a given list of Certs.
-fn get_signing_keys(certs: &[openpgp::Cert])
+fn get_signing_keys(certs: &[openpgp::Cert], timestamp: Option<SystemTime>)
-> Result<Vec<crypto::KeyPair>>
{
let mut keys = Vec::new();
'next_cert: for tsk in certs {
- for key in tsk.keys().policy(None).alive().revoked(false)
+ for key in tsk.keys().policy(timestamp).alive().revoked(false)
.for_signing()
.map(|ka| ka.key())
{
@@ -81,8 +82,8 @@ pub fn encrypt(mapping: &mut store::Mapping,
input: &mut dyn io::Read, output: &mut dyn io::Write,
npasswords: usize, recipients: Vec<&str>,
mut certs: Vec<openpgp::Cert>, signers: Vec<openpgp::Cert>,
- mode: openpgp::types::KeyFlags,
- compression: &str)
+ mode: openpgp::types::KeyFlags, compression: &str,
+ time: Option<SystemTime>)
-> Result<()> {
for r in recipients {
certs.push(mapping.lookup(r).context("No such key found")?.cert()?);
@@ -103,7 +104,7 @@ pub fn encrypt(mapping: &mut store::Mapping,
"Neither recipient nor password given"));
}
- let mut signers = get_signing_keys(&signers)?;
+ let mut signers = get_signing_keys(&signers, time)?;
// Build a vector of references to hand to Signer.
let recipients: Vec<&openpgp::Cert> = certs.iter().collect();
@@ -160,6 +161,9 @@ pub fn encrypt(mapping: &mut store::Mapping,
let mut signer = Signer::new(sink, signers.pop().unwrap());
for s in signers {
signer = signer.add_signer(s);
+ if let Some(time) = time {
+ signer = signer.creation_time(time);
+ }
}
for r in recipients {
signer = signer.add_intended_recipient(r);
diff --git a/tool/src/commands/sign.rs b/tool/src/commands/sign.rs
index a2303fba..673c1e51 100644
--- a/tool/src/commands/sign.rs
+++ b/tool/src/commands/sign.rs
@@ -2,6 +2,7 @@ use failure::{self, ResultExt};
use std::fs;
use std::io;
use std::path::PathBuf;
+use std::time::SystemTime;
use tempfile::NamedTempFile;
extern crate sequoia_openpgp as openpgp;
@@ -20,20 +21,22 @@ use crate::create_or_stdout;
pub fn sign(input: &mut dyn io::Read, output_path: Option<&str>,
secrets: Vec<openpgp::Cert>, detached: bool, binary: bool,
- append: bool, notarize: bool, force: bool)
+ append: bool, notarize: bool, time: Option<SystemTime>,
+ force: bool)
-> Result<()> {
match (detached, append|notarize) {
(_, false) | (true, true) =>
sign_data(input, output_path, secrets, detached, binary, append,
- force),
+ time, force),
(false, true) =>
- sign_message(input, output_path, secrets, binary, notarize, force),
+ sign_message(input, output_path, secrets, binary, notarize,
+ time, force),
}
}
fn sign_data(input: &mut dyn io::Read, output_path: Option<&str>,
secrets: Vec<openpgp::Cert>, detached: bool, binary: bool,
- append: bool, force: bool)
+ append: bool, time: Option<SystemTime>, force: bool)
-> Result<()> {
let (mut output, prepend_sigs, tmp_path):
(Box<dyn io::Write>, Vec<Signature>, Option<PathBuf>) =
@@ -80,7 +83,7 @@ fn sign_data(input: &mut dyn io::Read, output_path: Option<&str>,
output
};
- let mut keypairs = super::get_signing_keys(&secrets)?;
+ let mut keypairs = super::get_signing_keys(&secrets, time)?;
if keypairs.is_empty() {
return Err(failure::format_err!("No signing keys found"));
}
@@ -97,6 +100,9 @@ fn sign_data(input: &mut dyn io::Read, output_path: Option<&str>,
let mut signer = Signer::new(sink, keypairs.pop().unwrap());
for s in keypairs {
signer = signer.add_signer(s);
+ if let Some(time) = time {
+ signer = signer.creation_time(time);
+ }
}
if detached {
signer = signer.detached();
@@ -130,7 +136,7 @@ fn sign_data(input: &mut dyn io::Read, output_path: Option<&str>,
fn sign_message(input: &mut dyn io::Read, output_path: Option<&str>,
secrets: Vec<openpgp::Cert>, binary: bool, notarize: bool,
- force: bool)
+ time: Option<SystemTime>, force: bool)
-> Result<()> {
let mut output = create_or_stdout(output_path, force)?;
let output = if ! binary {
@@ -141,7 +147,7 @@ fn sign_message(input: &mut dyn io::Read, output_path: Option<&str>,
output
};
- let mut keypairs = super::get_signing_keys(&secrets)?;
+ let mut keypairs = super::get_signing_keys(&secrets, time)?;
if keypairs.is_empty() {
return Err(failure::format_err!("No signing keys found"));
}
@@ -213,6 +219,9 @@ fn sign_message(input: &mut dyn io::Read, output_path: Option<&str>,
let mut signer = Signer::new(sink, keypairs.pop().unwrap());
for s in keypairs.drain(..) {
signer = signer.add_signer(s);
+ if let Some(time) = time {
+ signer = signer.creation_time(time);
+ }
}
sink = signer.build().context("Failed to create signer")?;
state = State::Signing { signature_count: 0, };
diff --git a/tool/src/sq-usage.rs b/tool/src/sq-usage.rs
index be4b3695..d09e8e86 100644
--- a/tool/src/sq-usage.rs
+++ b/tool/src/sq-usage.rs
@@ -87,6 +87,8 @@
//! -r, --recipient <LABEL>... Recipient to encrypt for (can be given multiple times)
//! --recipient-key-file <CERT-FILE>... Recipient to encrypt for, given as a file (can be given multiple times)
//! --signer-key-file <TSK-FILE>... Secret key to sign with, given as a file (can be given multiple times)
+//! -t, --time <TIME> Chooses keys valid at the specified time and sets the signature's
+//! creation time
//!
//! ARGS:
//! <FILE> Sets the input file to use
@@ -111,6 +113,8 @@
//! OPTIONS:
//! -o, --output <FILE> Sets the output file to use
//! --secret-key-file <TSK-FILE>... Secret key to sign with, given as a file (can be given multiple times)
+//! -t, --time <TIME> Chooses keys valid at the specified time and sets the signature's creation
+//! time
//!
//! ARGS:
//! <FILE> Sets the input file to use
diff --git a/tool/src/sq.rs b/tool/src/sq.rs
index d0b3420f..facbd5c3 100644
--- a/tool/src/sq.rs
+++ b/tool/src/sq.rs
@@ -18,6 +18,7 @@ use std::fs::{File, OpenOptions};
use std::io;
use std::path::{Path, PathBuf};
use std::process::exit;
+use chrono::{DateTime, offset::Utc};
extern crate sequoia_openpgp as openpgp;
extern crate sequoia_core;
@@ -219,11 +220,19 @@ fn real_main() -> Result<(), failure::Error> {
.set_transport_encryption(true),
_ => unreachable!("uses possible_values"),
};
+ let time = if let Some(time) = m.value_of("time") {
+ Some(parse_iso8601(time, chrono::NaiveTime::from_hms(0, 0, 0))
+ .context(format!("Bad value passed to --time: {:?}",
+ time))?.into())
+ } else {
+ None
+ };
commands::encrypt(&mut mapping, &mut input, &mut output,
m.occurrences_of("symmetric") as usize,
recipients, additional_certs, additional_secrets,
mode,
- m.value_of("compression").expect("has default"))?;
+ m.value_of("compression").expect("has default"),
+ time.into())?;
},
("sign", Some(m)) => {
let mut input = open_or_stdin(m.value_of("input"))?;
@@ -235,8 +244,15 @@ fn real_main() -> Result<(), failure::Error> {
let secrets = m.values_of("secret-key-file")
.map(load_certs)
.unwrap_or(Ok(vec![]))?;
+ let time = if let Some(time) = m.value_of("time") {
+ Some(parse_iso8601(time, chrono::NaiveTime::from_hms(0, 0, 0))
+ .context(format!("Bad value passed to --time: {:?}",
+ time))?.into())
+ } else {
+ None
+ };
commands::sign(&mut input, output, secrets, detached, binary,
- append, notarize, force)?;
+ append, notarize, time, force)?;
},
("verify", Some(m)) => {
let mut input = open_or_stdin(m.value_of("input"))?;
@@ -639,6 +655,78 @@ fn print_log(iter: LogIter, with_slug: bool) {
table.printstd();
}
+/// Parses the given string depicting a ISO 8601 timestamp.
+fn parse_iso8601(s: &str, pad_date_with: chrono::NaiveTime)
+ -> failure::Fallible<DateTime<Utc>>
+{
+ // If you modify this function this function, synchronize the
+ // changes with the copy in sqv.rs!
+ for f in &[
+ "%Y-%m-%dT%H:%M:%S%#z",
+ "%Y-%m-%dT%H:%M:%S",
+ "%Y-%m-%dT%H:%M%#z",
+ "%Y-%m-%dT%H:%M",
+ "%Y-%m-%dT%H%#z",
+ "%Y-%m-%dT%H",
+ "%Y%m%dT%H%M%S%#z",
+ "%Y%m%dT%H%M%S",
+ "%Y%m%dT%H%M%#z",
+ "%Y%m%dT%H%M",
+ "%Y%m%dT%H%#z",
+ "%Y%m%dT%H",
+ ] {
+ if f.ends_with("%#z") {
+ if let Ok(d) = DateTime::parse_from_str(s, *f) {
+ return Ok(d.into());
+ }
+ } else {
+ if let Ok(d) = chrono::NaiveDateTime::parse_from_str(s, *f) {
+ return Ok(DateTime::from_utc(d, Utc));
+ }
+ }
+ }
+ for f in &[
+ "%Y-%m-%d",
+ "%Y-%m",
+ "%Y-%j",
+ "%Y%m%d",
+ "%Y%m",
+ "%Y%j",
+ "%Y",
+ ] {
+ if let Ok(d) = chrono::NaiveDate::parse_from_str(s, *f) {
+ return Ok(DateTime::from_utc(d.and_time(pad_date_with), Utc));
+ }
+ }
+ Err(failure::format_err!("Malformed ISO8601 timestamp: {}", s))
+}
+
+#[test]
+fn test_parse_iso8601() {
+ let z = chrono::NaiveTime::from_hms(0, 0, 0);
+ parse_iso8601("2017-03-04T13:25:35Z", z).unwrap();
+ parse_iso8601("2017-03-04T13:25:35+08:30", z).unwrap();
+ parse_iso8601("2017-03-04T13:25:35", z).unwrap();
+ parse_iso8601("2017-03-04T13:25Z", z).unwrap();
+ parse_iso8601("2017-03-04T13:25", z).unwrap();
+ // parse_iso8601("2017-03-04T13Z", z).unwrap(); // XXX: chrono doesn't like
+ // parse_iso8601("2017-03-04T13", z).unwrap(); // ditto
+ parse_iso8601("2017-03-04", z).unwrap();
+ // parse_iso8601("2017-03", z).unwrap(); // ditto
+ parse_iso8601("2017-031", z).unwrap();
+ parse_iso8601("20170304T132535Z", z).unwrap();
+ parse_iso8601("20170304T132535+0830", z).unwrap();
+ parse_iso8601("20170304T132535", z).unwrap();
+ parse_iso8601("20170304T1325Z", z).unwrap();
+ parse_iso8601("20170304T1325", z).unwrap();
+ // parse_iso8601("20170304T13Z", z).unwrap(); // ditto
+ // parse_iso8601("20170304T13", z).unwrap(); // ditto
+ parse_iso8601("20170304", z).unwrap();
+ // parse_iso8601("201703", z).unwrap(); // ditto
+ parse_iso8601("2017031", z).unwrap();
+ // parse_iso8601("2017", z).unwrap(); // ditto
+}
+
fn main() {
if let Err(e) = real_main() {
let mut cause = e.as_fail();
diff --git a/tool/src/sq_cli.rs b/tool/src/sq_cli.rs
index 06ccde14..722ce1f2 100644
--- a/tool/src/sq_cli.rs
+++ b/tool/src/sq_cli.rs
@@ -129,7 +129,13 @@ pub fn build() -> App<'static, 'static> {
.possible_values(&["none", "pad", "zip", "zlib",
"bzip2"])
.default_value("pad")
- .help("Selects compression scheme to use")))
+ .help("Selects compression scheme to use"))
+ .arg(Arg::with_name("time").value_name("TIME")
+ .long("time")
+ .short("t")
+ .help("Chooses keys valid at the specified time and \
+ sets the signature's creation time"))
+ )
.subcommand(SubCommand::with_name("sign")
.display_order(25)
@@ -164,7 +170,12 @@ pub fn build() -> App<'static, 'static> {
.value_name("TSK-FILE")
.number_of_values(1)
.help("Secret key to sign with, given as a file \
- (can be given multiple times)")))
+ (can be given multiple times)"))
+ .arg(Arg::with_name("time").value_name("TIME")
+ .long("time")
+ .short("t")
+ .help("Chooses keys valid at the specified time and \
+ sets the signature's creation time")))
.subcommand(SubCommand::with_name("verify")
.display_order(26)
.about("Verifies a message")