From 5a1942e1506ce1a0e5cad8eff4b1a4af3eabbadc Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 29 Dec 2024 11:54:25 +0800 Subject: [PATCH] feat: v1.10.14, add --no-pin for many subcommands --- Cargo.lock | 2 +- Cargo.toml | 2 +- justfile | 8 ++++++++ src/cmd_pivdecrypt.rs | 10 +++++----- src/cmd_pivecdh.rs | 7 +++---- src/cmd_pivecsign.rs | 13 +++---------- src/cmd_pivrsasign.rs | 10 +++++----- src/cmd_signfile.rs | 7 +++---- src/cmd_signjwt.rs | 7 +++---- src/cmd_sshpivcert.rs | 4 ++-- src/cmd_sshpivsign.rs | 7 +++---- src/pinutil.rs | 11 ++++++++++- 12 files changed, 47 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bf5eb1..d368b2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.10.13" +version = "1.10.14" dependencies = [ "authenticator 0.3.1", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 5e969f1..cb75b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.10.13" +version = "1.10.14" authors = ["Hatter Jiang "] edition = "2018" diff --git a/justfile b/justfile index 810c03d..43dabec 100644 --- a/justfile +++ b/justfile @@ -5,6 +5,14 @@ _: install: cargo install --path . +# build without default features +build-simple: + cargo build --no-default-features + +# install without default features +install-simple: + cargo install --no-default-features + # run --help help: cargo r -- --help diff --git a/src/cmd_pivdecrypt.rs b/src/cmd_pivdecrypt.rs index 9c55633..19a0a13 100644 --- a/src/cmd_pivdecrypt.rs +++ b/src/cmd_pivdecrypt.rs @@ -18,6 +18,7 @@ impl Command for CommandImpl { 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).help("PIV card user PIN")) + .arg(Arg::with_name("no-pin").long("no-pin").help("No 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")) @@ -29,10 +30,7 @@ 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 pin_opt = pinutil::read_pin(sub_arg_matches); let encrypted_data = if let Some(ciphertext) = sub_arg_matches.value_of("ciphertext") { opt_result!(try_decode(ciphertext), "Decode --ciphertext failed: {}") @@ -43,7 +41,9 @@ impl Command for CommandImpl { }; let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); - opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); + if let Some(pin) = &pin_opt { + opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); + } let slot_id = pivutil::get_slot_id(slot)?; let decrypt_result = yubikey::piv::decrypt_data(&mut yk, &encrypted_data, diff --git a/src/cmd_pivecdh.rs b/src/cmd_pivecdh.rs index 55b0231..0e7a1ec 100644 --- a/src/cmd_pivecdh.rs +++ b/src/cmd_pivecdh.rs @@ -19,6 +19,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("no-pin").long("no-pin").help("No 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)")) @@ -70,9 +71,7 @@ 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 pin_opt = pinutil::read_pin(sub_arg_matches); 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"); @@ -107,7 +106,7 @@ impl Command for CommandImpl { warning!("Get slot: {} meta data failed", slot); } - if let Some(pin) = pin_opt { + if let Some(pin) = &pin_opt { opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); } diff --git a/src/cmd_pivecsign.rs b/src/cmd_pivecsign.rs index 208920c..9a121b7 100644 --- a/src/cmd_pivecsign.rs +++ b/src/cmd_pivecsign.rs @@ -4,11 +4,11 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use rust_util::util_clap::{Command, CommandError}; use rust_util::util_msg; use x509_parser::nom::AsBytes; -use yubikey::piv::{AlgorithmId, ManagementAlgorithmId, metadata, sign_data}; +use yubikey::piv::{metadata, sign_data, AlgorithmId, ManagementAlgorithmId}; use yubikey::YubiKey; -use crate::{argsutil, pinutil, pivutil}; use crate::util::base64_encode; +use crate::{argsutil, pinutil, pivutil}; pub struct CommandImpl; @@ -33,14 +33,7 @@ impl Command for CommandImpl { let mut json = BTreeMap::<&'_ str, String>::new(); - let pin_opt = if sub_arg_matches.is_present("no-pin") { - None - } else { - let pin_opt = sub_arg_matches.value_of("pin"); - let pin_opt = pinutil::get_pin(pin_opt); - let pin_opt = pin_opt.as_deref(); - pin_opt.map(|p| p.to_string()) - }; + let pin_opt = pinutil::read_pin(sub_arg_matches); 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_pivrsasign.rs b/src/cmd_pivrsasign.rs index 4c4bb4c..65720aa 100644 --- a/src/cmd_pivrsasign.rs +++ b/src/cmd_pivrsasign.rs @@ -18,6 +18,7 @@ impl Command for CommandImpl { 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).help("PIV card user PIN")) + .arg(Arg::with_name("no-pin").long("no-pin").help("No 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")) } @@ -26,15 +27,14 @@ impl Command for CommandImpl { let json_output = sub_arg_matches.is_present("json"); 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 pin_opt = pinutil::read_pin(sub_arg_matches); let sha256_hex_opt = sub_arg_matches.value_of("sha256").map(|s| s.to_string()); let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); - opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); + if let Some(pin) = &pin_opt { + opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); + } let slot_id = match sub_arg_matches.value_of("slot") { None => SlotId::Signature, diff --git a/src/cmd_signfile.rs b/src/cmd_signfile.rs index 139516e..728f73f 100644 --- a/src/cmd_signfile.rs +++ b/src/cmd_signfile.rs @@ -41,6 +41,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("no-pin").long("no-pin").help("No 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")) @@ -57,9 +58,7 @@ impl Command for CommandImpl { let comment_opt = sub_arg_matches.value_of("comment").map(ToString::to_string); 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 pin_opt = pinutil::read_pin(sub_arg_matches); 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 @@ -83,7 +82,7 @@ impl Command for CommandImpl { let algorithm_id = opt_result!( pivutil::get_algorithm_id_by_certificate(certificate), "Get slot key algorithm failed: {}"); debugging!("PIV algorithm: {:?}", algorithm_id); - if let Some(pin) = pin_opt { + if let Some(pin) = &pin_opt { debugging!("PIN is assigned."); opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); } diff --git a/src/cmd_signjwt.rs b/src/cmd_signjwt.rs index 9e21762..3a93ac1 100644 --- a/src/cmd_signjwt.rs +++ b/src/cmd_signjwt.rs @@ -22,6 +22,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("no-pin").long("no-pin").help("No 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")) @@ -37,9 +38,7 @@ 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 pin_opt = pinutil::read_pin(sub_arg_matches); let slot = opt_value_result!( sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); @@ -110,7 +109,7 @@ impl Command for CommandImpl { } -fn sign_jwt(slot: &str, pin_opt: &Option<&str>, mut header: Header, payload: &Option<&str>, claims: &Map) -> XResult { +fn sign_jwt(slot: &str, pin_opt: &Option, mut header: Header, payload: &Option<&str>, claims: &Map) -> XResult { let mut yk = opt_result!(YubiKey::open(), "Find YubiKey failed: {}"); let slot_id = opt_result!(pivutil::get_slot_id(slot), "Get slot id failed: {}"); diff --git a/src/cmd_sshpivcert.rs b/src/cmd_sshpivcert.rs index a4e67da..35b56fb 100644 --- a/src/cmd_sshpivcert.rs +++ b/src/cmd_sshpivcert.rs @@ -28,6 +28,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("SSH PIV sign cert subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) + .arg(Arg::with_name("no-pin").long("no-pin").help("No 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).default_value("default_key_id").help("SSH user CA key id")) .arg(Arg::with_name("principal").short("P").long("principal").takes_value(true).default_value("root").multiple(true).help("SSH user CA principal")) @@ -85,8 +86,7 @@ impl Command for CommandImpl { information!("Principals: {:?}", principals); information!("Validity: {} seconds", validity_u64); - let pin_opt = sub_arg_matches.value_of("pin"); - let pin_opt = pinutil::get_pin(pin_opt); + let pin_opt = pinutil::read_pin(sub_arg_matches); let cert_der = find_cert(&mut yk, slot_id)?; let ca_ssh_pub_key = opt_result!(extract_ssh_pubkey_from_x509_certificate(&cert_der), "Extract SSH public key failed: {}"); diff --git a/src/cmd_sshpivsign.rs b/src/cmd_sshpivsign.rs index b95b501..34cd2c1 100644 --- a/src/cmd_sshpivsign.rs +++ b/src/cmd_sshpivsign.rs @@ -18,6 +18,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("SSH piv sign subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")) + .arg(Arg::with_name("no-pin").long("no-pin").help("No 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("namespace").short("n").long("namespace").takes_value(true).help("Namespace")) .arg(Arg::with_name("in").long("in").required(true).takes_value(true).help("In file, - for stdin")) @@ -35,9 +36,7 @@ impl Command for CommandImpl { let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); let slot_id = pivutil::get_slot_id(slot)?; - 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 = pinutil::read_pin(sub_arg_matches); let mut algorithm_id_opt = None; let mut ec_key_point = vec![]; @@ -96,7 +95,7 @@ impl Command for CommandImpl { crate::digest::sha384_bytes(&sign_message) }; - if let Some(pin) = pin_opt { + if let Some(pin) = &pin_opt { opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); } let mut signature_value = vec![]; diff --git a/src/pinutil.rs b/src/pinutil.rs index 4397623..d114f61 100644 --- a/src/pinutil.rs +++ b/src/pinutil.rs @@ -1,5 +1,5 @@ use std::{env, fs}; - +use clap::ArgMatches; use pinentry::PassphraseInput; use secrecy::ExposeSecret; @@ -7,6 +7,15 @@ 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 read_pin(sub_arg_matches: &ArgMatches) -> Option { + if sub_arg_matches.is_present("no-pin") { + None + } else { + let pin_opt = sub_arg_matches.value_of("pin"); + get_pin(pin_opt) + } +} + pub fn get_pin(pin_opt: Option<&str>) -> Option { if let Some(pin) = pin_opt { return Some(pin.to_string());