pinentry_util/
lib.rs

1use pinentry::PassphraseInput;
2use secrecy::ExposeSecret;
3use std::fmt::{Debug, Display, Formatter};
4use std::{env, fs};
5use zeroize::Zeroize;
6const PIN_ENTRY_ENV: &str = "PIN_ENTRY_CMD";
7const PIN_ENTRY_1: &str = "/usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac";
8const PIN_ENTRY_DEFAULT: &str = "pinentry";
9
10pub struct Pin {
11    pin: String,
12}
13
14impl Drop for Pin {
15    fn drop(&mut self) {
16        self.pin.zeroize();
17    }
18}
19
20impl Pin {
21    fn from(pin: String) -> Self {
22        Self { pin }
23    }
24
25    pub fn get_pin(&self) -> &str {
26        &self.pin
27    }
28}
29
30impl Debug for Pin {
31    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32        f.debug_struct("Pin").field("pin", &"******").finish()
33    }
34}
35
36#[derive(Debug)]
37pub enum PinError {
38    Cancel,
39    Other(String),
40}
41
42impl Display for PinError {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        match self {
45            PinError::Cancel => f.write_str("PinError:Cancel"),
46            PinError::Other(other) => f.write_str(&format!("PinError:Other({})", other)),
47        }
48    }
49}
50
51impl PinError {
52    fn from(err: String) -> Self {
53        if err.contains("Operation cancelled") {
54            return Self::Cancel;
55        }
56        Self::Other(err)
57    }
58}
59
60pub fn read_pin_default() -> Result<Pin, PinError> {
61    read_pin(None::<&str>, None::<&str>)
62}
63
64pub fn read_pin<S, T>(description: Option<S>, prompt: Option<T>) -> Result<Pin, PinError>
65where
66    S: AsRef<str>,
67    T: AsRef<str>,
68{
69    let pin_entry = get_pin_entry();
70    let description = description
71        .as_ref()
72        .map(|a| a.as_ref())
73        .unwrap_or("Please input PIN");
74    let prompt = prompt.as_ref().map(|a| a.as_ref()).unwrap_or("PIN: ");
75
76    if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
77        let secret = input
78            .with_description(&format!("{}.", description))
79            .with_prompt(prompt)
80            .interact();
81
82        match secret {
83            Ok(secret_string) => Ok(Pin::from(secret_string.expose_secret().to_string())),
84            Err(e) => Err(PinError::from(format!("{}", e))),
85        }
86    } else {
87        match rpassword::prompt_password(format!("{}: ", description)) {
88            Ok(pin) => Ok(Pin::from(pin)),
89            Err(e) => Err(PinError::from(format!("{}", e))),
90        }
91    }
92}
93
94fn get_pin_entry() -> String {
95    if let Ok(pin_entry) = env::var(PIN_ENTRY_ENV) {
96        return pin_entry;
97    }
98    if let Ok(m) = fs::metadata(PIN_ENTRY_1) {
99        if m.is_file() {
100            return PIN_ENTRY_1.to_string();
101        }
102    }
103    PIN_ENTRY_DEFAULT.to_string()
104}