diff options
| -rw-r--r-- | nitrocli/src/commands.rs | 103 | 
1 files 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 <http://www.gnu.org/licenses/>. *  // ************************************************************************* +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<D, F, R>( +  pin: pinentry::PinType, +  msg: &'static str, +  data: D, +  op: F, +) -> result::Result<R, (D, Error)> +where +  F: Fn(D, &str) -> result::Result<R, (D, nitrokey::CommandError)>, +{ +  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<F>(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")]  | 
