feat: v1.0.0, suport exec-env
This commit is contained in:
37
Cargo.lock
generated
37
Cargo.lock
generated
@@ -328,6 +328,16 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
@@ -1403,6 +1413,30 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"num-bigint",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.20"
|
||||
@@ -1657,7 +1691,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiny-encrypt"
|
||||
version = "0.9.1"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"aes-gcm-stream",
|
||||
"base64",
|
||||
@@ -1677,6 +1711,7 @@ dependencies = [
|
||||
"rsa",
|
||||
"rust-crypto",
|
||||
"rust_util",
|
||||
"security-framework",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simpledateformat",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tiny-encrypt"
|
||||
version = "0.9.1"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "A simple and tiny file encrypt tool"
|
||||
@@ -9,8 +9,9 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["smartcard"]
|
||||
default = ["smartcard", "macos"]
|
||||
smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"]
|
||||
macos = ["security-framework"]
|
||||
|
||||
[dependencies]
|
||||
aes-gcm-stream = "0.2"
|
||||
@@ -32,6 +33,7 @@ rpassword = "7.3"
|
||||
rsa = { version = "0.9", features = ["pem"] }
|
||||
rust-crypto = "0.2"
|
||||
rust_util = "0.6"
|
||||
security-framework = { version = "2.9.2", features = ["OSX_10_15"], optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
simpledateformat = "0.1"
|
||||
|
||||
4
justfile
4
justfile
@@ -5,7 +5,7 @@ _:
|
||||
build:
|
||||
cargo build --release
|
||||
|
||||
# Build release without smartcard
|
||||
build-no-smartcard:
|
||||
# Build release without features
|
||||
build-no-features:
|
||||
cargo build --release --no-default-features
|
||||
|
||||
|
||||
@@ -87,14 +87,8 @@ pub fn decrypt(cmd_decrypt: CmdDecrypt) -> XResult<()> {
|
||||
if cmd_decrypt.edit_file && (cmd_decrypt.paths.len() != 1) {
|
||||
return simple_error!("Edit mode only allows one file assigned.");
|
||||
}
|
||||
let pin = match &cmd_decrypt.pin {
|
||||
Some(pin) => Some(pin.clone()),
|
||||
None => util_env::get_pin(),
|
||||
};
|
||||
let key_id = match &cmd_decrypt.key_id {
|
||||
Some(key_id) => Some(key_id.clone()),
|
||||
None => util_env::get_key_id(),
|
||||
};
|
||||
let pin = cmd_decrypt.pin.clone().or_else(util_env::get_pin);
|
||||
let key_id = cmd_decrypt.key_id.clone().or_else(util_env::get_key_id);
|
||||
|
||||
for path in &cmd_decrypt.paths {
|
||||
let start_decrypt_single = Instant::now();
|
||||
@@ -322,8 +316,8 @@ fn create_edit_temp_file(file_content: &[u8], path_out: &str) -> XResult<PathBuf
|
||||
Ok(temp_file)
|
||||
}
|
||||
|
||||
fn decrypt_limited_content_to_vec(mut file_in: &mut File,
|
||||
meta: &TinyEncryptMeta, cryptor: Cryptor, key_nonce: &KeyNonce) -> XResult<Option<String>> {
|
||||
pub fn decrypt_limited_content_to_vec(mut file_in: &mut File,
|
||||
meta: &TinyEncryptMeta, cryptor: Cryptor, key_nonce: &KeyNonce) -> XResult<Option<String>> {
|
||||
if meta.file_length > 100 * 1024 {
|
||||
failure!("File too large(more than 100K) cannot direct print on console.");
|
||||
return Ok(None);
|
||||
@@ -420,10 +414,10 @@ fn parse_encrypted_meta(meta: &TinyEncryptMeta, cryptor: Cryptor, key_nonce: &Ke
|
||||
Ok(Some(enc_meta))
|
||||
}
|
||||
|
||||
fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
|
||||
envelop: &TinyEncryptEnvelop,
|
||||
pin: &Option<String>,
|
||||
slot: &Option<String>) -> XResult<Vec<u8>> {
|
||||
pub fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
|
||||
envelop: &TinyEncryptEnvelop,
|
||||
pin: &Option<String>,
|
||||
slot: &Option<String>) -> XResult<Vec<u8>> {
|
||||
match envelop.r#type {
|
||||
TinyEncryptEnvelopType::Pgp => try_decrypt_key_pgp(envelop, pin),
|
||||
TinyEncryptEnvelopType::PgpX25519 => try_decrypt_key_ecdh_pgp_x25519(envelop, pin),
|
||||
@@ -511,7 +505,7 @@ fn try_decrypt_key_pgp(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XR
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
fn select_envelop<'a>(meta: &'a TinyEncryptMeta, key_id: &Option<String>, config: &Option<TinyEncryptConfig>) -> XResult<&'a TinyEncryptEnvelop> {
|
||||
pub fn select_envelop<'a>(meta: &'a TinyEncryptMeta, key_id: &Option<String>, config: &Option<TinyEncryptConfig>) -> XResult<&'a TinyEncryptEnvelop> {
|
||||
let envelops = match &meta.envelops {
|
||||
None => return simple_error!("No envelops found"),
|
||||
Some(envelops) => if envelops.is_empty() {
|
||||
|
||||
154
src/cmd_execenv.rs
Normal file
154
src/cmd_execenv.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
|
||||
use clap::Args;
|
||||
use rust_util::{debugging, iff, information, opt_result, simple_error, util_cmd, util_msg, warning, XResult};
|
||||
use serde_json::Value;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::{consts, util, util_env};
|
||||
use crate::cmd_decrypt::{decrypt_limited_content_to_vec, select_envelop, try_decrypt_key};
|
||||
use crate::config::TinyEncryptConfig;
|
||||
use crate::consts::TINY_ENC_CONFIG_FILE;
|
||||
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||
use crate::util::SecVec;
|
||||
use crate::util_enc_file;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct CmdExecEnv {
|
||||
/// PIN
|
||||
#[arg(long, short = 'p')]
|
||||
pub pin: Option<String>,
|
||||
/// KeyID
|
||||
#[arg(long, short = 'k')]
|
||||
pub key_id: Option<String>,
|
||||
/// Slot
|
||||
#[arg(long, short = 's')]
|
||||
pub slot: Option<String>,
|
||||
// Tiny encrypt file name
|
||||
pub file_name: String,
|
||||
// Arguments
|
||||
pub arguments: Vec<String>,
|
||||
}
|
||||
|
||||
impl Drop for CmdExecEnv {
|
||||
fn drop(&mut self) {
|
||||
if let Some(p) = self.pin.as_mut() { p.zeroize(); }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec_env(cmd_exec_env: CmdExecEnv) -> XResult<()> {
|
||||
debugging!("Cmd exec env: {:?}", cmd_exec_env);
|
||||
let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE).ok();
|
||||
if cmd_exec_env.arguments.is_empty() {
|
||||
return simple_error!("No commands assigned.");
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let pin = cmd_exec_env.pin.clone().or_else(util_env::get_pin);
|
||||
let key_id = cmd_exec_env.key_id.clone().or_else(util_env::get_key_id);
|
||||
|
||||
let path = PathBuf::from(&cmd_exec_env.file_name);
|
||||
let path_display = format!("{}", &path.display());
|
||||
util::require_tiny_enc_file_and_exists(&path)?;
|
||||
|
||||
let mut file_in = opt_result!(File::open(&path), "Open file: {} failed: {}", &path_display);
|
||||
let (_, _, meta) = opt_result!(
|
||||
util_enc_file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display);
|
||||
util_msg::when_debug(|| {
|
||||
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
||||
});
|
||||
|
||||
let encryption_algorithm = meta.encryption_algorithm.as_deref()
|
||||
.unwrap_or(consts::TINY_ENC_AES_GCM);
|
||||
let cryptor = Cryptor::from(encryption_algorithm)?;
|
||||
|
||||
let selected_envelop = select_envelop(&meta, &key_id, &config)?;
|
||||
|
||||
let key = SecVec(try_decrypt_key(&config, selected_envelop, &pin, &cmd_exec_env.slot)?);
|
||||
let nonce = SecVec(opt_result!(util::decode_base64(&meta.nonce), "Decode nonce failed: {}"));
|
||||
let key_nonce = KeyNonce { k: key.as_ref(), n: nonce.as_ref() };
|
||||
|
||||
let decrypted_content = decrypt_limited_content_to_vec(&mut file_in, &meta, cryptor, &key_nonce)?;
|
||||
let exit_code = if let Some(output) = decrypted_content {
|
||||
debugging!("Outputs: {}", output);
|
||||
let arguments = &cmd_exec_env.arguments;
|
||||
let envs = parse_output_to_env(&output);
|
||||
|
||||
let mut command = Command::new(&arguments[0]);
|
||||
arguments.iter().skip(1).for_each(|a| { command.arg(a); });
|
||||
envs.iter().for_each(|(k, v)| { command.env(k, v); });
|
||||
|
||||
debugging!("Run cmd: {:?}", command);
|
||||
let run_cmd_result = util_cmd::run_command_and_wait(&mut command)?;
|
||||
debugging!("Run cmd result: {}", run_cmd_result);
|
||||
iff!(run_cmd_result.success(), 0, run_cmd_result.code().unwrap_or(-2))
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
|
||||
information!("Finished, cost: {}ms", start.elapsed().as_millis());
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
// supports format:
|
||||
// JSON:
|
||||
// {
|
||||
// "KEY": "value",
|
||||
// "KEY2": "value2"
|
||||
// }
|
||||
// ----OR----
|
||||
// [
|
||||
// "KEY": "value",
|
||||
// "KEY2": "value2"
|
||||
// ]
|
||||
// ENV:
|
||||
// KEY=value
|
||||
// KEY2=value2
|
||||
fn parse_output_to_env(output: &str) -> Vec<(String, String)> {
|
||||
let mut env = vec![];
|
||||
if let Ok(json) = serde_json::from_str::<Value>(output) {
|
||||
match &json {
|
||||
Value::Array(array) => {
|
||||
for a in array {
|
||||
match a {
|
||||
Value::String(s) => { env.push((s.to_string(), "".to_string())); }
|
||||
Value::Array(a2) => if a2.len() == 2 {
|
||||
env.push((a2[0].to_string(), a2[1].to_string()));
|
||||
} else {
|
||||
warning!("Invalid array object: {:?}", a2);
|
||||
}
|
||||
Value::Object(object) => {
|
||||
object.iter().for_each(|(k, v)| {
|
||||
env.push((k.to_string(), v.to_string()));
|
||||
});
|
||||
}
|
||||
_ => { warning!("Invalid array object: {}", a); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Object(object) => {
|
||||
object.iter().for_each(|(k, v)| {
|
||||
env.push((k.to_string(), v.to_string()));
|
||||
});
|
||||
}
|
||||
_ => { warning!("Parse to env failed: {}", json); }
|
||||
}
|
||||
} else {
|
||||
let lines = output.split('\n');
|
||||
lines.filter(|ln| !ln.trim().is_empty()).for_each(|ln| {
|
||||
if ln.contains('=') {
|
||||
let k = ln.chars().take_while(|c| c != &'=').collect::<String>();
|
||||
let v = ln.chars().skip_while(|c| c != &'=').skip(1).collect::<String>();
|
||||
env.push((k, v));
|
||||
} else {
|
||||
env.push((ln.to_string(), "".to_string()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
debugging!("Parsed env: {:?}", env);
|
||||
env
|
||||
}
|
||||
40
src/cmd_initkeychainkey.rs
Normal file
40
src/cmd_initkeychainkey.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use clap::Args;
|
||||
use rust_util::XResult;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct CmdKeychainKey {
|
||||
/// Keychain name, or default
|
||||
#[arg(long, short = 'c')]
|
||||
pub keychain_name: Option<String>,
|
||||
/// Service name, or tiny-encrypt
|
||||
#[arg(long, short = 's')]
|
||||
pub server_name: Option<String>,
|
||||
/// Key type, or default x25519
|
||||
#[arg(long, short = 't')]
|
||||
pub key_type: Option<String>,
|
||||
/// Key name
|
||||
#[arg(long, short = 'n')]
|
||||
pub key_name: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum KeyType {
|
||||
P256,
|
||||
P384,
|
||||
X25519,
|
||||
}
|
||||
|
||||
// TODO Under developing
|
||||
// keychain://keychain_name?sn=service_name&kt=kp-p256&kn=key_name&fp=fingerprint
|
||||
// keychain_name -> default
|
||||
// service_name -> tiny-encrypt
|
||||
// kt=kp-p256|kp-p384|kp-x25519 -> keypair P256, P385 or X25519
|
||||
// key_name -> key name in keychain
|
||||
// fingerprint -> hex(SHA256(public_key)[0..4])
|
||||
pub fn keychain_key(_cmd_keychain_key: CmdKeychainKey) -> XResult<()> {
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
@@ -10,6 +10,8 @@ pub fn version(_cmd_version: CmdVersion) -> XResult<()> {
|
||||
let mut features: Vec<&str> = vec![];
|
||||
#[cfg(feature = "smartcard")]
|
||||
features.push("smartcard");
|
||||
#[cfg(feature = "macos")]
|
||||
features.push("macos");
|
||||
if features.is_empty() { features.push("-"); }
|
||||
println!(
|
||||
"User-Agent: {} [ with features: {} ]\n{}",
|
||||
|
||||
10
src/lib.rs
10
src/lib.rs
@@ -17,6 +17,13 @@ pub use cmd_info::info;
|
||||
pub use cmd_info::info_single;
|
||||
pub use cmd_version::CmdVersion;
|
||||
pub use cmd_version::version;
|
||||
#[cfg(feature = "macos")]
|
||||
pub use cmd_initkeychainkey::CmdKeychainKey;
|
||||
#[cfg(feature = "macos")]
|
||||
pub use cmd_initkeychainkey::keychain_key;
|
||||
pub use cmd_execenv::CmdExecEnv;
|
||||
pub use cmd_execenv::exec_env;
|
||||
|
||||
|
||||
mod consts;
|
||||
mod util;
|
||||
@@ -47,4 +54,7 @@ mod cmd_info;
|
||||
mod cmd_decrypt;
|
||||
mod cmd_encrypt;
|
||||
mod cmd_directdecrypt;
|
||||
#[cfg(feature = "macos")]
|
||||
mod cmd_initkeychainkey;
|
||||
mod cmd_execenv;
|
||||
|
||||
|
||||
14
src/main.rs
14
src/main.rs
@@ -3,9 +3,11 @@ extern crate core;
|
||||
use clap::{Parser, Subcommand};
|
||||
use rust_util::XResult;
|
||||
|
||||
use tiny_encrypt::{CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdInfo, CmdVersion};
|
||||
use tiny_encrypt::{CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdExecEnv, CmdInfo, CmdVersion};
|
||||
#[cfg(feature = "smartcard")]
|
||||
use tiny_encrypt::CmdDecrypt;
|
||||
#[cfg(feature = "macos")]
|
||||
use tiny_encrypt::CmdKeychainKey;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "tiny-encrypt-rs")]
|
||||
@@ -30,6 +32,13 @@ enum Commands {
|
||||
/// Show file info
|
||||
#[command(arg_required_else_help = true, short_flag = 'I')]
|
||||
Info(CmdInfo),
|
||||
#[cfg(feature = "macos")]
|
||||
/// Keychain Key [pending implementation]
|
||||
#[command(arg_required_else_help = true, short_flag = 'k')]
|
||||
KeychainKey(CmdKeychainKey),
|
||||
/// Execute env
|
||||
#[command(arg_required_else_help = true, short_flag = 'X')]
|
||||
CmdExecEnv(CmdExecEnv),
|
||||
/// Show version
|
||||
#[command(short_flag = 'v')]
|
||||
Version(CmdVersion),
|
||||
@@ -46,6 +55,9 @@ fn main() -> XResult<()> {
|
||||
Commands::Decrypt(cmd_decrypt) => tiny_encrypt::decrypt(cmd_decrypt),
|
||||
Commands::DirectDecrypt(cmd_direct_decrypt) => tiny_encrypt::direct_decrypt(cmd_direct_decrypt),
|
||||
Commands::Info(cmd_info) => tiny_encrypt::info(cmd_info),
|
||||
#[cfg(feature = "macos")]
|
||||
Commands::KeychainKey(cmd_keychain_key) => tiny_encrypt::keychain_key(cmd_keychain_key),
|
||||
Commands::CmdExecEnv(cmd_exec_env) => tiny_encrypt::exec_env(cmd_exec_env),
|
||||
Commands::Version(cmd_version) => tiny_encrypt::version(cmd_version),
|
||||
Commands::Config(cmd_config) => tiny_encrypt::config(cmd_config),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user