From a57449dcd2abe1fa5dae195470fccc9a9a398e04 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Sun, 26 May 2019 22:11:36 -0700 Subject: Allow for disabling of secret caching So far we have cached secrets in gpg-agent(1) whenever that made sense to do (i.e., for the two PINs in most contexts but not for passwords). While there is reason to believe that such caching is desired by the majority of users, not everybody has a use for it. To give users an opportunity to opt out of such caching, this change introduces a new environment variable, NITROCLI_NO_CACHE, that, when present in the environment, instructs the program to bypass the cache for all operations that require a secret and to instead inquire such secrets each time they are needed. --- nitrocli/CHANGELOG.md | 2 ++ nitrocli/doc/nitrocli.1 | 9 ++++++++- nitrocli/doc/nitrocli.1.pdf | Bin 17751 -> 18096 bytes nitrocli/src/args.rs | 2 ++ nitrocli/src/commands.rs | 13 +++++++------ nitrocli/src/main.rs | 4 ++++ nitrocli/src/pinentry.rs | 19 ++++++++++++++----- nitrocli/src/tests/mod.rs | 1 + 8 files changed, 38 insertions(+), 12 deletions(-) diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index 0170fb7..fe3ac74 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -3,6 +3,8 @@ Unreleased - Added the `reset` command to perform a factory reset - Added the `-V`/`--version` option to print the program's version - Check the status of a PWS slot before accessing it in `pws get` +- Added `NITROCLI_NO_CACHE` environment variable to bypass caching of + secrets - Bumped `libc` dependency to `0.2.55` - Bumped `cc` dependency to `1.0.37` diff --git a/nitrocli/doc/nitrocli.1 b/nitrocli/doc/nitrocli.1 index 66d73f9..9029335 100644 --- a/nitrocli/doc/nitrocli.1 +++ b/nitrocli/doc/nitrocli.1 @@ -1,4 +1,4 @@ -.TH NITROCLI 1 2019-01-21 +.TH NITROCLI 1 2019-05-26 .SH NAME nitrocli \- access Nitrokey devices .SH SYNOPSIS @@ -279,6 +279,13 @@ for the \fBuser\fR type. .TP .B NITROCLI_PASSWORD A password used by commands that require one (e.g., \fBstorage hidden open\fR). +.TP +.B NITROCLI_NO_CACHE +If this variable is present in the environment, do not cache any inquired +secrets using \fBgpg\-agent\fR(1) but ask for them each time they are needed. +Note that this variable does not cause any cached secrets to be cleared. If a +secret is already in the cache it will be ignored, but left otherwise untouched. +Use the \fBpin clear\fR command to clear secrets from the cache. .SH EXAMPLES .SS Storage diff --git a/nitrocli/doc/nitrocli.1.pdf b/nitrocli/doc/nitrocli.1.pdf index ff72f94..fa4e4bc 100644 Binary files a/nitrocli/doc/nitrocli.1.pdf and b/nitrocli/doc/nitrocli.1.pdf differ diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 82e9c2a..10a097e 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -91,6 +91,7 @@ pub struct ExecCtx<'io> { pub new_admin_pin: Option, pub new_user_pin: Option, pub password: Option, + pub no_cache: bool, pub verbosity: u64, } @@ -929,6 +930,7 @@ pub(crate) fn handle_arguments(ctx: &mut RunCtx<'_>, args: Vec) -> Resul new_admin_pin: ctx.new_admin_pin.take(), new_user_pin: ctx.new_user_pin.take(), password: ctx.password.take(), + no_cache: ctx.no_cache, verbosity, }; command.execute(&mut ctx, subargs) diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 82d6240..a81859c 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -166,7 +166,7 @@ fn get_volume_status(status: &nitrokey::VolumeStatus) -> &'static str { /// /// This function will query the pin of the given type from the user /// using pinentry. It will then execute the given function. If this -/// function returns a result, the result will be passed it on. If it +/// function returns a result, the result will be passed on. If it /// returns a `CommandError::WrongPassword`, the user will be asked /// again to enter the pin. Otherwise, this function returns an error /// containing the given error message. The user will have at most @@ -177,6 +177,7 @@ fn get_volume_status(status: &nitrokey::VolumeStatus) -> &'static str { /// second or third try, it will call `op` with the data returned by the /// previous call to `op`. fn try_with_pin_and_data_with_pinentry( + ctx: &mut args::ExecCtx<'_>, pin_entry: &pinentry::PinEntry, msg: &'static str, data: D, @@ -189,7 +190,7 @@ where let mut retry = 3; let mut error_msg = None; loop { - let pin = pinentry::inquire(pin_entry, pinentry::Mode::Query, error_msg)?; + let pin = pinentry::inquire(ctx, pin_entry, pinentry::Mode::Query, error_msg)?; match op(data, &pin) { Ok(result) => return Ok(result), Err((new_data, err)) => match err { @@ -235,7 +236,7 @@ where })?; op(data, &pin).map_err(|(_, err)| get_error(msg, err)) } else { - try_with_pin_and_data_with_pinentry(pin_entry, msg, data, op) + try_with_pin_and_data_with_pinentry(ctx, pin_entry, msg, data, op) } } @@ -359,7 +360,7 @@ pub fn storage_hidden_create( .ok_or_else(|| Error::from("Failed to read password: invalid Unicode data found")) .map(ToOwned::to_owned) } else { - pinentry::choose(&pwd_entry) + pinentry::choose(ctx, &pwd_entry) }?; device @@ -377,7 +378,7 @@ pub fn storage_hidden_open(ctx: &mut args::ExecCtx<'_>) -> Result<()> { .ok_or_else(|| Error::from("Failed to read password: invalid Unicode data found")) .map(ToOwned::to_owned) } else { - pinentry::inquire(&pwd_entry, pinentry::Mode::Query, None) + pinentry::inquire(ctx, &pwd_entry, pinentry::Mode::Query, None) }?; // We may forcefully close an encrypted volume, if active, so be sure @@ -706,7 +707,7 @@ fn choose_pin( .ok_or_else(|| Error::from("Failed to read PIN: invalid Unicode data found")) .map(ToOwned::to_owned) } else { - pinentry::choose(pin_entry) + pinentry::choose(ctx, pin_entry) } } diff --git a/nitrocli/src/main.rs b/nitrocli/src/main.rs index 1629167..5cb3faf 100644 --- a/nitrocli/src/main.rs +++ b/nitrocli/src/main.rs @@ -102,6 +102,7 @@ const NITROCLI_USER_PIN: &str = "NITROCLI_USER_PIN"; const NITROCLI_NEW_ADMIN_PIN: &str = "NITROCLI_NEW_ADMIN_PIN"; const NITROCLI_NEW_USER_PIN: &str = "NITROCLI_NEW_USER_PIN"; const NITROCLI_PASSWORD: &str = "NITROCLI_PASSWORD"; +const NITROCLI_NO_CACHE: &str = "NITROCLI_NO_CACHE"; /// The context used when running the program. pub(crate) struct RunCtx<'io> { @@ -123,6 +124,8 @@ pub(crate) struct RunCtx<'io> { pub new_user_pin: Option, /// A password used by some commands, if provided through an environment variable. pub password: Option, + /// Whether to bypass the cache for all secrets or not. + pub no_cache: bool, } fn run<'ctx, 'io: 'ctx>(ctx: &'ctx mut RunCtx<'io>, args: Vec) -> i32 { @@ -157,6 +160,7 @@ fn main() { new_admin_pin: env::var_os(NITROCLI_NEW_ADMIN_PIN), new_user_pin: env::var_os(NITROCLI_NEW_USER_PIN), password: env::var_os(NITROCLI_PASSWORD), + no_cache: env::var_os(NITROCLI_NO_CACHE).is_some(), }; let rc = run(ctx, args); diff --git a/nitrocli/src/pinentry.rs b/nitrocli/src/pinentry.rs index 7bba6b9..d8a77d4 100644 --- a/nitrocli/src/pinentry.rs +++ b/nitrocli/src/pinentry.rs @@ -22,6 +22,7 @@ use std::fmt; use std::process; use std::str; +use crate::args; use crate::error::Error; type CowStr = borrow::Cow<'static, str>; @@ -223,19 +224,27 @@ where /// Inquire a secret from the user. /// /// This function inquires a secret from the user or returns a cached -/// entry, if available. If an error message is set, it is displayed in +/// entry, if available (and if caching is not disabled for the given +/// execution context). If an error message is set, it is displayed in /// the entry dialog. The mode describes the context of the pinentry /// dialog. It is used to choose an appropriate description and to /// decide whether a quality bar is shown in the dialog. -pub fn inquire(entry: &E, mode: Mode, error_msg: Option<&str>) -> crate::Result +pub fn inquire( + ctx: &mut args::ExecCtx<'_>, + entry: &E, + mode: Mode, + error_msg: Option<&str>, +) -> crate::Result where E: SecretEntry, { let cache_id = entry .cache_id() + .and_then(|id| if ctx.no_cache { None } else { Some(id) }) // "X" is a sentinel value indicating that no caching is desired. .unwrap_or_else(|| "X".into()) .into(); + let error_msg = error_msg .map(|msg| msg.replace(" ", "+")) .unwrap_or_else(|| String::from("+")); @@ -272,16 +281,16 @@ where } } -pub fn choose(entry: &E) -> crate::Result +pub fn choose(ctx: &mut args::ExecCtx<'_>, entry: &E) -> crate::Result where E: SecretEntry, { clear(entry)?; - let chosen = inquire(entry, Mode::Choose, None)?; + let chosen = inquire(ctx, entry, Mode::Choose, None)?; clear(entry)?; check(entry, &chosen)?; - let confirmed = inquire(entry, Mode::Confirm, None)?; + let confirmed = inquire(ctx, entry, Mode::Confirm, None)?; clear(entry)?; if chosen != confirmed { diff --git a/nitrocli/src/tests/mod.rs b/nitrocli/src/tests/mod.rs index e2e6ce0..b1e1618 100644 --- a/nitrocli/src/tests/mod.rs +++ b/nitrocli/src/tests/mod.rs @@ -157,6 +157,7 @@ impl Nitrocli { new_admin_pin: self.new_admin_pin.clone(), new_user_pin: self.new_user_pin.clone(), password: self.password.clone(), + no_cache: true, }; (f(ctx, args), stdout, stderr) -- cgit v1.2.1