feat: v0.3.4, encrypt supports --key-filter

This commit is contained in:
2023-10-15 13:55:14 +08:00
parent cd359bb6ac
commit b0af535aa3
8 changed files with 102 additions and 48 deletions

2
Cargo.lock generated
View File

@@ -2144,7 +2144,7 @@ dependencies = [
[[package]] [[package]]
name = "tiny-encrypt" name = "tiny-encrypt"
version = "0.3.3" version = "0.3.4"
dependencies = [ dependencies = [
"aes-gcm-stream", "aes-gcm-stream",
"base64", "base64",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "tiny-encrypt" name = "tiny-encrypt"
version = "0.3.3" version = "0.3.4"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
description = "A simple and tiny file encrypt tool" description = "A simple and tiny file encrypt tool"

View File

@@ -16,7 +16,7 @@ use yubikey::piv::{AlgorithmId, decrypt_data};
use yubikey::YubiKey; use yubikey::YubiKey;
use zeroize::Zeroize; 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::compress::GzStreamDecoder;
use crate::config::TinyEncryptConfig; use crate::config::TinyEncryptConfig;
use crate::consts::{ use crate::consts::{
@@ -99,7 +99,7 @@ pub fn decrypt_single(config: &Option<TinyEncryptConfig>,
let path_out = &path_display[0..path_display.len() - TINY_ENC_FILE_EXT.len()]; let path_out = &path_display[0..path_display.len() - TINY_ENC_FILE_EXT.len()];
util::require_file_not_exists(path_out)?; 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 key = try_decrypt_key(config, selected_envelop, pin, slot)?;
let nonce = opt_result!(util::decode_base64(&meta.nonce), "Decode nonce failed: {}"); 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<String>) -> XR
Ok(key) Ok(key)
} }
fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> { fn select_envelop<'a>(meta: &'a TinyEncryptMeta, config: &Option<TinyEncryptConfig>) -> XResult<&'a TinyEncryptEnvelop> {
let envelops = match &meta.envelops { let envelops = match &meta.envelops {
None => return simple_error!("No envelops found"), None => return simple_error!("No envelops found"),
Some(envelops) => if envelops.is_empty() { Some(envelops) => if envelops.is_empty() {
@@ -310,21 +310,13 @@ fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> {
success!("Found {} envelops:", envelops.len()); success!("Found {} envelops:", envelops.len());
if envelops.len() == 1 { if envelops.len() == 1 {
let selected_envelop = &envelops[0]; 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: "); util::read_line("Press enter to continue: ");
return Ok(selected_envelop); return Ok(selected_envelop);
} }
envelops.iter().enumerate().for_each(|(i, envelop)| { envelops.iter().enumerate().for_each(|(i, envelop)| {
let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid)); println!("#{} {}", i + 1, util_envelop::format_envelop(envelop, &config));
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,
);
}); });
let envelop_number = util::read_number("Please select an envelop:", 1, envelops.len()); let envelop_number = util::read_number("Please select an envelop:", 1, envelops.len());

View File

@@ -11,7 +11,7 @@ use rust_util::{debugging, failure, iff, information, opt_result, simple_error,
use rust_util::util_time::UnixEpochTime; use rust_util::util_time::UnixEpochTime;
use zeroize::Zeroize; 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::compress::GzStreamEncoder;
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop}; 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}; 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 { pub struct CmdEncrypt {
/// Files need to be decrypted /// Files need to be decrypted
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
/// Comment /// Plaintext comment
#[arg(long, short = 'c')] #[arg(long, short = 'c')]
pub comment: Option<String>, pub comment: Option<String>,
/// Encrypted comment /// Encrypted comment
#[arg(long, short = 'C')] #[arg(long, short = 'C')]
pub encrypted_comment: Option<String>, pub encrypted_comment: Option<String>,
/// Encryption profile /// Encryption profile (use default when --key-filter is assigned)
#[arg(long, short = 'p')] #[arg(long, short = 'p')]
pub profile: Option<String>, pub profile: Option<String>,
/// 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<String>,
/// Compress before encrypt /// Compress before encrypt
#[arg(long, short = 'x')] #[arg(long, short = 'x')]
pub compress: bool, pub compress: bool,
/// Compress level (from 0[none], 1[fast] .. 6[default] .. to 9[best]) /// Compress level (from 0[none], 1[fast] .. 6[default] .. to 9[best])
#[arg(long, short = 'L')] #[arg(long, short = 'L')]
pub compress_level: Option<u32>, pub compress_level: Option<u32>,
/// Compatible with 1.0 /// Compatible with 1.0 (requires assign --disable-compress-meta)
#[arg(long, short = '1')] #[arg(long, short = '1')]
pub compatible_with_1_0: bool, pub compatible_with_1_0: bool,
/// Remove source file /// Remove source file
@@ -53,7 +56,7 @@ pub struct CmdEncrypt {
pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> { pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?; let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?;
debugging!("Found tiny encrypt config: {:?}", config); 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"); } if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
debugging!("Found envelops: {:?}", envelops); debugging!("Found envelops: {:?}", envelops);
let envelop_tkids: Vec<_> = envelops.iter() let envelop_tkids: Vec<_> = envelops.iter()
@@ -306,7 +309,7 @@ fn encrypt_envelop_shared_secret(key: &[u8],
Ok(TinyEncryptEnvelop { Ok(TinyEncryptEnvelop {
r#type: envelop.r#type, r#type: envelop.r#type,
kid: envelop.kid.clone(), kid: envelop.kid.clone(),
desc: envelop.desc.clone(), desc: None, // envelop.desc.clone(),
encrypted_key: encoded_wrap_key, encrypted_key: encoded_wrap_key,
}) })
} }
@@ -318,7 +321,7 @@ fn encrypt_envelop_pgp(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResul
Ok(TinyEncryptEnvelop { Ok(TinyEncryptEnvelop {
r#type: envelop.r#type, r#type: envelop.r#type,
kid: envelop.kid.clone(), kid: envelop.kid.clone(),
desc: envelop.desc.clone(), desc: None, // envelop.desc.clone(),
encrypted_key: util::encode_base64(&encrypted_key), encrypted_key: util::encode_base64(&encrypted_key),
}) })
} }

View File

@@ -4,12 +4,13 @@ use std::path::PathBuf;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use clap::Args; 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 rust_util::util_time::UnixEpochTime;
use simpledateformat::format_human2; use simpledateformat::format_human2;
use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM, TINY_ENC_FILE_EXT}; use crate::{util_enc_file, util_envelop};
use crate::util_enc_file; 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)] #[derive(Debug, Args)]
pub struct CmdInfo { 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); 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 mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
let meta = opt_result!( let meta = opt_result!(
util_enc_file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display 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() { if let Some(envelops) = meta.envelops.as_ref() {
envelops.iter().enumerate().for_each(|(i, envelop)| { envelops.iter().enumerate().for_each(|(i, envelop)| {
let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid)); infos.push(format!("{}: {}",
let desc = envelop.desc.as_ref().map(|desc| format!(", Desc: {}", desc)).unwrap_or_else(|| "".to_string());
infos.push(format!("{}: {}{}{}",
header(&format!("Envelop #{}", i + 1)), header(&format!("Envelop #{}", i + 1)),
envelop.r#type.get_upper_name(), util_envelop::format_envelop(envelop, &config)
kid,
desc
)); ));
}) })
} }

View File

@@ -41,6 +41,7 @@ pub struct TinyEncryptConfig {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TinyEncryptConfigEnvelop { pub struct TinyEncryptConfigEnvelop {
pub r#type: TinyEncryptEnvelopType, pub r#type: TinyEncryptEnvelopType,
pub sid: Option<String>,
pub kid: String, pub kid: String,
pub desc: Option<String>, pub desc: Option<String>,
pub args: Option<Vec<String>>, pub args: Option<Vec<String>>,
@@ -81,32 +82,66 @@ impl TinyEncryptConfig {
} }
pub fn find_by_kid(&self, kid: &str) -> Option<&TinyEncryptConfigEnvelop> { 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<String>) -> XResult<Vec<&TinyEncryptConfigEnvelop>> { pub fn find_by_kid_or_type(&self, k_filter: &str) -> Vec<&TinyEncryptConfigEnvelop> {
let profile = profile.as_ref().map(String::as_str).unwrap_or("default"); self.find_by_kid_or_filter(k_filter, |e| {
debugging!("Profile: {}", profile); k_filter == &format!("type:{}", &e.r#type.get_name())
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_filter<F>(&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 { if let Some(sid) = &e.sid {
self.envelops.iter().for_each(|envelop| { return sid == kid;
let is_matched = (&envelop.kid == key_id) }
|| key_id == &format!("type:{}", &envelop.r#type.get_name()); f(e)
if is_matched { }).collect()
matched_envelops_map.insert(&envelop.kid, envelop); }
}
}); pub fn find_envelops(&self, profile: &Option<String>, key_filter: &Option<String>) -> XResult<Vec<&TinyEncryptConfigEnvelop>> {
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() let mut envelops: Vec<_> = matched_envelops_map.values()
.copied() .copied()
.collect(); .collect();
if envelops.is_empty() { 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| { envelops.sort_by(|e1, e2| {
if e1.r#type < e2.r#type { return Ordering::Greater; } if e1.r#type < e2.r#type { return Ordering::Greater; }

View File

@@ -22,6 +22,7 @@ mod spec;
mod crypto_aes; mod crypto_aes;
mod crypto_rsa; mod crypto_rsa;
mod wrap_key; mod wrap_key;
mod util_envelop;
mod util_file; mod util_file;
mod util_enc_file; mod util_enc_file;
mod cmd_version; mod cmd_version;

25
src/util_envelop.rs Normal file
View File

@@ -0,0 +1,25 @@
use rust_util::iff;
use crate::config::TinyEncryptConfig;
use crate::spec::TinyEncryptEnvelop;
pub fn format_envelop(envelop: &TinyEncryptEnvelop, config: &Option<TinyEncryptConfig>) -> 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<TinyEncryptConfig>) -> Option<String> {
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
}