diff options
author | Neal H. Walfield <neal@pep.foundation> | 2020-01-17 16:01:33 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2020-01-17 16:17:51 +0100 |
commit | 3dc2d24bc3920d0216ff7f19e01e4e073a292a24 (patch) | |
tree | 551997319c32bc9c684401a72301dfe41d85bd6c | |
parent | 7cc22083ffc9874c39c957464bde9662e2fe8317 (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.
-rw-r--r-- | sqv/src/sqv.rs | 2 | ||||
-rw-r--r-- | tool/src/commands/mod.rs | 14 | ||||
-rw-r--r-- | tool/src/commands/sign.rs | 23 | ||||
-rw-r--r-- | tool/src/sq-usage.rs | 4 | ||||
-rw-r--r-- | tool/src/sq.rs | 92 | ||||
-rw-r--r-- | tool/src/sq_cli.rs | 15 |
6 files changed, 134 insertions, 16 deletions
diff --git a/sqv/src/sqv.rs b/sqv/src/sqv.rs index afa27833..73ff0abb 100644 --- a/sqv/src/sqv.rs +++ b/sqv/src/sqv.rs @@ -377,6 +377,8 @@ fn main() { 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", 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") |