From 373b3bcefc498e826dff1e5cd913993faf7a889d Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 28 Oct 2023 11:38:16 +0800 Subject: [PATCH] feat: v0.6.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_decrypt.rs | 4 +-- src/cmd_encrypt.rs | 61 ++++++++++++---------------------------------- src/consts.rs | 1 - src/spec.rs | 3 ++- src/util.rs | 27 ++++++++++++++------ src/util_digest.rs | 4 +++ src/util_env.rs | 14 ++++++++--- 9 files changed, 56 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5faaccf..b6a6124 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1727,7 +1727,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "0.5.4" +version = "0.6.0" dependencies = [ "aes-gcm-stream", "base64", diff --git a/Cargo.toml b/Cargo.toml index 2378734..64e76bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "0.5.4" +version = "0.6.0" 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 4e95dc4..e733de9 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -345,7 +345,7 @@ fn try_decrypt_key_ecdh_pgp_x25519(envelop: &TinyEncryptEnvelop, pin: &Option) -> XResult> { let mut pgp = util_pgp::get_openpgp()?; - let mut trans = opt_result!(pgp.transaction(), "Open card failed: {}"); + let mut trans = opt_result!(pgp.transaction(), "Connect OpenPGP card failed: {}"); util_pgp::read_and_verify_openpgp_pin(&mut trans, pin)?; diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index 985e842..6c2c595 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -10,7 +10,7 @@ use rsa::Pkcs1v15Encrypt; use rust_util::{debugging, failure, iff, information, opt_result, simple_error, success, XResult}; use rust_util::util_time::UnixEpochTime; -use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_p256, util_p384, util_x25519}; +use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env, util_p256, util_p384, util_x25519}; use crate::compress::GzStreamEncoder; use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop}; use crate::consts::{ @@ -21,7 +21,7 @@ use crate::consts::{ use crate::crypto_cryptor::{Cryptor, KeyNonce}; use crate::crypto_rsa; use crate::spec::{ - EncEncryptedMeta, EncMetadata, TINY_ENCRYPT_VERSION_10, + EncEncryptedMeta, EncMetadata, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta, }; use crate::util_progress::Progress; @@ -49,9 +49,6 @@ pub struct CmdEncrypt { /// Compress level (from 0[none], 1[fast] .. 6[default] .. to 9[best]) #[arg(long, short = 'L')] pub compress_level: Option, - /// Compatible with 1.0 (requires assign --disable-compress-meta) - #[arg(long, short = '1')] - pub compatible_with_1_0: bool, /// Remove source file #[arg(long, short = 'R')] pub remove_file: bool, @@ -136,6 +133,7 @@ pub fn encrypt_single_file_out(path: &PathBuf, path_out: &str, envelops: &[&Tiny let (key, nonce) = util::make_key256_and_nonce(); let key_nonce = KeyNonce { k: &key.0, n: &nonce.0 }; + // Encrypt session key to envelops let envelops = encrypt_envelops(cryptor, &key.0, envelops)?; let encrypted_comment = match &cmd_encrypt.encrypted_comment { @@ -154,38 +152,25 @@ pub fn encrypt_single_file_out(path: &PathBuf, path_out: &str, envelops: &[&Tiny let enc_encrypted_meta_bytes = opt_result!(enc_encrypted_meta.seal( cryptor, &key_nonce), "Seal enc-encrypted-meta failed: {}"); + let compress_level = get_compress_level(cmd_encrypt); + let enc_metadata = EncMetadata { comment: cmd_encrypt.comment.clone(), encrypted_comment, encrypted_meta: Some(util::encode_base64(&enc_encrypted_meta_bytes)), - compress: cmd_encrypt.compress, + compress: compress_level.is_some(), }; - let mut encrypt_meta = TinyEncryptMeta::new( + let encrypt_meta = TinyEncryptMeta::new( &file_metadata, &enc_metadata, cryptor, &nonce.0, envelops); debugging!("Encrypted meta: {:?}", encrypt_meta); - if cmd_encrypt.compatible_with_1_0 { - if !cmd_encrypt.disable_compress_meta { - return simple_error!("Compatible with 1.0 mode must turns --disable-compress-meta on."); - } - if let Cryptor::Aes256Gcm = Cryptor::Aes256Gcm {} else { - return simple_error!("Compatible with 1.0 mode must use AES/GCM."); - } - encrypt_meta = process_compatible_with_1_0(encrypt_meta)?; - if encrypt_meta.pgp_envelop.is_none() && encrypt_meta.ecdh_envelop.is_none() { - return simple_error!("Compatible with 1.0 mode must contains PGP or ECDH Envelop."); - } - } - let mut file_out = File::create(path_out)?; let compress_meta = !cmd_encrypt.disable_compress_meta; let _ = util_enc_file::write_tiny_encrypt_meta(&mut file_out, &encrypt_meta, compress_meta)?; - let compress_desc = iff!(cmd_encrypt.compress, " [with compress]", ""); + let compress_desc = iff!(compress_level.is_some(), " [with compress]", ""); - let compress_level = iff!(cmd_encrypt.compress, - Some(cmd_encrypt.compress_level.unwrap_or_else(|| Compression::default().level())), None); let start = Instant::now(); encrypt_file( &mut file_in, file_metadata.len(), &mut file_out, cryptor, @@ -199,28 +184,6 @@ pub fn encrypt_single_file_out(path: &PathBuf, path_out: &str, envelops: &[&Tiny Ok(file_metadata.len()) } -fn process_compatible_with_1_0(mut encrypt_meta: TinyEncryptMeta) -> XResult { - if let Some(envelops) = encrypt_meta.envelops { - let mut filter_envelops = vec![]; - for envelop in envelops { - if (envelop.r#type == TinyEncryptEnvelopType::Pgp) && encrypt_meta.pgp_envelop.is_none() { - encrypt_meta.pgp_fingerprint = Some(format!("KID:{}", envelop.kid)); - encrypt_meta.pgp_envelop = Some(envelop.encrypted_key.clone()); - } else if (envelop.r#type == TinyEncryptEnvelopType::Ecdh) && encrypt_meta.ecdh_envelop.is_none() { - encrypt_meta.ecdh_point = Some(format!("KID:{}", envelop.kid)); - encrypt_meta.ecdh_envelop = Some(envelop.encrypted_key.clone()); - } else { - filter_envelops.push(envelop); - } - } - encrypt_meta.envelops = if filter_envelops.is_empty() { None } else { Some(filter_envelops) }; - if encrypt_meta.envelops.is_none() { - encrypt_meta.version = TINY_ENCRYPT_VERSION_10.to_string(); - } - } - Ok(encrypt_meta) -} - fn encrypt_file(file_in: &mut File, file_len: u64, file_out: &mut impl Write, cryptor: Cryptor, key_nonce: &KeyNonce, compress_level: &Option) -> XResult { let compress = compress_level.is_some(); @@ -375,3 +338,11 @@ fn encrypt_envelop_pgp(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResul encrypted_key: util::encode_base64(&encrypted_key), }) } + +fn get_compress_level(cmd_encrypt: &CmdEncrypt) -> Option { + if cmd_encrypt.compress || util_env::get_default_compress().unwrap_or(false) { + Some(cmd_encrypt.compress_level.unwrap_or_else(|| Compression::default().level())) + } else { + None + } +} diff --git a/src/consts.rs b/src/consts.rs index 887ae44..105ecda 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -10,7 +10,6 @@ pub const ENC_CHACHA20_POLY1305_X25519: &str = "chacha20-poly1305-x25519"; pub const TINY_ENC_FILE_EXT: &str = ".tinyenc"; pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json"; -pub const TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM: &str = "TINY_ENCRYPT_DEFAULT_ALGORITHM"; pub const TINY_ENC_AES_GCM: &str = "AES/GCM"; pub const TINY_ENC_CHACHA20_POLY1305: &str = "CHACHA20/POLY1305"; diff --git a/src/spec.rs b/src/spec.rs index eba3c7e..028d13a 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -10,7 +10,8 @@ use crate::consts::SALT_META; use crate::crypto_cryptor::{Cryptor, KeyNonce}; use crate::util::{encode_base64, get_user_agent}; -pub const TINY_ENCRYPT_VERSION_10: &str = "1.0"; +// Compatible with 1.0 is removed from v0.6.0 +// pub const TINY_ENCRYPT_VERSION_10: &str = "1.0"; pub const TINY_ENCRYPT_VERSION_11: &str = "1.1"; /// Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-encrypt-format-spec/blob/main/TinyEncryptSpecv1.1.md) diff --git a/src/util.rs b/src/util.rs index 6fb69a9..572be38 100644 --- a/src/util.rs +++ b/src/util.rs @@ -9,6 +9,7 @@ use rust_util::{information, print_ex, simple_error, util_term, warning, XResult use zeroize::Zeroize; use crate::consts::TINY_ENC_FILE_EXT; +use crate::util_digest::DigestWrite; pub struct SecVec(pub Vec); @@ -93,14 +94,13 @@ pub fn make_key256_and_nonce() -> (SecVec, SecVec) { } pub fn simple_kdf(input: &[u8]) -> Vec { - let input = hex::decode(sha256::digest(input)).unwrap(); - let input = hex::decode(sha256::digest(input)).unwrap(); - let input = hex::decode(sha256::digest(input)).unwrap(); - let input = hex::decode(sha256::digest(input)).unwrap(); - let input = hex::decode(sha256::digest(input)).unwrap(); - let input = hex::decode(sha256::digest(input)).unwrap(); - let input = hex::decode(sha256::digest(input)).unwrap(); - hex::decode(sha256::digest(input)).unwrap() + let mut input = input.to_vec(); + for _ in 0..8 { + let mut sha256 = DigestWrite::sha256().expect("SHOULD NOT HAPPEN"); + sha256.write_all(&input).expect("SHOULD NOT HAPPEN"); + input = sha256.digest(); + } + input } pub fn decode_base64(input: &str) -> XResult> { @@ -213,3 +213,14 @@ pub fn ratio(numerator: u64, denominator: u64) -> String { let r = (numerator * 10000) / denominator; format!("{:.2}", r as f64 / 100f64) } + +#[test] +fn test_simple_kdf() { + assert_eq!("30edbc354e8cf656adcddbeefbf3f5073372cdc42e4eca2e797bda8abebb6a05", + hex::encode(simple_kdf(b""))); + assert_eq!("0624d2a57bcb50f70aa19bab9fa75af1ca66cc701c341df865d430e2e6d9d936", + hex::encode(simple_kdf(b"hello"))); + assert_eq!("43367d255eddedc3c84b692b68de6d3d21da28caad6abd20ed85a4f2c89706ad", + hex::encode(simple_kdf(b"hello world"))); +} + diff --git a/src/util_digest.rs b/src/util_digest.rs index abb31f9..bf6fa62 100644 --- a/src/util_digest.rs +++ b/src/util_digest.rs @@ -33,6 +33,10 @@ impl DigestWrite { } } + pub fn sha256() -> XResult { + Ok(Self { digest: Box::new(Sha256::new()) }) + } + pub fn digest(self) -> Vec { let mut digest = self.digest; let mut buf: Vec = repeat(0).take((digest.output_bits() + 7) / 8).collect(); diff --git a/src/util_env.rs b/src/util_env.rs index 10a12b6..3179b61 100644 --- a/src/util_env.rs +++ b/src/util_env.rs @@ -1,12 +1,16 @@ use std::env; -use rust_util::{debugging, warning}; +use rust_util::{debugging, iff, warning}; +use rust_util::util_env as rust_util_env; use crate::consts; +pub const TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM: &str = "TINY_ENCRYPT_DEFAULT_ALGORITHM"; +pub const TINY_ENCRYPT_ENV_DEFAULT_COMPRESS: &str = "TINY_ENCRYPT_DEFAULT_COMPRESS"; + pub fn get_default_encryption_algorithm() -> Option<&'static str> { - let env_default_algorithm = env::var(consts::TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM).ok(); - debugging!("Environment variable {} = {:?}", consts::TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM, env_default_algorithm); + let env_default_algorithm = env::var(TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM).ok(); + debugging!("Environment variable {} = {:?}", TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM, env_default_algorithm); if let Some(env_algorithm) = env_default_algorithm { let lower_default_algorithm = env_algorithm.to_lowercase(); match lower_default_algorithm.as_str() { @@ -16,4 +20,8 @@ pub fn get_default_encryption_algorithm() -> Option<&'static str> { } } None +} + +pub fn get_default_compress() -> Option { + iff!(rust_util_env::is_env_off(TINY_ENCRYPT_ENV_DEFAULT_COMPRESS), Some(true), None) } \ No newline at end of file