diff --git a/Cargo.lock b/Cargo.lock index 0733e08..f8d067e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.9.6" +version = "1.9.7" dependencies = [ "authenticator", "base64 0.21.7", @@ -387,10 +387,13 @@ dependencies = [ "p256", "p384", "pem", + "pinentry", "rand 0.8.5", "reqwest", "ring 0.17.8", + "rpassword", "rust_util", + "secrecy", "sequoia-openpgp", "serde", "serde_json", @@ -1199,6 +1202,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.12" @@ -2233,6 +2245,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pinentry" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5b8bc68be6a5e2ba84ee86db53f816cba1905b94fcb7c236e606221cc8fc8" +dependencies = [ + "log", + "nom", + "percent-encoding", + "secrecy", + "which", + "zeroize", +] + [[package]] name = "pkcs1" version = "0.4.1" @@ -2584,6 +2610,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rpassword" +version = "7.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.48.0", +] + [[package]] name = "rsa" version = "0.8.2" @@ -2625,6 +2662,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rtoolbox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "runloop" version = "0.1.0" @@ -3830,6 +3877,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 850813d..9cb26fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.9.6" +version = "1.9.7" authors = ["Hatter Jiang "] edition = "2018" @@ -40,6 +40,9 @@ bech32 = "0.9" ecdsa = { version = "0.16", features = ["verifying", "spki", "pem", "der"] } jwt = "0.16.0" reqwest = { version = "0.11", features = ["blocking"] } +pinentry = "0.5.0" +rpassword = "7.3.1" +secrecy = "0.8.0" #lazy_static = "1.4.0" #ssh-key = "0.4.0" #ctap-hid-fido2 = "2.1.3" diff --git a/src/cmd_pgpcardadmin.rs b/src/cmd_pgpcardadmin.rs index 9cfe64c..87c21ab 100644 --- a/src/cmd_pgpcardadmin.rs +++ b/src/cmd_pgpcardadmin.rs @@ -2,7 +2,7 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use openpgp_card::card_do::{Lang, Sex}; use rust_util::util_clap::{Command, CommandError}; -use crate::pgpcardutil; +use crate::{pgpcardutil, pinutil}; pub struct CommandImpl; @@ -11,7 +11,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("OpenPGP Card admin subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("12345678").help("OpenPGP card admin pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("OpenPGP card admin pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).help("[deprecated] now OpenPGP card admin pin")) .arg(Arg::with_name("name").short("n").long("name").takes_value(true).required(false).help("Set name")) .arg(Arg::with_name("url").long("url").takes_value(true).required(false).help("Set URL")) @@ -22,6 +22,8 @@ impl Command for CommandImpl { fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin")); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let pin = opt_value_result!(pin_opt, "Pin must be assigned"); if pin.len() < 8 { return simple_error!("Admin pin length:{}, must >= 8!", pin.len()); } diff --git a/src/cmd_pgpcarddecrypt.rs b/src/cmd_pgpcarddecrypt.rs index a23b92f..eeb3197 100644 --- a/src/cmd_pgpcarddecrypt.rs +++ b/src/cmd_pgpcarddecrypt.rs @@ -5,7 +5,7 @@ use openpgp_card::crypto_data::Cryptogram; use rust_util::{util_msg, XResult}; use rust_util::util_clap::{Command, CommandError}; -use crate::pgpcardutil; +use crate::{pgpcardutil, pinutil}; use crate::util::{base64_encode, read_stdin, try_decode}; #[derive(Debug, Clone, Copy)] @@ -31,7 +31,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("OpenPGP Card decrypt subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("OpenPGP card user pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).help("[deprecated] now OpenPGP card user pin")) .arg(Arg::with_name("ciphertext").short("c").long("ciphertext").takes_value(true).help("Cipher text (HEX or Base64)")) .arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)")) @@ -44,6 +44,8 @@ impl Command for CommandImpl { if json_output { util_msg::set_logger_std_out(false); } let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin")); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let pin = opt_value_result!(pin_opt, "User pin must be assigned"); if pin.len() < 6 { return simple_error!("User pin length:{}, must >= 6!", pin.len()); } diff --git a/src/cmd_pgpcardmake.rs b/src/cmd_pgpcardmake.rs index 62da359..d5ffedd 100644 --- a/src/cmd_pgpcardmake.rs +++ b/src/cmd_pgpcardmake.rs @@ -21,6 +21,7 @@ use rust_util::util_clap::{Command, CommandError}; use rust_util::XResult; use sequoia_openpgp as openpgp; +use crate::pinutil; use crate::rsautil::RsaCrt; #[derive(Debug)] @@ -128,7 +129,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("OpenPGP Card make subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("12345678").help("OpenPGP card admin pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("OpenPGP card admin pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).required(false).help("Password for PGP secret key")) .arg(Arg::with_name("in").long("in").takes_value(true).required(false).help("PGP file in")) .arg(Arg::with_name("force-make").long("force-make").help("Force make OpenPGP card")) @@ -138,6 +139,8 @@ impl Command for CommandImpl { fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let pin = opt_value_result!(pin_opt, "Pin must be assigned"); if pin.len() < 8 { return simple_error!("Admin pin length:{}, must >= 8!", pin.len()); } diff --git a/src/cmd_pgpcardsign.rs b/src/cmd_pgpcardsign.rs index cfc07aa..a1ebffb 100644 --- a/src/cmd_pgpcardsign.rs +++ b/src/cmd_pgpcardsign.rs @@ -5,11 +5,11 @@ use std::io::{ErrorKind, Read}; use clap::{App, Arg, ArgMatches, SubCommand}; use digest::Digest; use openpgp_card::crypto_data::Hash; -use rust_util::util_clap::{Command, CommandError}; use rust_util::{util_msg, XResult}; +use rust_util::util_clap::{Command, CommandError}; use sha2::{Sha256, Sha384, Sha512}; -use crate::pgpcardutil; +use crate::{pgpcardutil, pinutil}; use crate::util::base64_encode; const BUFF_SIZE: usize = 512 * 1024; @@ -39,7 +39,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("OpenPGP Card sign subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("OpenPGP card user pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).help("[deprecated] now OpenPGP card user pin")) .arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX")) .arg(Arg::with_name("sha384").short("3").long("sha384").takes_value(true).help("Digest SHA384 HEX")) @@ -57,6 +57,8 @@ impl Command for CommandImpl { if json_output { util_msg::set_logger_std_out(false); } let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin")); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let pin = opt_value_result!(pin_opt, "User pin must be assigned"); if pin.len() < 6 { return simple_error!("User pin length:{}, must >= 6!", pin.len()); } @@ -177,7 +179,10 @@ impl Command for CommandImpl { } } -fn calc_file_digest(file_name: &str) -> XResult> where D: Digest { +fn calc_file_digest(file_name: &str) -> XResult> +where + D: Digest, +{ let mut hasher = D::new(); let mut buf: [u8; BUFF_SIZE] = [0u8; BUFF_SIZE]; let mut f = File::open(file_name)?; diff --git a/src/cmd_pivdecrypt.rs b/src/cmd_pivdecrypt.rs index c685112..9c55633 100644 --- a/src/cmd_pivdecrypt.rs +++ b/src/cmd_pivdecrypt.rs @@ -6,7 +6,7 @@ use rust_util::util_msg; use yubikey::piv::AlgorithmId; use yubikey::YubiKey; -use crate::pivutil; +use crate::{pinutil, pivutil}; use crate::util::{read_stdin, try_decode}; pub struct CommandImpl; @@ -17,7 +17,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV decrypt(RSA) subcommand") .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) .arg(Arg::with_name("ciphertext").long("ciphertext").short("c").takes_value(true).help("Encrypted data (HEX or Base64)")) .arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)")) .arg(Arg::with_name("json").long("json").help("JSON output")) @@ -30,6 +30,8 @@ impl Command for CommandImpl { let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let pin = opt_value_result!(pin_opt, "User pin must be assigned"); let encrypted_data = if let Some(ciphertext) = sub_arg_matches.value_of("ciphertext") { diff --git a/src/cmd_pivecdh.rs b/src/cmd_pivecdh.rs index 42a0fce..55b0231 100644 --- a/src/cmd_pivecdh.rs +++ b/src/cmd_pivecdh.rs @@ -8,7 +8,7 @@ use rust_util::util_msg; use yubikey::{PinPolicy, YubiKey}; use yubikey::piv::{AlgorithmId, decrypt_data, metadata}; -use crate::{ecdhutil, pivutil}; +use crate::{ecdhutil, pinutil, pivutil}; use crate::pivutil::get_algorithm_id; pub struct CommandImpl; @@ -18,7 +18,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV ECDH subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ...")) .arg(Arg::with_name("public-256").long("public-256").help("Public key (P-256)")) .arg(Arg::with_name("public-384").long("public-384").help("Public key (P-384)")) @@ -71,6 +71,8 @@ impl Command for CommandImpl { if private { let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ..."); let epk = opt_value_result!(sub_arg_matches.value_of("epk"), "--epk must assigned"); diff --git a/src/cmd_pivecsign.rs b/src/cmd_pivecsign.rs index b657410..96341d7 100644 --- a/src/cmd_pivecsign.rs +++ b/src/cmd_pivecsign.rs @@ -7,7 +7,7 @@ use x509_parser::nom::AsBytes; use yubikey::piv::{AlgorithmId, ManagementAlgorithmId, metadata, sign_data}; use yubikey::YubiKey; -use crate::{argsutil, pivutil}; +use crate::{argsutil, pinutil, pivutil}; use crate::util::base64_encode; pub struct CommandImpl; @@ -17,7 +17,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV EC sign(with SHA256) subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("algorithm").short("a").long("algorithm").takes_value(true).help("Algorithm, p256 or p384")) .arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file")) @@ -33,6 +33,8 @@ impl Command for CommandImpl { let mut json = BTreeMap::<&'_ str, String>::new(); let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); let hash_bytes = argsutil::get_sha256_digest_or_hash(sub_arg_matches)?; diff --git a/src/cmd_pivgenerate.rs b/src/cmd_pivgenerate.rs index ce0b120..b6f42c8 100644 --- a/src/cmd_pivgenerate.rs +++ b/src/cmd_pivgenerate.rs @@ -4,6 +4,8 @@ use rust_util::util_msg; use yubikey::{PinPolicy, piv, TouchPolicy, YubiKey}; use yubikey::piv::{AlgorithmId, SlotId}; +use crate::pinutil; + pub struct CommandImpl; impl Command for CommandImpl { @@ -11,7 +13,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV generate subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("OpenPGP card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) .arg(Arg::with_name("force").long("force").help("Force generate")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -21,7 +23,10 @@ impl Command for CommandImpl { if json_output { util_msg::set_logger_std_out(false); } warning!("This feature is not works"); - let pin = opt_value_result!(sub_arg_matches.value_of("pin"), "User pin must be assigned"); + let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); + let pin = opt_value_result!(pin_opt, "User pin must be assigned"); if !sub_arg_matches.is_present("force") { failure_and_exit!("--force must be assigned"); diff --git a/src/cmd_pivrsasign.rs b/src/cmd_pivrsasign.rs index ff3ad68..4c4bb4c 100644 --- a/src/cmd_pivrsasign.rs +++ b/src/cmd_pivrsasign.rs @@ -6,7 +6,7 @@ use rust_util::util_msg; use yubikey::{piv, YubiKey}; use yubikey::piv::{AlgorithmId, SlotId}; -use crate::{pivutil, rsautil}; +use crate::{pinutil, pivutil, rsautil}; use crate::util::base64_encode; pub struct CommandImpl; @@ -17,7 +17,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV RSA sign(with SHA256) subcommand") .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) .arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -27,6 +27,8 @@ impl Command for CommandImpl { if json_output { util_msg::set_logger_std_out(false); } let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let pin = opt_value_result!(pin_opt, "User pin must be assigned"); let sha256_hex_opt = sub_arg_matches.value_of("sha256").map(|s| s.to_string()); diff --git a/src/cmd_signfile.rs b/src/cmd_signfile.rs index 250805a..139516e 100644 --- a/src/cmd_signfile.rs +++ b/src/cmd_signfile.rs @@ -10,7 +10,7 @@ use x509_parser::nom::AsBytes; use yubikey::{Key, YubiKey}; use yubikey::piv::{sign_data, SlotId}; -use crate::{argsutil, pivutil}; +use crate::{argsutil, pinutil, pivutil}; use crate::digest::sha256_bytes; use crate::signfile::{CERTIFICATES_SEARCH_URL, HASH_ALGORITHM_SHA256, SIGNATURE_ALGORITHM_SHA256_WITH_ECDSA, SignFileRequest, SIMPLE_SIG_SCHEMA, SimpleSignFile, SimpleSignFileSignature}; use crate::util::base64_encode; @@ -40,7 +40,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV sign(with SHA256) subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) .arg(Arg::with_name("slot").short("s").long("slot") .takes_value(true).required(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("file").short("f").long("file").takes_value(true).required(true).help("Input file")) @@ -58,6 +58,8 @@ impl Command for CommandImpl { let attributes_opt = sub_arg_matches.value_of("attributes").map(ToString::to_string); let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); // TODO read from stream not in memory diff --git a/src/cmd_signjwt.rs b/src/cmd_signjwt.rs index 76436d6..9e21762 100644 --- a/src/cmd_signjwt.rs +++ b/src/cmd_signjwt.rs @@ -10,7 +10,7 @@ use serde_json::{Map, Number, Value}; use yubikey::{Certificate, YubiKey}; use yubikey::piv::{AlgorithmId, sign_data}; -use crate::{digest, pivutil, rsautil, util}; +use crate::{digest, pinutil, pivutil, rsautil, util}; const SEPARATOR: &str = "."; @@ -21,7 +21,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("Sign JWT subcommand") - .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) + .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("key-id").short("K").long("key-id").takes_value(true).help("Header key ID")) .arg(Arg::with_name("claims").short("C").long("claims").takes_value(true).multiple(true).help("Claims, key:value")) @@ -38,6 +38,8 @@ impl Command for CommandImpl { let mut json = BTreeMap::<&'_ str, String>::new(); let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pin_opt.as_deref(); let slot = opt_value_result!( sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); diff --git a/src/cmd_sshagent.rs b/src/cmd_sshagent.rs index 8d897bd..e0f823c 100644 --- a/src/cmd_sshagent.rs +++ b/src/cmd_sshagent.rs @@ -15,6 +15,7 @@ use ssh_agent::proto::message::{self, Message}; use ssh_agent::proto::public_key::PublicKey; use crate::digest::{copy_sha256, copy_sha512}; +use crate::pinutil; use crate::sshutil::{generate_ssh_string, with_sign}; struct SshAgent { @@ -161,7 +162,9 @@ impl Command for CommandImpl { if use_pgp && !(use_pgp_sign ^ use_pgp_auth) { return simple_error!("Args --pgp-sign or --pgp-auth must have one selection when use --pgp"); } - let pin = sub_arg_matches.value_of("pin").unwrap(); + let pin_opt = sub_arg_matches.value_of("pin"); + let pin_opt = pinutil::get_pin(pin_opt); + let pin = pin_opt.as_deref().unwrap(); let sock_file = sub_arg_matches.value_of("sock-file").unwrap(); information!("Sock file: {}", sock_file); diff --git a/src/main.rs b/src/main.rs index c6a4eec..e681625 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,7 @@ mod cmd_signfile; mod cmd_verifyfile; mod signfile; mod ecdhutil; +mod pinutil; pub struct DefaultCommandImpl; diff --git a/src/pinutil.rs b/src/pinutil.rs new file mode 100644 index 0000000..4397623 --- /dev/null +++ b/src/pinutil.rs @@ -0,0 +1,49 @@ +use std::{env, fs}; + +use pinentry::PassphraseInput; +use secrecy::ExposeSecret; + +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 fn get_pin(pin_opt: Option<&str>) -> Option { + if let Some(pin) = pin_opt { + return Some(pin.to_string()); + } + let pin_entry = get_pin_entry(); + + if let Some(mut input) = PassphraseInput::with_binary(pin_entry) { + let secret = input + .with_description("Please input PIN.") + .with_prompt("PIN: ") + .interact(); + match secret { + Ok(secret_string) => Some(secret_string.expose_secret().to_string()), + Err(e) => { + warning!("Input PIN failed: {}", e); + None + } + } + } else { + match rpassword::prompt_password("Please input PIN: ") { + Ok(pin) => Some(pin), + Err(e) => { + warning!("Input PIN failed: {}", e); + None + } + } + } +} + +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() +} \ No newline at end of file