aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nitrocli/src/error.rs20
-rw-r--r--nitrocli/src/pinentry.rs116
2 files changed, 136 insertions, 0 deletions
diff --git a/nitrocli/src/error.rs b/nitrocli/src/error.rs
index 65992f0..a88b5a7 100644
--- a/nitrocli/src/error.rs
+++ b/nitrocli/src/error.rs
@@ -19,11 +19,15 @@
use libhid;
use std::fmt;
+use std::io;
+use std::string;
#[derive(Debug)]
pub enum Error {
HidError(libhid::Error),
+ IoError(io::Error),
+ Utf8Error(string::FromUtf8Error),
Error(String),
}
@@ -35,10 +39,26 @@ impl From<libhid::Error> for Error {
}
+impl From<io::Error> for Error {
+ fn from(e: io::Error) -> Error {
+ return Error::IoError(e);
+ }
+}
+
+
+impl From<string::FromUtf8Error> for Error {
+ fn from(e: string::FromUtf8Error) -> Error {
+ return Error::Utf8Error(e);
+ }
+}
+
+
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *&self {
&Error::HidError(ref e) => return write!(f, "hidapi error: {}", e),
+ &Error::Utf8Error(_) => return write!(f, "Encountered UTF-8 conversion error"),
+ &Error::IoError(ref e) => return write!(f, "IO error: {}", e.get_ref().unwrap()),
&Error::Error(ref e) => return write!(f, "{}", e),
}
}
diff --git a/nitrocli/src/pinentry.rs b/nitrocli/src/pinentry.rs
new file mode 100644
index 0000000..eabf598
--- /dev/null
+++ b/nitrocli/src/pinentry.rs
@@ -0,0 +1,116 @@
+// pinentry.rs
+
+// *************************************************************************
+// * Copyright (C) 2017 Daniel Mueller (deso@posteo.net) *
+// * *
+// * This program is free software: you can redistribute it and/or modify *
+// * it under the terms of the GNU General Public License as published by *
+// * the Free Software Foundation, either version 3 of the License, or *
+// * (at your option) any later version. *
+// * *
+// * This program is distributed in the hope that it will be useful, *
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+// * GNU General Public License for more details. *
+// * *
+// * You should have received a copy of the GNU General Public License *
+// * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+// *************************************************************************
+
+use error::Error as Error;
+use std::process;
+
+
+const CACHE_ID: &'static str = "nitrokey";
+
+
+fn parse_pinentry_passphrase(response: Vec<u8>) -> Result<Vec<u8>, Error> {
+ let string = String::from_utf8(response)?;
+ let lines: Vec<&str> = string.lines().collect();
+
+ // We expect the response to be of the form:
+ // > D passphrase
+ // > OK
+ // or potentially:
+ // > ERR 83886179 Operation cancelled <Pinentry>
+ if lines.len() == 2 && lines[1] == "OK" && lines[0].starts_with("D ") {
+ // We got the only valid answer we accept.
+ let (_, pass) = lines[0].split_at(2);
+ return Ok(pass.to_string().into_bytes());
+ }
+
+ // Check if we are dealing with a special "ERR " line and report that
+ // specially.
+ if lines.len() >= 1 && lines[0].starts_with("ERR ") {
+ let (_, error) = lines[0].split_at(4);
+ return Err(Error::Error(error.to_string()));
+ }
+ return Err(Error::Error("Unexpected response: ".to_string() + &string));
+}
+
+
+pub fn inquire_passphrase() -> Result<Vec<u8>, Error> {
+ const PINENTRY_DESCR: &'static str = "+";
+ const PINENTRY_TITLE: &'static str = "Please+enter+user+PIN";
+ const PINENTRY_PASSWD: &'static str = "PIN";
+
+ let args = vec![CACHE_ID,
+ PINENTRY_DESCR,
+ PINENTRY_PASSWD,
+ PINENTRY_TITLE].join(" ");
+ let command = "GET_PASSPHRASE --data ".to_string() + &args;
+ // We could also use the --data parameter here to have a more direct
+ // representation of the passphrase but the resulting response was
+ // considered more difficult to parse overall. It appears an error
+ // reported for the GET_PASSPHRASE command does not actually cause
+ // gpg-connect-agent to exit with a non-zero error code, we have to
+ // evaluate the output to determine success/failure.
+ let output = process::Command::new("gpg-connect-agent")
+ .arg(command)
+ .arg("/bye")
+ .output()?;
+ return parse_pinentry_passphrase(output.stdout);
+}
+
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn parse_pinentry_passphrase_good() {
+ let response = "D passphrase\nOK\n".to_string().into_bytes();
+ let expected = "passphrase".to_string().into_bytes();
+
+ assert_eq!(parse_pinentry_passphrase(response).unwrap(), expected)
+ }
+
+ #[test]
+ fn parse_pinentry_passphrase_error() {
+ let error = "83886179 Operation cancelled";
+ let response = "ERR ".to_string() + error + "\n";
+ let expected = error;
+
+ let error = parse_pinentry_passphrase(response.to_string().into_bytes());
+
+ if let Error::Error(ref e) = error.err().unwrap() {
+ assert_eq!(e, &expected);
+ } else {
+ panic!("Unexpected result");
+ }
+ }
+
+ #[test]
+ fn parse_pinentry_passphrase_unexpected() {
+ let response = "foobar\n";
+ let expected = "Unexpected response: ".to_string() + response;
+
+ let error = parse_pinentry_passphrase(response.to_string().into_bytes());
+
+ if let Error::Error(ref e) = error.err().unwrap() {
+ assert_eq!(e, &expected);
+ } else {
+ panic!("Unexpected result");
+ }
+ }
+}