Merge pull request 'simple-encrypt-decrypt' (#4) from simple-encrypt-decrypt into master

Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2024-11-17 10:32:36 +08:00
7 changed files with 255 additions and 9 deletions

10
Cargo.lock generated
View File

@@ -885,9 +885,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.162"
version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libm"
@@ -1609,9 +1609,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
@@ -1870,7 +1870,7 @@ dependencies = [
[[package]]
name = "tiny-encrypt"
version = "1.7.15"
version = "1.8.0"
dependencies = [
"aes-gcm-stream",
"base64 0.22.1",

View File

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

View File

@@ -295,7 +295,7 @@ pub(crate) fn encrypt_file(file_in: &mut impl Read, file_len: u64, file_out: &mu
Ok(total_len)
}
fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResult<Vec<TinyEncryptEnvelop>> {
pub fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResult<Vec<TinyEncryptEnvelop>> {
let mut encrypted_envelops = vec![];
for envelop in envelops {
match envelop.r#type {

View File

@@ -0,0 +1,226 @@
use crate::cmd_decrypt::try_decrypt_key;
use crate::config::TinyEncryptConfig;
use crate::consts::TINY_ENC_CONFIG_FILE;
use crate::spec::TinyEncryptEnvelop;
use crate::{cmd_encrypt, crypto_cryptor, util, util_env};
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
use base64::Engine;
use clap::Args;
use rust_util::{debugging, opt_result, simple_error, XResult};
use serde::Serialize;
use std::process::exit;
// Reference: https://git.hatter.ink/hatter/tiny-encrypt-rs/issues/3
const SIMPLE_ENCRYPTION_HEADER: &str = "tinyencrypt-dir";
const SIMPLE_ENCRYPTION_DOT: &str = ".";
#[derive(Debug, Args)]
pub struct CmdSimpleEncrypt {
/// Encryption profile (use default when --key-filter is assigned)
#[arg(long, short = 'p')]
pub profile: Option<String>,
/// Encryption key filter (key_id or type:TYPE(e.g. ecdh, pgp, ecdh-p384, pgp-ed25519), multiple joined by ',', ALL for all)
#[arg(long, short = 'k')]
pub key_filter: Option<String>,
/// Encrypt value from stdin
#[arg(long)]
pub value_stdin: bool,
/// Encrypt value
#[arg(long, short = 'v')]
pub value: Option<String>,
/// Encrypt value in bse64
#[arg(long)]
pub value_base64: Option<String>,
/// Encrypt value in hex
#[arg(long)]
pub value_hex: Option<String>,
}
#[derive(Debug, Args)]
pub struct CmdSimpleDecrypt {
/// PGP or PIV PIN
#[arg(long, short = 'p')]
pub pin: Option<String>,
/// Decrypt key ID
#[arg(long, short = 'k')]
pub key_id: Option<String>,
/// PIV slot
#[arg(long, short = 's')]
pub slot: Option<String>,
/// Decrypt value from stdin
#[arg(long)]
pub value_stdin: bool,
/// Decrypt value
#[arg(long, short = 'v')]
pub value: Option<String>,
/// Decrypt result output type (plain, hex, bse64)
#[arg(long, short = 'o')]
pub output_type: Option<String>,
}
impl CmdSimpleEncrypt {
pub fn get_value(&self) -> XResult<Option<Vec<u8>>> {
if self.value_stdin {
return Ok(Some(util::read_stdin()?));
}
if let Some(value) = &self.value {
return Ok(Some(value.as_bytes().to_vec()));
}
if let Some(value_base64) = &self.value_base64 {
return Ok(Some(opt_result!(STANDARD.decode(value_base64), "Parse value base64 failed: {}")));
}
if let Some(value_hex) = &self.value_hex {
return Ok(Some(opt_result!(hex::decode(value_hex), "Parse value hex failed: {}")));
}
Ok(None)
}
}
impl CmdSimpleDecrypt {
pub fn get_value(&self) -> XResult<Option<String>> {
if self.value_stdin {
return Ok(Some(opt_result!(String::from_utf8(util::read_stdin()?), "Read stdin value failed: {}")));
}
Ok(self.value.clone())
}
}
#[derive(Serialize)]
pub struct CmdResult {
pub code: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
}
impl CmdResult {
pub fn fail(code: i32, message: &str) -> Self {
Self {
code,
message: Some(message.to_string()),
result: None,
}
}
pub fn success(result: &str) -> Self {
Self {
code: 0,
message: None,
result: Some(result.to_string()),
}
}
pub fn print_exit(&self) -> ! {
let result = serde_json::to_string_pretty(self).unwrap();
println!("{}", result);
exit(self.code)
}
}
pub fn simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
if let Err(inner_result_error) = inner_simple_encrypt(cmd_simple_encrypt) {
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit();
}
Ok(())
}
pub fn simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
if let Err(inner_result_error) = inner_simple_decrypt(cmd_simple_decrypt) {
CmdResult::fail(-1, &format!("{}", inner_result_error)).print_exit();
}
Ok(())
}
pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?;
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"); }
debugging!("Found envelops: {:?}", envelops);
let envelop_tkids: Vec<_> = envelops.iter()
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
.collect();
debugging!("Matched {} envelop(s): \n- {}", envelops.len(), envelop_tkids.join("\n- "));
if envelop_tkids.is_empty() {
return simple_error!("no matched envelops found");
}
let value = match cmd_simple_encrypt.get_value()? {
None => return simple_error!("--value-stdin/value/value-base64/value-hex must assign one"),
Some(value) => value,
};
let cryptor = crypto_cryptor::get_cryptor_by_encryption_algorithm(&None)?;
let envelops = cmd_encrypt::encrypt_envelops(cryptor, &value, &envelops)?;
let envelops_json = serde_json::to_string(&envelops)?;
let simple_encrypt_result = format!("{}.{}",
SIMPLE_ENCRYPTION_HEADER,
URL_SAFE_NO_PAD.encode(envelops_json.as_bytes())
);
CmdResult::success(&simple_encrypt_result).print_exit();
}
pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE).ok();
let pin = cmd_simple_decrypt.pin.clone().or_else(util_env::get_pin);
let slot = cmd_simple_decrypt.slot.clone();
let output_type = cmd_simple_decrypt.output_type.as_deref().unwrap_or("plain");
match output_type {
"plain" | "hex" | "base64" => (),
_ => return simple_error!("not supported output type: {}", output_type),
};
let value = match cmd_simple_decrypt.get_value()? {
None => return simple_error!("--value-stdin/value must assign one"),
Some(value) => value,
};
let value_parts = value.trim().split(SIMPLE_ENCRYPTION_DOT).collect::<Vec<_>>();
if value_parts.len() != 2 {
return simple_error!("bad value format: {}", value);
}
if value_parts[0] != SIMPLE_ENCRYPTION_HEADER {
return simple_error!("bad value format: {}", value);
}
let envelopes_json = opt_result!(URL_SAFE_NO_PAD.decode(&value_parts[1]), "bad value format: {}");
let envelops: Vec<TinyEncryptEnvelop> = match serde_json::from_slice(&envelopes_json) {
Err(_) => return simple_error!("bad value format: {}", value),
Ok(value) => value,
};
let filter_envelops = envelops.iter().filter(|e| {
match &cmd_simple_decrypt.key_id {
None => true,
Some(key_id) => &e.kid == key_id,
}
}).collect::<Vec<_>>();
if filter_envelops.is_empty() {
return simple_error!("no envelops found: {:?}", cmd_simple_decrypt.key_id);
}
if filter_envelops.len() > 1 {
return simple_error!("too many envelops: {:?}, len: {}", cmd_simple_decrypt.key_id, filter_envelops.len());
}
let value = try_decrypt_key(&config, filter_envelops[0], &pin, &slot, false)?;
let value = match output_type {
"plain" => opt_result!(String::from_utf8(value), "bad value encoding: {}"),
"hex" => hex::encode(&value),
"base64" => STANDARD.encode(&value),
_ => return simple_error!("not supported output type: {}", output_type),
};
CmdResult::success(&value).print_exit();
}

View File

@@ -9,9 +9,13 @@ pub use cmd_decrypt::decrypt_single;
pub use cmd_directdecrypt::CmdDirectDecrypt;
pub use cmd_directdecrypt::direct_decrypt;
pub use cmd_encrypt::CmdEncrypt;
pub use cmd_simple_encrypt_decrypt::CmdSimpleEncrypt;
pub use cmd_simple_encrypt_decrypt::CmdSimpleDecrypt;
pub use cmd_encrypt::encrypt;
pub use cmd_encrypt::encrypt_single;
pub use cmd_encrypt::encrypt_single_file_out;
pub use cmd_simple_encrypt_decrypt::simple_encrypt;
pub use cmd_simple_encrypt_decrypt::simple_decrypt;
#[cfg(feature = "decrypt")]
pub use cmd_execenv::CmdExecEnv;
#[cfg(feature = "decrypt")]
@@ -57,6 +61,7 @@ mod cmd_info;
#[cfg(feature = "decrypt")]
mod cmd_decrypt;
mod cmd_encrypt;
mod cmd_simple_encrypt_decrypt;
mod cmd_directdecrypt;
#[cfg(feature = "macos")]
mod cmd_initkeychain;

View File

@@ -3,7 +3,6 @@ extern crate core;
use clap::{Parser, Subcommand};
use rust_util::XResult;
use tiny_encrypt::{CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdInfo, CmdVersion};
#[cfg(feature = "decrypt")]
use tiny_encrypt::CmdDecrypt;
#[cfg(feature = "decrypt")]
@@ -12,6 +11,7 @@ use tiny_encrypt::CmdExecEnv;
use tiny_encrypt::CmdInitKeychain;
#[cfg(feature = "smartcard")]
use tiny_encrypt::CmdInitPiv;
use tiny_encrypt::{CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdInfo, CmdSimpleDecrypt, CmdSimpleEncrypt, CmdVersion};
#[derive(Debug, Parser)]
#[command(name = "tiny-encrypt-rs")]
@@ -26,6 +26,12 @@ enum Commands {
/// Encrypt file(s)
#[command(arg_required_else_help = true, short_flag = 'e')]
Encrypt(CmdEncrypt),
/// Simple encrypt message
#[command(arg_required_else_help = true)]
SimpleEncrypt(CmdSimpleEncrypt),
/// Simple decrypt message
#[command(arg_required_else_help = true)]
SimpleDecrypt(CmdSimpleDecrypt),
#[cfg(feature = "decrypt")]
/// Decrypt file(s)
#[command(arg_required_else_help = true, short_flag = 'd')]
@@ -60,6 +66,8 @@ fn main() -> XResult<()> {
let args = Cli::parse();
match args.command {
Commands::Encrypt(cmd_encrypt) => tiny_encrypt::encrypt(cmd_encrypt),
Commands::SimpleEncrypt(cmd_simple_encrypt) => tiny_encrypt::simple_encrypt(cmd_simple_encrypt),
Commands::SimpleDecrypt(cmd_simple_decrypt) => tiny_encrypt::simple_decrypt(cmd_simple_decrypt),
#[cfg(feature = "decrypt")]
Commands::Decrypt(cmd_decrypt) => tiny_encrypt::decrypt(cmd_decrypt),
Commands::DirectDecrypt(cmd_direct_decrypt) => tiny_encrypt::direct_decrypt(cmd_direct_decrypt),

View File

@@ -1,4 +1,4 @@
use std::io::Write;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::{fs, io};
@@ -32,6 +32,13 @@ impl AsRef<[u8]> for SecVec {
}
}
pub fn read_stdin() -> XResult<Vec<u8>> {
let mut buffer = vec![];
let mut stdin = io::stdin();
opt_result!(stdin.read_to_end(&mut buffer), "Read stdin failed: {}");
Ok(buffer)
}
pub fn read_pin(pin: &Option<String>) -> XResult<String> {
let rpin = match pin {
Some(pin) => pin.to_string(),