From 88e3eb6937b59f44a2c2ba5a8ecbede1c6628718 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Thu, 12 Dec 2024 22:07:35 +0800 Subject: [PATCH] feat: v0.1.0 --- .gitignore | 1 + Cargo.toml | 15 ++++++++ src/lib.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore index 3bf25c0..409abaa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea/ # ---> Rust # Generated by Cargo # will have compiled files and executables diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ec8e8b9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pinentry-util" +version = "0.1.0" +edition = "2021" +authors = ["Hatter Jiang"] +repository = "https://git.hatter.ink/hatter/pinentry-util" +description = "pinentry util" +license = "MIT" +keywords = ["pinentry"] + +[dependencies] +pinentry = "0.6.0" +rpassword = "7.3.1" +secrecy = "0.10.3" +zeroize = "1.8.1" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b414efc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,100 @@ +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() +}