From b0af535aa3f8d3c2b4d6621dad090fb2fa5b9210 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 15 Oct 2023 13:55:14 +0800 Subject: [PATCH] feat: v0.3.4, encrypt supports --key-filter --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_decrypt.rs | 18 ++++-------- src/cmd_encrypt.rs | 17 ++++++----- src/cmd_info.rs | 16 +++++------ src/config.rs | 69 ++++++++++++++++++++++++++++++++++----------- src/main.rs | 1 + src/util_envelop.rs | 25 ++++++++++++++++ 8 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 src/util_envelop.rs diff --git a/Cargo.lock b/Cargo.lock index a0d1eaa..5dc5af3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2144,7 +2144,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "0.3.3" +version = "0.3.4" dependencies = [ "aes-gcm-stream", "base64", diff --git a/Cargo.toml b/Cargo.toml index a4d080d..edf6f94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "0.3.3" +version = "0.3.4" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index 9a7d36d..5da6ed6 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -16,7 +16,7 @@ use yubikey::piv::{AlgorithmId, decrypt_data}; use yubikey::YubiKey; use zeroize::Zeroize; -use crate::{util, util_enc_file, util_file, util_pgp, util_piv}; +use crate::{util, util_enc_file, util_envelop, util_file, util_pgp, util_piv}; use crate::compress::GzStreamDecoder; use crate::config::TinyEncryptConfig; use crate::consts::{ @@ -99,7 +99,7 @@ pub fn decrypt_single(config: &Option, let path_out = &path_display[0..path_display.len() - TINY_ENC_FILE_EXT.len()]; util::require_file_not_exists(path_out)?; - let selected_envelop = select_envelop(&meta)?; + let selected_envelop = select_envelop(&meta, config)?; let key = try_decrypt_key(config, selected_envelop, pin, slot)?; let nonce = opt_result!(util::decode_base64(&meta.nonce), "Decode nonce failed: {}"); @@ -297,7 +297,7 @@ fn try_decrypt_key_pgp(envelop: &TinyEncryptEnvelop, pin: &Option) -> XR Ok(key) } -fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> { +fn select_envelop<'a>(meta: &'a TinyEncryptMeta, config: &Option) -> XResult<&'a TinyEncryptEnvelop> { let envelops = match &meta.envelops { None => return simple_error!("No envelops found"), Some(envelops) => if envelops.is_empty() { @@ -310,21 +310,13 @@ fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> { success!("Found {} envelops:", envelops.len()); if envelops.len() == 1 { let selected_envelop = &envelops[0]; - success!("Auto selected envelop: #{} {}", 1, selected_envelop.r#type.get_upper_name()); + success!("Auto selected envelop: #{} {}", 1, util_envelop::format_envelop(selected_envelop, &config)); util::read_line("Press enter to continue: "); return Ok(selected_envelop); } envelops.iter().enumerate().for_each(|(i, envelop)| { - let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid)); - let desc = envelop.desc.as_ref() - .map(|desc| format!(", Desc: {}", desc)) - .unwrap_or_else(|| "".to_string()); - println!("#{} {}{}{}", i + 1, - envelop.r#type.get_upper_name(), - kid, - desc, - ); + println!("#{} {}", i + 1, util_envelop::format_envelop(envelop, &config)); }); let envelop_number = util::read_number("Please select an envelop:", 1, envelops.len()); diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index 1f65d4a..907f867 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -11,7 +11,7 @@ use rust_util::{debugging, failure, iff, information, opt_result, simple_error, use rust_util::util_time::UnixEpochTime; use zeroize::Zeroize; -use crate::{util_enc_file, util, util_ecdh, util_p384, util_x25519}; +use crate::{util, util_ecdh, util_enc_file, util_p384, util_x25519}; use crate::compress::GzStreamEncoder; use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop}; use crate::consts::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT}; @@ -24,22 +24,25 @@ use crate::wrap_key::{WrapKey, WrapKeyHeader}; pub struct CmdEncrypt { /// Files need to be decrypted pub paths: Vec, - /// Comment + /// Plaintext comment #[arg(long, short = 'c')] pub comment: Option, /// Encrypted comment #[arg(long, short = 'C')] pub encrypted_comment: Option, - /// Encryption profile + /// Encryption profile (use default when --key-filter is assigned) #[arg(long, short = 'p')] pub profile: Option, + /// Encryption key filter (key_id or type:TYPE(e.g. ecdh, pgp, ecdh-p384, pgp-ed25519), multiple joined by ',') + #[arg(long, short = 'k')] + pub key_filter: Option, /// Compress before encrypt #[arg(long, short = 'x')] pub compress: bool, /// Compress level (from 0[none], 1[fast] .. 6[default] .. to 9[best]) #[arg(long, short = 'L')] pub compress_level: Option, - /// Compatible with 1.0 + /// Compatible with 1.0 (requires assign --disable-compress-meta) #[arg(long, short = '1')] pub compatible_with_1_0: bool, /// Remove source file @@ -53,7 +56,7 @@ pub struct CmdEncrypt { pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> { let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?; debugging!("Found tiny encrypt config: {:?}", config); - let envelops = config.find_envelops(&cmd_encrypt.profile)?; + let envelops = config.find_envelops(&cmd_encrypt.profile, &cmd_encrypt.key_filter)?; if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); } debugging!("Found envelops: {:?}", envelops); let envelop_tkids: Vec<_> = envelops.iter() @@ -306,7 +309,7 @@ fn encrypt_envelop_shared_secret(key: &[u8], Ok(TinyEncryptEnvelop { r#type: envelop.r#type, kid: envelop.kid.clone(), - desc: envelop.desc.clone(), + desc: None, // envelop.desc.clone(), encrypted_key: encoded_wrap_key, }) } @@ -318,7 +321,7 @@ fn encrypt_envelop_pgp(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResul Ok(TinyEncryptEnvelop { r#type: envelop.r#type, kid: envelop.kid.clone(), - desc: envelop.desc.clone(), + desc: None, // envelop.desc.clone(), encrypted_key: util::encode_base64(&encrypted_key), }) } diff --git a/src/cmd_info.rs b/src/cmd_info.rs index e72c614..38176ab 100644 --- a/src/cmd_info.rs +++ b/src/cmd_info.rs @@ -4,12 +4,13 @@ use std::path::PathBuf; use std::time::{Duration, SystemTime}; use clap::Args; -use rust_util::{util_time, iff, opt_result, simple_error, success, warning, XResult}; +use rust_util::{opt_result, simple_error, success, util_time, warning, XResult}; use rust_util::util_time::UnixEpochTime; use simpledateformat::format_human2; -use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM, TINY_ENC_FILE_EXT}; -use crate::util_enc_file; +use crate::{util_enc_file, util_envelop}; +use crate::config::TinyEncryptConfig; +use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT}; #[derive(Debug, Args)] pub struct CmdInfo { @@ -37,6 +38,7 @@ pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo) -> XResult<()> { return simple_error!("Not a Tiny Encrypt file: {}", path_display); } + let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE).ok(); let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display); let meta = opt_result!( util_enc_file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display @@ -71,13 +73,9 @@ pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo) -> XResult<()> { if let Some(envelops) = meta.envelops.as_ref() { envelops.iter().enumerate().for_each(|(i, envelop)| { - let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid)); - let desc = envelop.desc.as_ref().map(|desc| format!(", Desc: {}", desc)).unwrap_or_else(|| "".to_string()); - infos.push(format!("{}: {}{}{}", + infos.push(format!("{}: {}", header(&format!("Envelop #{}", i + 1)), - envelop.r#type.get_upper_name(), - kid, - desc + util_envelop::format_envelop(envelop, &config) )); }) } diff --git a/src/config.rs b/src/config.rs index 7605a4c..9b7ee50 100644 --- a/src/config.rs +++ b/src/config.rs @@ -41,6 +41,7 @@ pub struct TinyEncryptConfig { #[serde(rename_all = "camelCase")] pub struct TinyEncryptConfigEnvelop { pub r#type: TinyEncryptEnvelopType, + pub sid: Option, pub kid: String, pub desc: Option, pub args: Option>, @@ -81,32 +82,66 @@ impl TinyEncryptConfig { } pub fn find_by_kid(&self, kid: &str) -> Option<&TinyEncryptConfigEnvelop> { - self.envelops.iter().find(|e| e.kid == kid) + let config_envelops = self.find_by_kid_or_filter(kid, |_| false); + if config_envelops.is_empty() { + None + } else { + Some(config_envelops[0]) + } } - pub fn find_envelops(&self, profile: &Option) -> XResult> { - let profile = profile.as_ref().map(String::as_str).unwrap_or("default"); - debugging!("Profile: {}", profile); - let mut matched_envelops_map = HashMap::new(); - if let Some(key_ids) = self.profiles.get(profile) { - if key_ids.is_empty() { - return simple_error!("Profile: {} contains no valid envelopes", profile); + pub fn find_by_kid_or_type(&self, k_filter: &str) -> Vec<&TinyEncryptConfigEnvelop> { + self.find_by_kid_or_filter(k_filter, |e| { + k_filter == &format!("type:{}", &e.r#type.get_name()) + }) + } + + pub fn find_by_kid_or_filter(&self, kid: &str, f: F) -> Vec<&TinyEncryptConfigEnvelop> + where F: Fn(&TinyEncryptConfigEnvelop) -> bool { + self.envelops.iter().filter(|e| { + if e.kid == kid { + return true; } - for key_id in key_ids { - self.envelops.iter().for_each(|envelop| { - let is_matched = (&envelop.kid == key_id) - || key_id == &format!("type:{}", &envelop.r#type.get_name()); - if is_matched { - matched_envelops_map.insert(&envelop.kid, envelop); - } - }); + if let Some(sid) = &e.sid { + return sid == kid; + } + f(e) + }).collect() + } + + pub fn find_envelops(&self, profile: &Option, key_filter: &Option) -> XResult> { + debugging!("Profile: {:?}", profile); + debugging!("Key filter: {:?}", key_filter); + let mut matched_envelops_map = HashMap::new(); + let mut key_ids = vec![]; + if key_filter.is_none() || profile.is_some() { + let profile = profile.as_ref().map(String::as_str).unwrap_or("default"); + if let Some(kids) = self.profiles.get(profile) { + kids.iter().for_each(|k| key_ids.push(k.to_string())); } } + if let Some(key_filter) = key_filter { + key_filter.split(",").for_each(|k| { + let k = k.trim(); + if !k.is_empty() { + key_ids.push(k.to_string()); + } + }); + } + if key_ids.is_empty() { + return simple_error!("Profile or key filter cannot find valid envelopes"); + } + for key_id in &key_ids { + for envelop in self.find_by_kid_or_type(key_id) { + matched_envelops_map.insert(&envelop.kid, envelop); + } + } + let mut envelops: Vec<_> = matched_envelops_map.values() .copied() .collect(); if envelops.is_empty() { - return simple_error!("Profile: {} has no valid envelopes found", profile); + return simple_error!("Profile or key filter cannot find valid envelopes"); } envelops.sort_by(|e1, e2| { if e1.r#type < e2.r#type { return Ordering::Greater; } diff --git a/src/main.rs b/src/main.rs index 1407a23..bbfe86c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ mod spec; mod crypto_aes; mod crypto_rsa; mod wrap_key; +mod util_envelop; mod util_file; mod util_enc_file; mod cmd_version; diff --git a/src/util_envelop.rs b/src/util_envelop.rs new file mode 100644 index 0000000..25f80c6 --- /dev/null +++ b/src/util_envelop.rs @@ -0,0 +1,25 @@ +use rust_util::iff; + +use crate::config::TinyEncryptConfig; +use crate::spec::TinyEncryptEnvelop; + +pub fn format_envelop(envelop: &TinyEncryptEnvelop, config: &Option) -> String { + let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid)); + let envelop_desc = get_envelop_desc(&kid, envelop, &config); + let desc = envelop_desc.as_ref() + .map(|desc| format!(", Desc: {}", desc)) + .unwrap_or_else(|| "".to_string()); + format!("{}{}{}", envelop.r#type.get_upper_name(), kid, desc) +} + +pub fn get_envelop_desc(kid: &str, envelop: &TinyEncryptEnvelop, config: &Option) -> Option { + if let Some(desc) = &envelop.desc { + return Some(desc.to_string()); + } + if let Some(config) = config { + if let Some(config_envelop) = config.find_by_kid(kid) { + return config_envelop.desc.clone(); + } + } + None +}