Files
pinentry-util/src/lib.rs
2024-12-12 22:07:35 +08:00

101 lines
2.5 KiB
Rust

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<Pin, PinError> {
read_pin(None::<&str>, None::<&str>)
}
pub fn read_pin<S, T>(description: Option<S>, prompt: Option<T>) -> Result<Pin, PinError>
where
S: AsRef<str>,
T: AsRef<str>,
{
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()
}