aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mueller <deso@posteo.net>2017-04-09 20:21:39 -0700
committerDaniel Mueller <deso@posteo.net>2017-04-09 20:21:39 -0700
commita23c692dc38fe95b1a584663166fd3c9ed251326 (patch)
tree21f2a08703245b0022da01d7ab6cc312bacfb584
parentf94d04578f44fc79212550203838f7c78e1ac414 (diff)
downloadnitrocli-a23c692dc38fe95b1a584663166fd3c9ed251326.tar.gz
nitrocli-a23c692dc38fe95b1a584663166fd3c9ed251326.tar.bz2
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.
-rw-r--r--nitrocli/src/main.rs34
-rw-r--r--nitrocli/src/nitrokey.rs14
-rw-r--r--nitrocli/src/pinentry.rs49
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<nitrokey::StorageResponse>;
+
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::<Response>::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 {
@@ -273,6 +276,17 @@ impl<P> AsRef<[u8]> for Response<P> {
#[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],
pub magic: u16,
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<Vec<u8>, Error> {
}
+fn parse_pinentry_response(response: Vec<u8>) -> 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");
+ }
+ }
}