diff --git a/Cargo.lock b/Cargo.lock index 296ed50..fdf74b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -626,6 +626,19 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "external-command-rs" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb012063f6c7e1cb7a5e9e6ad789ad941035e563c7d0b214b05cc3493aeb7cf9" +dependencies = [ + "base64 0.22.1", + "hex", + "rust_util", + "serde", + "serde_json", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1995,7 +2008,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.9.11" +version = "1.9.12" dependencies = [ "aes-gcm-stream", "base64 0.22.1", @@ -2004,6 +2017,7 @@ dependencies = [ "clap", "ctrlc", "dialoguer", + "external-command-rs", "flate2", "fs-set-times", "hex", diff --git a/Cargo.toml b/Cargo.toml index 2e046a7..b87d360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.9.11" +version = "1.9.12" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" @@ -53,6 +53,7 @@ dialoguer = "0.11" ctrlc = "3.4" swift-secure-enclave-tool-rs = "1.0" json5 = "0.4" +external-command-rs = "0.1" [profile.release] codegen-units = 1 diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index 6272d46..11be5c6 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -38,6 +38,7 @@ use crate::crypto_cryptor::{Cryptor, KeyNonce}; use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta}; use crate::util::SecVec; use crate::util_digest::DigestWrite; +use crate::util_env::TINY_ENCRYPT_ENV_EXTERNAL_COMMAND; use crate::util_keychainkey; #[cfg(feature = "macos")] use crate::util_keychainstatic; @@ -467,6 +468,7 @@ pub fn try_decrypt_key(config: &Option, TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop), TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot, silent), TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop), + TinyEncryptEnvelopType::ExtP256 | TinyEncryptEnvelopType::ExtP384 => try_decrypt_ext_key_ecdh(config, envelop), TinyEncryptEnvelopType::PivRsa => try_decrypt_piv_key_rsa(config, envelop, pin, slot, silent), #[cfg(feature = "macos")] TinyEncryptEnvelopType::StaticKyber1024 => try_decrypt_key_ecdh_static_kyber1204(config, envelop), @@ -597,6 +599,45 @@ fn try_decrypt_se_key_ecdh(config: &Option, Ok(decrypted_key) } +fn try_decrypt_ext_key_ecdh(config: &Option, + envelop: &TinyEncryptEnvelop) -> XResult> { + let wrap_key = WrapKey::parse(&envelop.encrypted_key)?; + let cryptor = match wrap_key.header.enc.as_str() { + ENC_AES256_GCM_P256 | ENC_AES256_GCM_P384 => Cryptor::Aes256Gcm, + ENC_CHACHA20_POLY1305_P256 | ENC_CHACHA20_POLY1305_P384 => Cryptor::ChaCha20Poly1305, + _ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc), + }; + let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?; + + let config = opt_value_result!(config, "Tiny encrypt config is not found"); + let config_envelop = opt_value_result!( + config.find_by_kid(&envelop.kid), "Cannot find config for: {}", &envelop.kid); + let config_envelop_args = opt_value_result!(&config_envelop.args, "No arguments found for: {}", &envelop.kid); + if config_envelop_args.len() < 2 { + return simple_error!("Not enough arguments for: {}", &envelop.kid); + } + + let external_command = if config_envelop_args[0].is_empty() { + std::env::var(TINY_ENCRYPT_ENV_EXTERNAL_COMMAND).unwrap_or_else(|_| "card-cli".to_string()) + } else { + config_envelop_args[0].clone() + }; + let external_parameter = &config_envelop_args[1]; + + let shared_secret = opt_result!(external_command_rs::external_ecdh( + &external_command, + external_parameter, + &e_pub_key_bytes + ), "Decrypt via secure enclave failed: {}"); + let key = util::simple_kdf(shared_secret.as_slice()); + let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce }; + let decrypted_key = crypto_simple::decrypt( + cryptor, &key_nonce, &wrap_key.encrypted_data)?; + util::zeroize(key); + util::zeroize(shared_secret); + Ok(decrypted_key) +} + fn try_decrypt_key_ecdh_pgp_x25519(envelop: &TinyEncryptEnvelop, pin: &Option) -> XResult> { let wrap_key = WrapKey::parse(&envelop.encrypted_key)?; let cryptor = match wrap_key.header.enc.as_str() { diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index 333b444..129e4e9 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -313,10 +313,10 @@ pub fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptCo TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => { encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?); } - TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 => { + TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 | TinyEncryptEnvelopType::ExtP256 => { encrypted_envelops.push(encrypt_envelop_ecdh_p256(cryptor, key, envelop)?); } - TinyEncryptEnvelopType::PivP384 => { + TinyEncryptEnvelopType::PivP384 | TinyEncryptEnvelopType::ExtP384 => { encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?); } TinyEncryptEnvelopType::StaticKyber1024 => { diff --git a/src/spec.rs b/src/spec.rs index a2a1db4..1ae940f 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -92,6 +92,12 @@ pub enum TinyEncryptEnvelopType { // PIV ECDH P384 #[serde(rename = "piv-p384", alias = "ecdh-p384")] PivP384, + // External ECDH P256 + #[serde(rename = "ext-p256")] + ExtP256, + // External ECDH P384 + #[serde(rename = "ext-p384")] + ExtP384, // PIV RSA #[serde(rename = "piv-rsa")] PivRsa, @@ -116,6 +122,8 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::StaticX25519 => "static-x25519", TinyEncryptEnvelopType::StaticKyber1024 => "static-kyber1024", TinyEncryptEnvelopType::KeyP256 => "key-p256", + TinyEncryptEnvelopType::ExtP256 => "ext-p256", + TinyEncryptEnvelopType::ExtP384 => "ext-p384", TinyEncryptEnvelopType::PivP256 => "piv-p256", TinyEncryptEnvelopType::PivP384 => "piv-p384", TinyEncryptEnvelopType::PivRsa => "piv-rsa", @@ -133,6 +141,8 @@ impl TinyEncryptEnvelopType { | TinyEncryptEnvelopType::Kms => true, TinyEncryptEnvelopType::PgpRsa | TinyEncryptEnvelopType::PgpX25519 + | TinyEncryptEnvelopType::ExtP256 + | TinyEncryptEnvelopType::ExtP384 | TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 | TinyEncryptEnvelopType::PivRsa @@ -153,7 +163,9 @@ impl TinyEncryptEnvelopType { | TinyEncryptEnvelopType::StaticKyber1024 | TinyEncryptEnvelopType::Kms => Some(false), // GPG is unknown(hardware/software) - TinyEncryptEnvelopType::Gpg => None, + TinyEncryptEnvelopType::Gpg + | TinyEncryptEnvelopType::ExtP256 + | TinyEncryptEnvelopType::ExtP384 => None, } } } diff --git a/src/util_env.rs b/src/util_env.rs index efbb92a..ec1be55 100644 --- a/src/util_env.rs +++ b/src/util_env.rs @@ -16,6 +16,7 @@ pub const TINY_ENCRYPT_EVN_AUTO_COMPRESS_EXTS: &str = "TINY_ENCRYPT_AUTO_COMPRES pub const TINY_ENCRYPT_ENV_GPG_COMMAND: &str = "TINY_ENCRYPT_GPG_COMMAND"; pub const TINY_ENCRYPT_ENV_NO_DEFAULT_PIN_HINT: &str = "TINY_ENCRYPT_NO_DEFAULT_PIN_HINT"; pub const TINY_ENCRYPT_ENV_PIN_ENTRY: &str = "TINY_ENCRYPT_PIN_ENTRY"; +pub const TINY_ENCRYPT_ENV_EXTERNAL_COMMAND: &str = "TINY_ENCRYPT_EXTERNAL_COMMAND"; pub fn get_default_encryption_algorithm() -> Option<&'static str> { let env_default_algorithm = rust_util_env::env_var(TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM);