use pinentry::PassphraseInput; use secrecy::ExposeSecret; use std::fmt::{Debug, Display, Formatter}; use std::{env, fs}; use zeroize::Zeroize; const PIN_ENTRY_ENV: &str = "PIN_ENTRY_CMD"; const PIN_ENTRY_1: &str = "/usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac"; const PIN_ENTRY_DEFAULT: &str = "pinentry"; pub struct Pin { pin: String, } impl Drop for Pin { fn drop(&mut self) { self.pin.zeroize(); } } impl Pin { fn from(pin: String) -> Self { Self { pin } } } impl Debug for Pin { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("Pin").field("pin", &"******").finish() } } #[derive(Debug)] pub enum PinError { Cancel, Other(String), } impl Display for PinError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { PinError::Cancel => f.write_str("PinError:Cancel"), PinError::Other(other) => f.write_str(&format!("PinError:Other({})", other)), } } } impl PinError { fn from(err: String) -> Self { if err.contains("Operation cancelled") { return Self::Cancel; } Self::Other(err) } } pub fn read_pin_default() -> Result { read_pin(None::<&str>, None::<&str>) } pub fn read_pin(description: Option, prompt: Option) -> Result where S: AsRef, T: AsRef, { let pin_entry = get_pin_entry(); let description = description .as_ref() .map(|a| a.as_ref()) .unwrap_or("Please input PIN"); let prompt = prompt.as_ref().map(|a| a.as_ref()).unwrap_or("PIN: "); if let Some(mut input) = PassphraseInput::with_binary(pin_entry) { let secret = input .with_description(&format!("{}.", description)) .with_prompt(prompt) .interact(); match secret { Ok(secret_string) => Ok(Pin::from(secret_string.expose_secret().to_string())), Err(e) => Err(PinError::from(format!("{}", e))), } } else { match rpassword::prompt_password(format!("{}: ", description)) { Ok(pin) => Ok(Pin::from(pin)), Err(e) => Err(PinError::from(format!("{}", e))), } } } fn get_pin_entry() -> String { if let Ok(pin_entry) = env::var(PIN_ENTRY_ENV) { return pin_entry; } if let Ok(m) = fs::metadata(PIN_ENTRY_1) { if m.is_file() { return PIN_ENTRY_1.to_string(); } } PIN_ENTRY_DEFAULT.to_string() }