From 9e6a1f3ab53800f1728dd5183e016b9bef954e16 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 9 Apr 2022 09:34:24 +0800 Subject: [PATCH] feat: v1.1.7, add rsa-encrypt sub command --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_rsaencrypt.rs | 92 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/cmd_rsaencrypt.rs diff --git a/Cargo.lock b/Cargo.lock index e540638..36eaa94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,7 +384,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.1.6" +version = "1.1.7" dependencies = [ "authenticator", "base64 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 4d4bffe..498127f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.1.6" +version = "1.1.7" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_rsaencrypt.rs b/src/cmd_rsaencrypt.rs new file mode 100644 index 0000000..87841a8 --- /dev/null +++ b/src/cmd_rsaencrypt.rs @@ -0,0 +1,92 @@ +use std::collections::BTreeMap; + +use clap::{App, Arg, ArgMatches, SubCommand}; +use openssl::encrypt::Encrypter; +use openssl::pkey::PKey; +use openssl::rsa::{Padding, Rsa}; +use rust_util::util_clap::{Command, CommandError}; + +use crate::digest::sha256_bytes; + +pub struct CommandImpl; + +// https://docs.rs/openssl/0.10.36/openssl/encrypt/index.html +impl Command for CommandImpl { + fn name(&self) -> &str { "rsa-encrypt" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("RSA Encrypt subcommand") + .arg(Arg::with_name("pub-key-in").long("pub-key-in").takes_value(true).help("Public key in")) + .arg(Arg::with_name("data").long("data").takes_value(true).help("Data")) + .arg(Arg::with_name("data-hex").long("data-hex").takes_value(true).help("Data in HEX")) + .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")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let json_output = sub_arg_matches.is_present("json"); + if json_output { rust_util::util_msg::set_logger_std_out(false); } + + let pub_key_in = opt_value_result!(sub_arg_matches.value_of("pub-key-in"), "Require public key in"); + let pub_key_bytes = opt_result!(std::fs::read(pub_key_in), "Read file: {}, failed: {}", pub_key_in); + + let padding_opt = sub_arg_matches.value_of("padding"); + + let padding = match padding_opt { + Some("oaep") | Some("pkcs1_oaep") => Padding::PKCS1_OAEP, + Some("pss") | Some("pkcs1_pss") => Padding::PKCS1_PSS, + Some("none") => Padding::NONE, + Some("pkcs1") | None => Padding::PKCS1, + Some(p) => return simple_error!("Not supported padding: {}", p), + }; + let padding_str = match padding { + Padding::NONE => "none", + Padding::PKCS1 => "pkcs1", + Padding::PKCS1_PSS => "pkcs1_pss", + Padding::PKCS1_OAEP => "pkcs1_oaep", + _ => "unknown", + }; + + let mut json = BTreeMap::new(); + + let keypair = opt_result!(Rsa::public_key_from_pem(&pub_key_bytes), "Parse RSA failed: {}"); + let pub_key_der = opt_result!(keypair.public_key_to_der(), "RSA public key to der failed: {}"); + let pub_key_fingerprint = hex::encode(sha256_bytes(&pub_key_der)); + let keypair = opt_result!(PKey::from_rsa(keypair), "RSA to PKey failed: {}"); + + let data = if let Some(data_hex) = sub_arg_matches.value_of("data-hex") { + opt_result!(hex::decode(data_hex), "Decode data HEX failed: {}") + } else if let Some(data) = sub_arg_matches.value_of("data") { + data.as_bytes().to_vec() + } else { + return simple_error!("Data is required, --data-hex or --data argument!"); + }; + + let mut encrypter = opt_result!(Encrypter::new(&keypair), "Encrypter new failed: {}"); + opt_result!(encrypter.set_rsa_padding(padding), "Set RSA padding failed: {}"); + let buffer_len = opt_result!(encrypter.encrypt_len(&data), "Encrypt len failed: {}"); + let mut encrypted = vec![0; buffer_len]; + let encrypted_len = opt_result!(encrypter.encrypt(&data, &mut encrypted), "Encrypt failed: {}"); + encrypted.truncate(encrypted_len); + + let encrypted_hex = hex::encode(&encrypted); + information!("Message: {}", String::from_utf8_lossy(&data)); + information!("Message HEX: {}", hex::encode(&data)); + information!("Padding: {}", padding_str); + information!("Public key fingerprint: {}", pub_key_fingerprint); + success!("Encrypted message: {}", encrypted_hex); + if json_output { + json.insert("data", hex::encode(&data)); + json.insert("public_key_fingerprint", pub_key_fingerprint); + json.insert("padding", padding_str.to_string()); + json.insert("encrypted", encrypted_hex); + } + + if json_output { + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + } + + Ok(None) + } +} diff --git a/src/main.rs b/src/main.rs index 9694762..4ebf35d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod pkiutil; mod pgpcardutil; mod cmd_u2fregister; mod cmd_u2fsign; +mod cmd_rsaencrypt; mod cmd_pgp; mod cmd_pgpcardadmin; mod cmd_pgpcardlist; @@ -44,6 +45,7 @@ fn inner_main() -> CommandError { let commands: Vec> = vec![ Box::new(cmd_chall::CommandImpl), Box::new(cmd_challconfig::CommandImpl), + Box::new(cmd_rsaencrypt::CommandImpl), Box::new(cmd_pgp::CommandImpl), Box::new(cmd_pgpcardadmin::CommandImpl), Box::new(cmd_pgpcardlist::CommandImpl),