diff options
author | Neal H. Walfield <neal@pep.foundation> | 2019-05-17 23:18:03 +0200 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2019-05-17 23:47:50 +0200 |
commit | 37008e9c8b925ce541412d3a57c25d310af88bb9 (patch) | |
tree | 771632b1ddd20fb84689a3fd62603b6742482b06 | |
parent | 8ae82dc89a1d3e1acabad540cbbdd9db56add29e (diff) |
tool: Support setting the expiration time on new keys
- Add an option to set the expiration time: --expiry
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | tool/Cargo.toml | 1 | ||||
-rw-r--r-- | tool/src/commands/key.rs | 91 | ||||
-rw-r--r-- | tool/src/sq.rs | 1 | ||||
-rw-r--r-- | tool/src/sq_cli.rs | 8 |
5 files changed, 102 insertions, 0 deletions
@@ -1684,6 +1684,7 @@ dependencies = [ "assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "sequoia-core 0.7.0", 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::<String>(); + 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::<String>(); + + if digits == "" { + return Err(format_err!( + "--expiry: missing count \ + (try: '2y' for 2 years)")); + } + + let count : i64 = match digits.parse::<i32>() { + 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"])) |