diff --git a/Cargo.lock b/Cargo.lock index 28022bf..17b9213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.9.5" +version = "1.9.6" dependencies = [ "authenticator", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 0078350..850813d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.9.5" +version = "1.9.6" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_pgpcarddecrypt.rs b/src/cmd_pgpcarddecrypt.rs index 0a2cdab..a23b92f 100644 --- a/src/cmd_pgpcarddecrypt.rs +++ b/src/cmd_pgpcarddecrypt.rs @@ -6,7 +6,7 @@ use rust_util::{util_msg, XResult}; use rust_util::util_clap::{Command, CommandError}; use crate::pgpcardutil; -use crate::util::{base64_encode, try_decode}; +use crate::util::{base64_encode, read_stdin, try_decode}; #[derive(Debug, Clone, Copy)] enum EncryptAlgo { @@ -34,6 +34,7 @@ impl Command for CommandImpl { .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").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)")) .arg(Arg::with_name("algo").long("algo").takes_value(true).help("Algo: RSA, X25519/ECDH")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -53,6 +54,8 @@ impl Command for CommandImpl { let ciphertext_bytes = if let Some(ciphertext) = ciphertext { opt_result!(try_decode(ciphertext), "Decode cipher failed: {}") + } else if sub_arg_matches.is_present("stdin") { + read_stdin()? } else { return simple_error!("--ciphertext must be assigned"); }; diff --git a/src/cmd_pivdecrypt.rs b/src/cmd_pivdecrypt.rs index d43b585..c685112 100644 --- a/src/cmd_pivdecrypt.rs +++ b/src/cmd_pivdecrypt.rs @@ -7,7 +7,7 @@ use yubikey::piv::AlgorithmId; use yubikey::YubiKey; use crate::pivutil; -use crate::util::try_decode; +use crate::util::{read_stdin, try_decode}; pub struct CommandImpl; @@ -19,6 +19,7 @@ impl Command for CommandImpl { .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("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")) } @@ -33,6 +34,8 @@ impl Command for CommandImpl { let encrypted_data = if let Some(ciphertext) = sub_arg_matches.value_of("ciphertext") { opt_result!(try_decode(ciphertext), "Decode --ciphertext failed: {}") + } else if sub_arg_matches.is_present("stdin") { + read_stdin()? } else { return simple_error!("Argument --ciphertext must be assigned"); }; diff --git a/src/cmd_rsadecrypt.rs b/src/cmd_rsadecrypt.rs index 1f06f9f..b309b9f 100644 --- a/src/cmd_rsadecrypt.rs +++ b/src/cmd_rsadecrypt.rs @@ -9,6 +9,8 @@ use rust_util::util_clap::{Command, CommandError}; use rust_util::util_msg; use rust_util::util_msg::MessageType; +use crate::util::{read_stdin, try_decode}; + pub struct CommandImpl; // https://docs.rs/openssl/0.10.36/openssl/encrypt/index.html @@ -19,6 +21,8 @@ impl Command for CommandImpl { SubCommand::with_name(self.name()).about("RSA decrypt subcommand") .arg(Arg::with_name("pri-key-in").long("pri-key-in").takes_value(true).help("Private key in")) .arg(Arg::with_name("encrypted").long("encrypted").takes_value(true).help("Encrypted data")) + .arg(Arg::with_name("ciphertext").long("ciphertext").takes_value(true).help("Encrypted data")) + .arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)")) .arg(Arg::with_name("padding").long("padding").takes_value(true) .possible_values(&["pkcs1", "oaep", "pss", "none"]).help("Padding")) .arg(Arg::with_name("json").long("json").help("JSON output")) @@ -40,17 +44,21 @@ impl Command for CommandImpl { let keypair = opt_result!(Rsa::private_key_from_pem(&pri_key_bytes), "Parse RSA failed: {}"); let keypair = opt_result!(PKey::from_rsa(keypair), "RSA to PKey failed: {}"); - let encrypted = if let Some(encrypted) = sub_arg_matches.value_of("encrypted") { - opt_result!(hex::decode(encrypted), "Decode encrypted HEX failed: {}") + let ciphertext_opt = sub_arg_matches.value_of("encrypted") + .or_else(|| sub_arg_matches.value_of("ciphertext")); + let ciphertext = if let Some(ciphertext) = ciphertext_opt { + opt_result!(try_decode(ciphertext), "Decode ciphertext HEX or Base64 failed: {}") + } else if sub_arg_matches.is_present("stdin") { + read_stdin()? } else { - return simple_error!("Data is required, --data-hex or --data argument!"); + return simple_error!("Data is required, --ciphertext or --encrypted argument!"); }; util_msg::when(MessageType::DEBUG, || { let rsa = keypair.rsa().unwrap(); let n = rsa.n(); let d = rsa.d(); - let m = BigNum::from_slice(&encrypted).unwrap(); + let m = BigNum::from_slice(&ciphertext).unwrap(); let mut r = BigNum::new().unwrap(); r.mod_exp(&m, d, n, &mut BigNumContext::new().unwrap()).unwrap(); let v = r.to_vec(); @@ -63,12 +71,12 @@ impl Command for CommandImpl { let mut decrypter = opt_result!(Decrypter::new(&keypair), "Decrypter new failed: {}"); opt_result!(decrypter.set_rsa_padding(padding), "Set RSA padding failed: {}"); - let buffer_len = opt_result!(decrypter.decrypt_len(&encrypted), "Decrypt len failed: {}"); + let buffer_len = opt_result!(decrypter.decrypt_len(&ciphertext), "Decrypt len failed: {}"); let mut data = vec![0; buffer_len]; - let decrypted_len = opt_result!(decrypter.decrypt(&encrypted, &mut data), "Decrypt failed: {}"); + let decrypted_len = opt_result!(decrypter.decrypt(&ciphertext, &mut data), "Decrypt failed: {}"); data.truncate(decrypted_len); - let encrypted_hex = hex::encode(&encrypted); + let encrypted_hex = hex::encode(&ciphertext); information!("Padding: {}", padding_str); success!("Message HEX: {}", hex::encode(&data)); success!("Message: {}", String::from_utf8_lossy(&data)); diff --git a/src/util.rs b/src/util.rs index 97d3f1b..8c4a889 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +use std::io::Read; + use base64::{DecodeError, Engine}; use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}; use rust_util::XResult; @@ -15,11 +17,18 @@ pub fn base64_decode>(input: T) -> Result, DecodeError> { } pub fn try_decode(input: &str) -> XResult> { - return match hex::decode(input) { + match hex::decode(input) { Ok(v) => Ok(v), Err(_) => match base64_decode(input) { Ok(v) => Ok(v), Err(e) => simple_error!("decode hex or base64 error: {}", e), } - }; + } +} + +pub fn read_stdin() -> XResult> { + let mut buffer = vec![]; + let mut stdin = std::io::stdin(); + opt_result!(stdin.read_to_end(&mut buffer), "Read stdin failed: {}"); + Ok(buffer) }