From 37008e9c8b925ce541412d3a57c25d310af88bb9 Mon Sep 17 00:00:00 2001 From: "Neal H. Walfield" Date: Fri, 17 May 2019 23:18:03 +0200 Subject: tool: Support setting the expiration time on new keys - Add an option to set the expiration time: --expiry --- tool/Cargo.toml | 1 + tool/src/commands/key.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ tool/src/sq.rs | 1 + tool/src/sq_cli.rs | 8 +++++ 4 files changed, 101 insertions(+) (limited to 'tool') diff --git a/tool/Cargo.toml b/tool/Cargo.toml index ee6c9609..aee63a48 100644 --- a/tool/Cargo.toml +++ b/tool/Cargo.toml @@ -27,6 +27,7 @@ sequoia-net = { path = "../net", version = "0.7" } sequoia-store = { path = "../store", version = "0.7" } clap = "2.32.0" failure = "0.1.2" +itertools = "0.8" prettytable-rs = "0.8.0" rpassword = "3.0" tempfile = "3.0.4" diff --git a/tool/src/commands/key.rs b/tool/src/commands/key.rs index 43dde438..90f7284c 100644 --- a/tool/src/commands/key.rs +++ b/tool/src/commands/key.rs @@ -1,5 +1,7 @@ use failure; +use failure::Fail; use clap::ArgMatches; +use itertools::Itertools; use openpgp::tpk::{TPKBuilder, CipherSuite}; use openpgp::packet::KeyFlags; @@ -19,6 +21,95 @@ pub fn generate(m: &ArgMatches, force: bool) -> failure::Fallible<()> { } } + // Expiration. + const SECONDS_IN_DAY : i64 = 24 * 60 * 60; + const SECONDS_IN_YEAR : i64 = + // Average number of days in a year. + (365.2422222 * SECONDS_IN_DAY as f64) as i64; + + let even_off = |s| { + if s < 7 * SECONDS_IN_DAY { + // Don't round down, too small. + s + } else { + s - (s % SECONDS_IN_DAY) + } + }; + + match m.value_of("expiry") { + Some(expiry) if expiry == "never" => + builder = builder.set_expiration(None), + + Some(expiry) => { + let mut expiry = expiry.chars().peekable(); + + let _ = expiry.by_ref() + .peeking_take_while(|c| c.is_whitespace()) + .for_each(|_| ()); + let digits = expiry.by_ref() + .peeking_take_while(|c| { + *c == '+' || *c == '-' || c.is_digit(10) + }).collect::(); + let _ = expiry.by_ref() + .peeking_take_while(|c| c.is_whitespace()) + .for_each(|_| ()); + let suffix = expiry.next(); + let _ = expiry.by_ref() + .peeking_take_while(|c| c.is_whitespace()) + .for_each(|_| ()); + let junk = expiry.collect::(); + + if digits == "" { + return Err(format_err!( + "--expiry: missing count \ + (try: '2y' for 2 years)")); + } + + let count : i64 = match digits.parse::() { + Ok(count) if count < 0 => + return Err(format_err!( + "--expiry: Expiration can't be in the past")), + Ok(count) => count as i64, + Err(err) => + return Err(err.context( + "--expiry: count is out of range").into()), + }; + + let factor = match suffix { + Some('y') | Some('Y') => SECONDS_IN_YEAR, + Some('m') | Some('M') => SECONDS_IN_YEAR / 12, + Some('w') | Some('W') => 7 * SECONDS_IN_DAY, + Some('d') | Some('D') => SECONDS_IN_DAY, + None => + return Err(format_err!( + "--expiry: missing suffix \ + (try: '{}y', '{}m', '{}w' or '{}d' instead)", + digits, digits, digits, digits)), + Some(suffix) => + return Err(format_err!( + "--expiry: invalid suffix '{}' \ + (try: '{}y', '{}m', '{}w' or '{}d' instead)", + suffix, digits, digits, digits, digits)), + }; + + if junk != "" { + return Err(format_err!( + "--expiry: contains trailing junk ('{:?}') \ + (try: '{}{}')", + junk, count, factor)); + } + + builder = builder.set_expiration( + Some(time::Duration::seconds(even_off(count * factor)))); + } + + // Not specified. Use the default. + None => { + builder = builder.set_expiration( + Some(time::Duration::seconds(even_off(3 * SECONDS_IN_YEAR)))); + } + }; + // Cipher Suite match m.value_of("cipher-suite") { None | Some("rsa3k") => { diff --git a/tool/src/sq.rs b/tool/src/sq.rs index b18bada4..83235bb7 100644 --- a/tool/src/sq.rs +++ b/tool/src/sq.rs @@ -8,6 +8,7 @@ extern crate prettytable; extern crate rpassword; extern crate tempfile; extern crate time; +extern crate itertools; use failure::ResultExt; use prettytable::{Table, Cell, Row}; diff --git a/tool/src/sq_cli.rs b/tool/src/sq_cli.rs index 9c5e50b9..51f78468 100644 --- a/tool/src/sq_cli.rs +++ b/tool/src/sq_cli.rs @@ -356,6 +356,14 @@ pub fn build() -> App<'static, 'static> { .long("with-password") .help("Prompt for a password to protect the \ generated key with.")) + .arg(Arg::with_name("expiry") + .value_name("EXPIRY") + .long("expiry") + // Catch negative numbers. + .allow_hyphen_values(true) + .help("When the key should expire. \ + Either 'N[ymwd]', for N years, months, \ + weeks, or days, or 'never'.")) .group(ArgGroup::with_name("cap-sign") .args(&["can-sign", "cannot-sign"])) -- cgit v1.2.3