diff options
-rw-r--r-- | nitrocli/src/error.rs | 20 | ||||
-rw-r--r-- | nitrocli/src/pinentry.rs | 116 |
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"); + } + } +} |