summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2019-05-17 23:18:03 +0200
committerNeal H. Walfield <neal@pep.foundation>2019-05-17 23:47:50 +0200
commit37008e9c8b925ce541412d3a57c25d310af88bb9 (patch)
tree771632b1ddd20fb84689a3fd62603b6742482b06
parent8ae82dc89a1d3e1acabad540cbbdd9db56add29e (diff)
tool: Support setting the expiration time on new keys
- Add an option to set the expiration time: --expiry
-rw-r--r--Cargo.lock1
-rw-r--r--tool/Cargo.toml1
-rw-r--r--tool/src/commands/key.rs91
-rw-r--r--tool/src/sq.rs1
-rw-r--r--tool/src/sq_cli.rs8
5 files changed, 102 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 78c80361..a6ebb3fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"]))