From 27ed7f31128cf17dee1ab8e7cfa49f45ca123aec Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Mon, 24 Dec 2018 17:49:38 -0800 Subject: Refactor the pinentry call into new helper functions Currently, `open` directly calls the `pinentry` module and loops until the user entered a correct passphrase or the retry limit is reached. This patch moves the pinentry call and the loop into the `try_with_passphrase_and_data` function. This function queries a passphrase of a given type and executes a function with that passphrase. This function has a data argument and may return data that is passed to the next call of the function (if it failed). This data-passing mechanism is required for the `nitrokey` authentication functions: These functions take ownership of the device and either return an authenticated device after successful authentication, or an error including the unauthenticated device if the authentication failed. This patch enables the usage of these functions in future patches. --- nitrocli/src/commands.rs | 103 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 4f04684..269cafc 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -17,6 +17,8 @@ // * along with this program. If not, see . * // ************************************************************************* +use std::result; + use crate::error::Error; use crate::pinentry; use crate::Result; @@ -47,6 +49,78 @@ fn get_volume_status(status: &nitrokey::VolumeStatus) -> &'static str { } } +/// Try to execute the given function with a passphrase queried using pinentry. +/// +/// This function will query the passphrase 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 returns a `CommandError::WrongPassword`, the user will be asked +/// again to enter the passphrase. Otherwise, this function returns an +/// error containing the given error message. The user will have at +/// most three tries to get the passphrase right. +/// +/// The data argument can be used to pass on data between the tries. At +/// the first try, this function will call `op` with `data`. At the +/// second or third try, it will call `op` with the data returned by the +/// previous call to `op`. +fn try_with_passphrase_and_data( + pin: pinentry::PinType, + msg: &'static str, + data: D, + op: F, +) -> result::Result +where + F: Fn(D, &str) -> result::Result, +{ + let mut data = data; + let mut retry = 3; + let mut error_msg: Option<&str> = None; + loop { + let passphrase = match pinentry::inquire_passphrase(pin, error_msg) { + Ok(passphrase) => passphrase, + Err(err) => return Err((data, err)), + }; + let passphrase = match String::from_utf8(passphrase) { + Ok(passphrase) => passphrase, + Err(err) => return Err((data, Error::from(err))), + }; + match op(data, &passphrase) { + Ok(result) => return Ok(result), + Err((new_data, err)) => match err { + nitrokey::CommandError::WrongPassword => { + if let Err(err) = pinentry::clear_passphrase(pin) { + return Err((new_data, err)); + } + retry -= 1; + + if retry > 0 { + error_msg = Some("Wrong password, please reenter"); + data = new_data; + continue; + } + let error = format!("{}: Wrong password", msg); + return Err((new_data, Error::Error(error))); + } + err => return Err((new_data, get_error(msg, &err))), + }, + }; + } +} + +/// Try to execute the given function with a passphrase queried using pinentry. +/// +/// This function behaves exactly as `try_with_passphrase_and_data`, but +/// it refrains from passing any data to it. +fn try_with_passphrase(pin: pinentry::PinType, msg: &'static str, op: F) -> Result<()> +where + F: Fn(&str) -> result::Result<(), nitrokey::CommandError>, +{ + try_with_passphrase_and_data(pin, msg, (), |data, passphrase| { + op(passphrase).map_err(|err| (data, err)) + }) + .map_err(|(_data, err)| err) +} + /// Pretty print the response of a status command. fn print_status(status: &nitrokey::StorageStatus) { // We omit displaying information about the smartcard here as this @@ -97,30 +171,11 @@ pub fn status() -> Result<()> { /// Open the encrypted volume on the nitrokey. pub fn open() -> Result<()> { let device = get_storage_device()?; - - let mut retry = 3; - let mut error_msg: Option<&str> = None; - loop { - let passphrase = pinentry::inquire_passphrase(PIN_TYPE, error_msg)?; - let passphrase = String::from_utf8(passphrase)?; - match device.enable_encrypted_volume(&passphrase) { - Ok(()) => return Ok(()), - Err(err) => match err { - nitrokey::CommandError::WrongPassword => { - pinentry::clear_passphrase(PIN_TYPE)?; - retry -= 1; - - if retry > 0 { - error_msg = Some("Wrong password, please reenter"); - continue; - } - let error = "Opening encrypted volume failed: Wrong password"; - return Err(Error::Error(error.to_string())); - } - err => return Err(get_error("Opening encrypted volume failed", &err)), - }, - }; - } + try_with_passphrase( + pinentry::PinType::User, + "Opening encrypted volume failed", + |passphrase| device.enable_encrypted_volume(&passphrase), + ) } #[link(name = "c")] -- cgit v1.2.3