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"); +    } +  } +} | 
