From a23c692dc38fe95b1a584663166fd3c9ed251326 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Sun, 9 Apr 2017 20:21:39 -0700 Subject: Detect wrong password during 'open' command When a wrong password is entered when attempting to open the encrypted volume the nitrokey will report that in the form of an error. In such a case we should retry the operation after asking the user for the corrected password. This change implements this logic. Note that because we use gpg-agent for the PIN inquiry and because it caches passwords by default we must make sure to clear the cache before retrying. --- nitrocli/src/main.rs | 34 ++++++++++++++++++++++++++++----- nitrocli/src/nitrokey.rs | 14 ++++++++++++++ nitrocli/src/pinentry.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/nitrocli/src/main.rs b/nitrocli/src/main.rs index 92aa79e..d75fe77 100644 --- a/nitrocli/src/main.rs +++ b/nitrocli/src/main.rs @@ -230,13 +230,37 @@ fn status() -> Result<()> { /// Open the encrypted volume on the nitrokey. fn open() -> Result<()> { + type Response = nitrokey::Response; + return nitrokey_do(&|handle| { - let passphrase = pinentry::inquire_passphrase()?; - let payload = nitrokey::EnableEncryptedVolumeCommand::new(&passphrase); - let report = nitrokey::Report::from(payload); + let mut retry = 3; + loop { + let passphrase = pinentry::inquire_passphrase()?; + let payload = nitrokey::EnableEncryptedVolumeCommand::new(&passphrase); + let report = nitrokey::Report::from(payload); - transmit::<_, nitrokey::EmptyPayload>(handle, &report)?; - return Ok(()); + let report = transmit::<_, nitrokey::EmptyPayload>(handle, &report)?; + let response = AsRef::::as_ref(&report.data); + let status = response.data.storage_status; + + if status == nitrokey::StorageStatus::WrongPassword { + pinentry::clear_passphrase()?; + retry -= 1; + + if retry > 0 { + println!("Wrong password, please reenter"); + continue; + } + let error = "Opening encrypted volume failed: Wrong password"; + return Err(Error::Error(error.to_string())); + } + if status != nitrokey::StorageStatus::Okay && status != nitrokey::StorageStatus::Idle { + let status = format!("{:?}", status); + let error = format!("Opening encrypted volume failed: {}", status); + return Err(Error::Error(error)); + } + return Ok(()); + } }); } diff --git a/nitrocli/src/nitrokey.rs b/nitrocli/src/nitrokey.rs index d1d6c72..00a681c 100644 --- a/nitrocli/src/nitrokey.rs +++ b/nitrocli/src/nitrokey.rs @@ -241,6 +241,9 @@ pub enum CommandStatus { #[allow(dead_code)] +#[derive(Copy)] +#[derive(Clone)] +#[derive(Debug)] #[derive(PartialEq)] #[repr(u8)] pub enum StorageStatus { @@ -272,6 +275,17 @@ impl

AsRef<[u8]> for Response

{ } +#[repr(packed)] +pub struct StorageResponse { + pub padding1: [u8; 13], + pub command_counter: u8, + pub last_storage_command: Command, + pub storage_status: StorageStatus, + pub progress: u8, + pub padding2: [u8; 2], +} + + #[repr(packed)] pub struct DeviceStatusResponse { pub padding0: [u8; 22], diff --git a/nitrocli/src/pinentry.rs b/nitrocli/src/pinentry.rs index 3fb533a..8de788f 100644 --- a/nitrocli/src/pinentry.rs +++ b/nitrocli/src/pinentry.rs @@ -69,6 +69,29 @@ pub fn inquire_passphrase() -> Result, Error> { } +fn parse_pinentry_response(response: Vec) -> Result<(), Error> { + let string = String::from_utf8(response)?; + let lines: Vec<&str> = string.lines().collect(); + + if lines.len() == 1 && lines[0] == "OK" { + // We got the only valid answer we accept. + return Ok(()); + } + return Err(Error::Error("Unexpected response: ".to_string() + &string)); +} + + +/// Clear the cached passphrase. +pub fn clear_passphrase() -> Result<(), Error> { + let command = "CLEAR_PASSPHRASE ".to_string() + CACHE_ID; + let output = process::Command::new("gpg-connect-agent").arg(command) + .arg("/bye") + .output()?; + + return parse_pinentry_response(output.stdout); +} + + #[cfg(test)] mod tests { use super::*; @@ -109,4 +132,30 @@ mod tests { panic!("Unexpected result"); } } + + #[test] + fn parse_pinentry_response_ok() { + let response = "OK\n".to_string().into_bytes(); + assert!(parse_pinentry_response(response).is_ok()) + } + + #[test] + fn parse_pinentry_response_ok_no_newline() { + let response = "OK".to_string().into_bytes(); + assert!(parse_pinentry_response(response).is_ok()) + } + + #[test] + fn parse_pinentry_response_unexpected() { + let response = "ERR 42"; + let expected = "Unexpected response: ".to_string() + response; + + let error = parse_pinentry_response(response.to_string().into_bytes()); + + if let Error::Error(ref e) = error.err().unwrap() { + assert_eq!(e, &expected); + } else { + panic!("Unexpected result"); + } + } } -- cgit v1.2.3