1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
// 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;
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");
}
}
}
|