diff --git a/Cargo.lock b/Cargo.lock index fdf74b6..5447b44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2008,7 +2008,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.9.12" +version = "1.9.13" dependencies = [ "aes-gcm-stream", "base64 0.22.1", @@ -2027,6 +2027,7 @@ dependencies = [ "openpgp-card-pcsc", "p256", "p384", + "percent-encoding", "pinentry", "pqcrypto-kyber", "pqcrypto-traits", diff --git a/Cargo.toml b/Cargo.toml index b87d360..048f4cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.9.12" +version = "1.9.13" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" @@ -54,6 +54,7 @@ ctrlc = "3.4" swift-secure-enclave-tool-rs = "1.0" json5 = "0.4" external-command-rs = "0.1" +percent-encoding = "2.3" [profile.release] codegen-units = 1 diff --git a/src/cmd_config.rs b/src/cmd_config.rs index ce12df0..f4cc7af 100644 --- a/src/cmd_config.rs +++ b/src/cmd_config.rs @@ -7,6 +7,7 @@ use tabled::{Table, Tabled}; use tabled::settings::Style; use crate::config::TinyEncryptConfig; +use crate::temporary_key::serialize_config_envelop; use crate::util_envelop; #[derive(Tabled, Eq)] @@ -50,6 +51,9 @@ pub struct CmdConfig { /// JSON output #[arg(long)] pub json: bool, + /// Temporary key output + #[arg(long)] + pub temporary_key: bool, /// Encryption profile (use default when --key-filter is assigned) #[arg(long, short = 'p')] pub profile: Option, @@ -71,6 +75,14 @@ pub fn config(cmd_config: CmdConfig) -> XResult<()> { return Ok(()); } + if cmd_config.temporary_key { + for envelop in &config.envelops { + let k = serialize_config_envelop(envelop); + println!("{}", k); + } + return Ok(()); + } + if cmd_config.profile.is_some() || cmd_config.key_filter.is_some() { return config_key_filter(&cmd_config, &config); } diff --git a/src/cmd_simple_encrypt_decrypt.rs b/src/cmd_simple_encrypt_decrypt.rs index 5adb72d..bea21b6 100644 --- a/src/cmd_simple_encrypt_decrypt.rs +++ b/src/cmd_simple_encrypt_decrypt.rs @@ -9,6 +9,7 @@ use serde::Serialize; use std::io; use std::io::Write; use std::process::exit; +use crate::temporary_key::deserialize_config_envelop; use crate::util_simple_pbe::SimplePbkdfEncryptionV1; // Reference: https://git.hatter.ink/hatter/tiny-encrypt-rs/issues/3 @@ -25,6 +26,10 @@ pub struct CmdSimpleEncrypt { #[arg(long, short = 'k')] pub key_filter: Option, + /// Temporary key + #[arg(long)] + pub temporary_key: Option>, + /// Encrypt value from stdin #[arg(long)] pub value_stdin: bool, @@ -190,9 +195,26 @@ pub fn simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> { pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> { let config = TinyEncryptConfig::load_default()?; debugging!("Found tiny encrypt config: {:?}", config); - let envelops = config.find_envelops(&cmd_simple_encrypt.profile, &cmd_simple_encrypt.key_filter)?; - if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); } + + let mut envelops = config.find_envelops( + &cmd_simple_encrypt.profile, + &cmd_simple_encrypt.key_filter)?; debugging!("Found envelops: {:?}", envelops); + + let mut temporary_envelops = vec![]; + if let Some(temporary_key) = &cmd_simple_encrypt.temporary_key { + for t_key in temporary_key { + let envelop = opt_result!(deserialize_config_envelop(t_key), "Parse temporary key: {} failed: {}", t_key); + temporary_envelops.push(envelop); + } + // FIXME should check kid not exists + for t_envelop in &mut temporary_envelops { + envelops.push(t_envelop) + } + debugging!("Final envelops: {:?}", envelops); + } + if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); } + let envelop_tkids: Vec<_> = envelops.iter() .map(|e| format!("{}:{}", e.r#type.get_name(), e.kid)) .collect(); diff --git a/src/config.rs b/src/config.rs index 0e04e93..d0aafbc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use std::{env, fs}; use rust_util::util_env as rust_util_env; use rust_util::util_file::resolve_file_path; -use rust_util::{debugging, opt_result, simple_error, warning, XResult}; +use rust_util::{debugging, opt_result, warning, XResult}; use serde::{Deserialize, Serialize}; use crate::consts::{ENV_TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE_2, TINY_ENC_CONFIG_FILE_3, TINY_ENC_FILE_EXT}; use crate::spec::TinyEncryptEnvelopType; @@ -257,7 +257,8 @@ impl TinyEncryptConfig { }); } if key_ids.is_empty() { - return simple_error!("Profile or key filter cannot find any valid envelopes"); + // return simple_error!("Profile or key filter cannot find any valid envelopes"); + return Ok(vec![]); } for key_id in &key_ids { for envelop in self.find_by_kid_or_type(key_id) { @@ -267,7 +268,8 @@ impl TinyEncryptConfig { let mut envelops: Vec<_> = matched_envelops_map.values().copied().collect(); if envelops.is_empty() { - return simple_error!("Profile or key filter cannot find any valid envelopes"); + // return simple_error!("Profile or key filter cannot find any valid envelopes"); + return Ok(vec![]); } envelops.sort_by(|e1, e2| { if e1.r#type < e2.r#type { diff --git a/src/lib.rs b/src/lib.rs index 58a5abb..779bcaa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,4 +77,5 @@ mod cmd_execenv; mod util_keychainkey; mod util_simple_pbe; mod util_log; +mod temporary_key; diff --git a/src/spec.rs b/src/spec.rs index 1ae940f..b6e3d52 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -132,6 +132,25 @@ impl TinyEncryptEnvelopType { } } + pub fn from_name(name: &str) -> Option { + match name { + "pgp-rsa" => Some(TinyEncryptEnvelopType::PgpRsa), + "pgp-x25519" => Some(TinyEncryptEnvelopType::PgpX25519), + "gpg" => Some(TinyEncryptEnvelopType::Gpg), + "static-x25519" => Some(TinyEncryptEnvelopType::StaticX25519), + "static-kyber1024" => Some(TinyEncryptEnvelopType::StaticKyber1024), + "key-p256" => Some(TinyEncryptEnvelopType::KeyP256), + "ext-p256" => Some(TinyEncryptEnvelopType::ExtP256), + "ext-p384" => Some(TinyEncryptEnvelopType::ExtP384), + "piv-p256" => Some(TinyEncryptEnvelopType::PivP256), + "piv-p384" => Some(TinyEncryptEnvelopType::PivP384), + "piv-rsa" => Some(TinyEncryptEnvelopType::PivRsa), + "age" => Some(TinyEncryptEnvelopType::Age), + "kms" => Some(TinyEncryptEnvelopType::Kms), + _ => None, + } + } + pub fn auto_select(&self) -> bool { match self { TinyEncryptEnvelopType::StaticX25519 diff --git a/src/temporary_key.rs b/src/temporary_key.rs new file mode 100644 index 0000000..567b5aa --- /dev/null +++ b/src/temporary_key.rs @@ -0,0 +1,56 @@ +// syntax +// tiny-encrypt-key:type:sid:key_id:public_part[?key=value] +// e.g. +// tiny-encrypt-key:ext-p256:ext-key-1:02536aef5742b4288f1b44b3cc96f1556c35e4fac4e8e117e1f7ae091e42d0835b:04536aef5742b4288f1b44b3cc96f1556c35e4fac4e8e117e1f7ae091e42d0835bf3f95d932c22a74a91859bd7fdd8829a02d38cf4ec598b1cf6e02fa09f707a6f + +use crate::config::TinyEncryptConfigEnvelop; +use crate::spec::TinyEncryptEnvelopType; +use rust_util::{iff, opt_result, opt_value_result, simple_error, XResult}; + +const TINY_ENCRYPT_KEY_PREFIX: &str = "tiny-encrypt-key:"; + +pub fn serialize_config_envelop(config_envelop: &TinyEncryptConfigEnvelop) -> String { + let mut s = String::new(); + s.push_str(TINY_ENCRYPT_KEY_PREFIX); + s.push_str(config_envelop.r#type.get_name()); + s.push(':'); + s.push_str(&encode(config_envelop.sid.as_deref().unwrap_or(""))); + s.push(':'); + s.push_str(&encode(&config_envelop.kid)); + s.push(':'); + s.push_str(&encode(&config_envelop.public_part)); + s +} + +pub fn deserialize_config_envelop(k: &str) -> XResult { + if !k.starts_with(TINY_ENCRYPT_KEY_PREFIX) { + return simple_error!("invalid temporary key"); + } + let k_parts = k.split(":").collect::>(); + if k_parts.len() != 5 { + return simple_error!("invalid temporary key (parts)"); + } + let envelop_type = opt_value_result!( + TinyEncryptEnvelopType::from_name(k_parts[1]), "Unknown envelop type: {}", k_parts[1]); + Ok(TinyEncryptConfigEnvelop { + r#type: envelop_type, + sid: iff!(k_parts[2].is_empty(), None, Some(decode(k_parts[2])?)), + kid: decode(k_parts[3])?, + desc: None, + args: None, + public_part: decode(k_parts[4])?, + }) +} + +fn encode(s: &str) -> String { + percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).to_string() +} + +fn decode(s: &str) -> XResult { + Ok(opt_result!( + percent_encoding::percent_decode_str(s).decode_utf8(), + "decode: {} failed: {}", + s + ) + .to_string()) +}