diff --git a/Cargo.lock b/Cargo.lock index a7ff70f..6f0b1fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1753,7 +1753,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.6.1" +version = "1.7.0" dependencies = [ "aes-gcm-stream", "base64", diff --git a/Cargo.toml b/Cargo.toml index be4bf0a..93cb037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.6.1" +version = "1.7.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 1cef335..a3ad41e 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -20,10 +20,7 @@ use yubikey::piv::{AlgorithmId, decrypt_data}; use yubikey::YubiKey; use zeroize::Zeroize; -use crate::{ - cmd_encrypt, config, consts, crypto_simple, - util, util_enc_file, util_env, util_envelop, util_file, util_pgp, util_piv, -}; +use crate::{cmd_encrypt, config, consts, crypto_simple, util, util_enc_file, util_env, util_envelop, util_file, util_gpg, util_pgp, util_piv}; use crate::compress::GzStreamDecoder; use crate::config::TinyEncryptConfig; use crate::consts::{ @@ -461,6 +458,7 @@ pub fn try_decrypt_key(config: &Option, match envelop.r#type { TinyEncryptEnvelopType::PgpRsa => try_decrypt_key_pgp_rsa(envelop, pin), TinyEncryptEnvelopType::PgpX25519 => try_decrypt_key_ecdh_pgp_x25519(envelop, pin), + TinyEncryptEnvelopType::Gpg => try_decrypt_key_gpg(envelop), #[cfg(feature = "macos")] TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop), TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot), @@ -613,6 +611,10 @@ fn try_decrypt_key_ecdh_pgp_x25519(envelop: &TinyEncryptEnvelop, pin: &Option XResult> { + Ok(util_gpg::gpg_decrypt(&envelop.encrypted_key)?) +} + #[cfg(feature = "macos")] fn try_decrypt_key_ecdh_static_x25519(config: &Option, envelop: &TinyEncryptEnvelop) -> XResult> { let wrap_key = WrapKey::parse(&envelop.encrypted_key)?; diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index c2ff9ea..b8833cb 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, util_size, XResult}; use rust_util::util_time::UnixEpochTime; -use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env}; +use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env, util_gpg}; use crate::compress::GzStreamEncoder; use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop}; use crate::consts::{ @@ -23,13 +23,13 @@ use crate::consts::{ SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT, }; use crate::crypto_cryptor::{Cryptor, KeyNonce}; -use crate::util_rsa; use crate::spec::{ EncEncryptedMeta, EncMetadata, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta, }; use crate::util_ecdh::{ecdh_kyber1024, ecdh_p256, ecdh_p384, ecdh_x25519}; use crate::util_progress::Progress; +use crate::util_rsa; use crate::wrap_key::{WrapKey, WrapKeyHeader}; #[derive(Debug, Args)] @@ -284,6 +284,9 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig TinyEncryptEnvelopType::PgpRsa | TinyEncryptEnvelopType::PivRsa => { encrypted_envelops.push(encrypt_envelop_rsa(key, envelop)?); } + TinyEncryptEnvelopType::Gpg => { + encrypted_envelops.push(encrypt_envelop_gpg(key, envelop)?); + } TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => { encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?); } @@ -382,6 +385,16 @@ fn encrypt_envelop_rsa(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResul }) } +fn encrypt_envelop_gpg(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult { + let encrypted_key = opt_result!(util_gpg::gpg_encrypt(&envelop.public_part, key), "GPG encrypt failed: {}"); + Ok(TinyEncryptEnvelop { + r#type: envelop.r#type, + kid: envelop.kid.clone(), + desc: None, + 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())) diff --git a/src/spec.rs b/src/spec.rs index dbc3165..bb9d045 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -20,6 +20,7 @@ pub struct TinyEncryptMeta { pub version: String, pub created: u64, pub user_agent: String, + #[serde(skip_serializing_if = "Option::is_none")] pub latest_user_agent: Option, #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, @@ -73,6 +74,9 @@ pub enum TinyEncryptEnvelopType { // OpenPGP Card X25519 #[serde(rename = "pgp-x25519")] PgpX25519, + // GPG Command Line + #[serde(rename = "gpg")] + Gpg, // Keychain Static X25519 (less secure) #[serde(rename = "static-x25519")] StaticX25519, @@ -108,6 +112,7 @@ impl TinyEncryptEnvelopType { match self { TinyEncryptEnvelopType::PgpRsa => "pgp-rsa", TinyEncryptEnvelopType::PgpX25519 => "pgp-x25519", + TinyEncryptEnvelopType::Gpg => "gpg", TinyEncryptEnvelopType::StaticX25519 => "static-x25519", TinyEncryptEnvelopType::StaticKyber1024 => "static-kyber1024", TinyEncryptEnvelopType::KeyP256 => "key-p256", @@ -124,6 +129,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::StaticX25519 | TinyEncryptEnvelopType::StaticKyber1024 | TinyEncryptEnvelopType::KeyP256 + | TinyEncryptEnvelopType::Gpg | TinyEncryptEnvelopType::Kms => true, TinyEncryptEnvelopType::PgpRsa | TinyEncryptEnvelopType::PgpX25519 @@ -145,6 +151,7 @@ impl TinyEncryptEnvelopType { | TinyEncryptEnvelopType::Age => true, TinyEncryptEnvelopType::StaticX25519 | TinyEncryptEnvelopType::StaticKyber1024 + | TinyEncryptEnvelopType::Gpg // GPG is unknown(hardware/software) | TinyEncryptEnvelopType::Kms => false, } } diff --git a/src/util_env.rs b/src/util_env.rs index f25b628..9afcb1f 100644 --- a/src/util_env.rs +++ b/src/util_env.rs @@ -11,6 +11,7 @@ pub const TINY_ENCRYPT_ENV_NO_PROGRESS: &str = "TINY_ENCRYPT_NO_PROGRESS"; pub const TINY_ENCRYPT_ENV_PIN: &str = "TINY_ENCRYPT_PIN"; pub const TINY_ENCRYPT_ENV_KEY_ID: &str = "TINY_ENCRYPT_KEY_ID"; pub const TINY_ENCRYPT_ENV_AUTO_SELECT_KEY_IDS: &str = "TINY_ENCRYPT_AUTO_SELECT_KEY_IDS"; +pub const TINY_ENCRYPT_ENV_GPG_COMMAND: &str = "TINY_ENCRYPT_GPG_COMMAND"; pub fn get_default_encryption_algorithm() -> Option<&'static str> { let env_default_algorithm = env::var(TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM).ok(); @@ -34,6 +35,10 @@ pub fn get_key_id() -> Option { env::var(TINY_ENCRYPT_ENV_KEY_ID).ok() } +pub fn get_gpg_cmd() -> Option { + env::var(TINY_ENCRYPT_ENV_GPG_COMMAND).ok() +} + pub fn get_auto_select_key_ids() -> Option> { env::var(TINY_ENCRYPT_ENV_AUTO_SELECT_KEY_IDS).ok().map(|key_ids| { key_ids.split(',').map(ToString::to_string).collect::>() diff --git a/src/util_gpg.rs b/src/util_gpg.rs index 3df7a84..6883cfd 100644 --- a/src/util_gpg.rs +++ b/src/util_gpg.rs @@ -41,6 +41,8 @@ use std::process::{Command, Stdio}; use rust_util::{opt_result, opt_value_result, simple_error, XResult}; +use crate::util_env; + pub fn gpg_encrypt(key_id: &str, message: &[u8]) -> XResult { let message_hex = hex::encode(message); let echo = opt_result!(Command::new("echo").arg(&message_hex) @@ -48,7 +50,7 @@ pub fn gpg_encrypt(key_id: &str, message: &[u8]) -> XResult { .spawn(), "echo message failed: {}"); let echo_stdout = opt_value_result!(echo.stdout, "Get echo stdout failed: none"); - let mut cmd = Command::new("gpg"); + let mut cmd = Command::new(&get_gpg_cmd()); let encrypt_result = cmd .args([ "-e", "-a", "--no-comment", @@ -62,7 +64,7 @@ pub fn gpg_encrypt(key_id: &str, message: &[u8]) -> XResult { let stderr = String::from_utf8_lossy(&encrypt_output.stderr).to_string(); if !encrypt_output.status.success() { return simple_error!( - "GPG encrypt failed: {:?}, stdout: {}, stderr: {}", + "GPG encrypt failed: {:?}\n- stdout: {}\n- stderr: {}", encrypt_output.status.code(), stdout, stderr ); } @@ -75,7 +77,7 @@ pub fn gpg_decrypt(message: &str) -> XResult> { .spawn(), "echo message failed: {}"); let echo_stdout = opt_value_result!(echo.stdout, "Get echo stdout failed: none"); - let mut cmd = Command::new("gpg"); + let mut cmd = Command::new(&get_gpg_cmd()); let encrypt_result = cmd .arg("-d") .stdin(Stdio::from(echo_stdout)) @@ -85,10 +87,14 @@ pub fn gpg_decrypt(message: &str) -> XResult> { let stderr = String::from_utf8_lossy(&decrypt_output.stderr).to_string(); if !decrypt_output.status.success() { return simple_error!( - "GPG decrypt failed: {:?}, stdout: {}, stderr: {}", + "GPG decrypt failed: {:?}\n- stdout: {}\n- stderr: {}", decrypt_output.status.code(), stdout, stderr ); } let decrypted = opt_result!(hex::decode(&stdout.trim()), "Decode decrypted message failed: {}"); Ok(decrypted) } + +fn get_gpg_cmd() -> String { + util_env::get_gpg_cmd().unwrap_or("gpg".to_string()) +}