Compare commits

...

39 Commits

Author SHA1 Message Date
0604745f82 feat: v1.9.20, simple-encrypt/simple-decrypt support iterations 2026-01-01 21:16:10 +08:00
1f4db9d1b0 feat: update gpg 2025-12-28 20:45:45 +08:00
cdec79e4dc feat: v1.9.18 2025-12-28 20:18:39 +08:00
3812001474 feat: updates 2025-10-18 14:45:50 +08:00
3708781390 feat: v1.9.18 2025-10-18 14:32:06 +08:00
a6397fc45a feat: v1.9.17 2025-10-17 19:20:41 +08:00
0e6b590f32 feat: udpate README 2025-10-16 07:08:14 +08:00
b0ee1f2c59 feat: v1.9.16 2025-10-14 23:38:22 +08:00
c176021c81 feat: v1.9.15, Secure Enclave ML-KEM-768, ML-KEM-1024 encrypt/decrypt 2025-09-27 20:51:56 +08:00
d75c589b66 feat: add keymlkem 2025-09-26 23:44:31 +08:00
0c4663f7f0 feat: updates 2025-09-26 23:28:41 +08:00
c446a52462 feat: encrypt ML-KEM-768&1024 2025-09-26 23:25:53 +08:00
9b0ecef9a0 feat: pending ML-KEM encryption and decryption 2025-09-26 01:13:37 +08:00
783b3e1962 feat: pending ML-KEM encryption and decryption 2025-09-26 01:13:24 +08:00
7b7878e2c1 feat: make clippy happy 2025-09-14 17:21:55 +08:00
403eaf1669 feat: v1.9.14, enhance temporary keys 2025-09-14 17:17:18 +08:00
75ed193d86 feaat: v1.9.13, add temporary key for simple encrypt 2025-09-14 15:29:07 +08:00
c8175e2654 feat: update readme, add TINY_ENCRYPT_ENV_EXTERNAL_COMMAND 2025-09-13 23:58:08 +08:00
7c752eab41 feat: update readme, add TINY_ENCRYPT_ENV_EXTERNAL_COMMAND 2025-09-13 23:57:39 +08:00
96ebf7f592 feat: udpate readme, add ext-p256, ext-p384 2025-09-13 23:53:46 +08:00
b18a5ec3e2 feat: v1.9.12, supports ext keys(p256, p384) 2025-09-13 23:17:00 +08:00
813b8b1b24 feat: v1.9.11 2025-09-13 22:30:13 +08:00
6a84ae7f5c feat: update USAGE 2025-08-25 00:07:59 +08:00
ff30fd42dd feat: v1.9.10 2025-08-24 23:29:24 +08:00
0ad8f83092 feat: v0.6.50 2025-08-24 23:01:11 +08:00
1741c335db feat: update dependencies 2025-08-24 16:17:47 +08:00
93cb3309bf feat: fix ask use default pin 2025-08-24 14:12:22 +08:00
6a24388a19 feat: v1.9.9 2025-08-24 13:53:33 +08:00
b94acf9c31 feat: v1.9.8 2025-08-24 12:34:25 +08:00
b91b29e22d feat: update readme 2025-07-27 11:11:07 +08:00
12e828732e feat: v1.9.7 2025-07-27 11:03:17 +08:00
d98005d799 feat: v1.9.7 2025-07-27 11:02:23 +08:00
e308809b20 feat: v1.9.7 2025-07-27 11:01:40 +08:00
920aa92b0e feat: v1.9.6 2025-07-27 10:43:42 +08:00
20c54350ee feat: v1.9.5 2025-07-27 10:32:07 +08:00
044daaad7d feat: v1.9.4, config support human JSON 2025-07-24 22:51:43 +08:00
f0f505bde3 feat: v1.9.3 2025-05-12 23:58:14 +08:00
4ad735d840 feat: v1.9.2, fix compile issue 2025-03-26 23:49:42 +08:00
d831b606cd feat: v1.9.1 2025-03-24 01:16:33 +08:00
27 changed files with 1471 additions and 492 deletions

978
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "tiny-encrypt"
version = "1.9.0"
version = "1.9.20"
edition = "2021"
license = "MIT"
description = "A simple and tiny file encrypt tool"
@@ -24,7 +24,7 @@ clap = { version = "4.4", features = ["derive"] }
flate2 = "1.0"
fs-set-times = "0.20"
hex = "0.4"
indicatif = "0.17"
indicatif = "0.18"
openpgp-card = { version = "0.3", optional = true }
openpgp-card-pcsc = { version = "0.3", optional = true }
p256 = { version = "0.13", features = ["pem", "ecdh", "pkcs8"] }
@@ -39,9 +39,9 @@ security-framework = { version = "3.0", features = ["OSX_10_15"], optional = tru
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
simpledateformat = "0.1"
tabled = "0.17"
tabled = "0.20"
x25519-dalek = { version = "2.0", features = ["static_secrets", "getrandom"] }
x509-parser = "0.16"
x509-parser = "0.17"
yubikey = { version = "0.8", features = ["untested"], optional = true }
zeroize = "1.7"
spki = "0.7"
@@ -51,7 +51,12 @@ pinentry = "0.6"
secrecy = "0.10"
dialoguer = "0.11"
ctrlc = "3.4"
swift-secure-enclave-tool-rs = "0.1.0"
swift-secure-enclave-tool-rs = "1.0"
json5 = "0.4"
external-command-rs = "0.1"
percent-encoding = "2.3"
ml-kem = { version = "0.2.1", features = ["zeroize"] }
zeroizing-alloc = "0.1.0"
[profile.release]
codegen-units = 1

View File

@@ -115,7 +115,7 @@ Last, config key id to profile.
Supported PKI encryption types:
| Type | Algorithm | Description |
|------------------|-----------------|-----------------------------------------|
|------------------|---------------------|--------------------------------------------------------|
| pgp-rsa | PKCS1-v1.5 | OpenPGP Encryption Key (Previous `pgp`) |
| pgp-x25519 | ECDH(X25519) | OpenPGP Encryption Key |
| gpg | OpenPGP | GnuPG Command |
@@ -123,7 +123,13 @@ Supported PKI encryption types:
| static-kyber1024 | Kyber1024 | Key Stored in macOS Keychain Access |
| piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) |
| piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) |
| key-p256 | ECDH(secp256r1) | Key Stored in macOS Secure Enclave |
| key-p256 | ECDH(secp256r1) | Key Stored in macOS Secure Enclave (using P256) |
| key-mlkem768 | ML-KEM(ML-KEM-768) | Key Stored in macOS Secure Enclave (using ML-KEM-768) |
| key-mlkem1024 | ML-KEM(ML-KEM-1024) | Key Stored in macOS Secure Enclave (using ML-KEM-1024) |
| ext-p256 | ECDH(secp256r1) | Key Protected by External Command |
| ext-p384 | ECDH(secp384r1) | Key Protected by External Command |
| ext-mlkem768 | ML-KEM(ML-KEM-768) | Key Protected by External Command |
| ext-mlkem1024 | ML-KEM(ML-KEM-1024) | Key Protected by External Command |
| piv-rsa | PKCS1-v1.5 | PIV Slot |
Smart Card(Yubikey) protected ECDH Encryption description as below:
@@ -152,7 +158,8 @@ Smart Card(Yubikey) protected ECDH Encryption description as below:
Environment
| KEY | Comment |
|----------------------------------|---------------------------------------------|
|----------------------------------|----------------------------------------------------------------|
| TINY_ENCRYPT_CONFIG_FILE | Config file |
| TINY_ENCRYPT_DEFAULT_ALGORITHM | Encryption algorithm, `aes` or `chacha20` |
| TINY_ENCRYPT_DEFAULT_COMPRESS | File compress, `1` or `on`, default `false` |
| TINY_ENCRYPT_NO_PROGRESS | Do not display progress bar |
@@ -163,7 +170,11 @@ Environment
| TINY_ENCRYPT_AUTO_SELECT_KEY_IDS | Auto select Key IDs |
| TINY_ENCRYPT_AUTO_COMPRESS_EXTS | Auto compress file exts |
| TINY_ENCRYPT_PIN_ENTRY | PIN entry command cli |
| SECURE_EDITOR | Secure Editor |
| TINY_ENCRYPT_EXTERNAL_COMMAND | External command cli |
| SECURE_EDITOR | Secure Editor [\[OWS RFC6\]](https://openwebstandard.org/rfc6) |
| EDITOR | Editor (Plaintext) |
Alternative environment setup:
```shell
~/.config/envs/ENV_VARIABLE_NAME <--> File Content
```

View File

@@ -133,5 +133,10 @@ tiny-encrypt -e [-p Profile] [-x] [-L 6] [-1] [-R] [-c Comment] [-C EncryptedCom
tiny-encrypt -d [-p PIN] [-s Slot] [-R] FILENAMES
```
## 5 Generate key(s)
### 5.1 Generate SE based key(s)
```shell
tiny-encrypt init-keychain -S -C biometry-current-set -E --key-name <key-name>
```

View File

@@ -1,6 +1,10 @@
_:
@just --list
# publish
publish:
cargo publish --registry crates-io
# Install local
install:
cargo install --path .
@@ -27,5 +31,4 @@ try-build-all:
cargo build --no-default-features --features smartcard
cargo build --no-default-features --features decrypt
cargo build --no-default-features --features macos
cargo build --no-default-features --features secure-enclave
cargo build

View File

@@ -7,6 +7,7 @@ use tabled::{Table, Tabled};
use tabled::settings::Style;
use crate::config::TinyEncryptConfig;
use crate::temporary_key::serialize_config_envelop;
use crate::util_envelop;
#[derive(Tabled, Eq)]
@@ -47,6 +48,15 @@ pub struct CmdConfig {
/// Show KID
#[arg(long)]
pub show_kid: bool,
/// JSON output
#[arg(long)]
pub json: bool,
/// Temporary key output
#[arg(long)]
pub temporary_key: bool,
/// Hide __all__
#[arg(long)]
pub hide_all: bool,
/// Encryption profile (use default when --key-filter is assigned)
#[arg(long, short = 'p')]
pub profile: Option<String>,
@@ -55,13 +65,36 @@ pub struct CmdConfig {
pub key_filter: Option<String>,
}
pub fn config(cmd_version: CmdConfig) -> XResult<()> {
let config = TinyEncryptConfig::load_default()?;
pub fn config(cmd_config: CmdConfig) -> XResult<()> {
let config = TinyEncryptConfig::load_default(&None)?;
if cmd_version.profile.is_some() || cmd_version.key_filter.is_some() {
return config_key_filter(&cmd_version, &config);
if cmd_config.json {
let mut config = config;
config.includes = None;
if let Some(profiles) = &mut config.profiles {
profiles.remove("__all__");
}
config_profiles(&cmd_version, &config)
println!("{}", serde_json::to_string_pretty(&config)?);
return Ok(());
}
if cmd_config.temporary_key {
let envelops = if cmd_config.profile.is_some() || cmd_config.key_filter.is_some() {
config.find_envelops(&cmd_config.profile, &cmd_config.key_filter)?
} else {
config.find_envelops(&None, &None)?
};
for envelop in envelops {
let k = serialize_config_envelop(envelop);
println!("{}", k);
}
return Ok(());
}
if cmd_config.profile.is_some() || cmd_config.key_filter.is_some() {
return config_key_filter(&cmd_config, &config);
}
config_profiles(&cmd_config, &config)
}
fn config_key_filter(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
@@ -99,9 +132,10 @@ fn strip_field(kid: &str, max_len: usize) -> String {
}
}
fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
fn config_profiles(cmd_config: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
let mut reverse_map = HashMap::new();
for (p, v) in &config.profiles {
if let Some(profiles) = &config.profiles {
for (p, v) in profiles {
let mut v2 = v.clone();
v2.sort();
let vs = v2.join(",");
@@ -110,12 +144,16 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu
Some(vec) => { vec.push((p, v)); }
}
}
}
let mut config_profiles = vec![];
for pvs in reverse_map.values() {
let mut ps: Vec<_> = pvs.iter().map(|pv| pv.0).collect();
ps.sort();
let pp = ps.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", ");
if cmd_config.hide_all && pp == "__all__" {
continue;
}
let kids = pvs[0].1;
let mut ks = Vec::with_capacity(kids.len());
for kid in kids {
@@ -124,7 +162,7 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu
ks.push(format!("[ERROR] Key not found: {}", kid));
}
Some(envelop) => {
let kid = if cmd_version.show_kid {
let kid = if cmd_config.show_kid {
format!("Kid: {}", envelop.kid)
} else {
envelop.sid.as_ref()

View File

@@ -1,4 +1,4 @@
use std::{env, fs};
use std::fs;
use std::env::temp_dir;
use std::fs::File;
use std::io::{Read, Write};
@@ -16,6 +16,7 @@ use rust_util::{
debugging, failure, iff, information, opt_result, opt_value_result, println_ex, simple_error, success,
util_cmd, util_msg, util_size, util_time, warning, XResult,
};
use rust_util::util_env as rust_util_env;
use rust_util::util_time::UnixEpochTime;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
@@ -28,6 +29,8 @@ use crate::compress::GzStreamDecoder;
use crate::config::TinyEncryptConfig;
use crate::consts::{
DATE_TIME_FORMAT,
ENC_AES256_GCM_MLKEM768, ENC_AES256_GCM_MLKEM1024,
ENC_CHACHA20_POLY1305_MLKEM768, ENC_CHACHA20_POLY1305_MLKEM1024,
ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384,
ENC_AES256_GCM_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256,
ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519,
@@ -37,6 +40,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;
@@ -91,6 +95,10 @@ pub struct CmdDecrypt {
#[arg(long, short = 'A')]
pub digest_algorithm: Option<String>,
/// Config file or based64 encoded (starts with: base64:)
#[arg(long)]
pub config: Option<String>,
/// Files need to be decrypted
pub paths: Vec<PathBuf>,
}
@@ -104,7 +112,7 @@ impl Drop for CmdDecrypt {
pub fn decrypt(cmd_decrypt: CmdDecrypt) -> XResult<()> {
if cmd_decrypt.split_print { util_msg::set_logger_std_out(false); }
debugging!("Cmd decrypt: {:?}", cmd_decrypt);
let config = TinyEncryptConfig::load_default().ok();
let config = TinyEncryptConfig::load_default(&cmd_decrypt.config).ok();
let start = Instant::now();
let mut succeed_count = 0;
@@ -327,12 +335,12 @@ fn run_file_editor_and_wait_content(editor: &str, temp_file: &PathBuf, secure_ed
}
fn get_file_editor() -> (bool, String) {
if let Ok(secure_editor) = env::var("SECURE_EDITOR") {
if let Some(secure_editor) = rust_util_env::env_var("SECURE_EDITOR") {
// cmd <file-name> "aes-256-gcm" <key-in-hex> <nonce-in-hex>
information!("Found secure editor: {}", &secure_editor);
return (true, secure_editor);
}
match env::var("EDITOR").ok() {
match rust_util_env::env_var("EDITOR") {
Some(editor) => (false, editor),
None => {
warning!("EDITOR is not assigned, use default editor vi");
@@ -466,6 +474,9 @@ pub fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
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::KeyMlKem768 | TinyEncryptEnvelopType::KeyMlKem1024 => try_decrypt_se_key_ecdh(config, envelop),
TinyEncryptEnvelopType::ExtP256 | TinyEncryptEnvelopType::ExtP384 |
TinyEncryptEnvelopType::ExtMlKem768 | TinyEncryptEnvelopType::ExtMlKem1024 => 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),
@@ -555,8 +566,8 @@ fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
let cryptor = match wrap_key.header.enc.as_str() {
ENC_AES256_GCM_P256 => Cryptor::Aes256Gcm,
ENC_CHACHA20_POLY1305_P256 => Cryptor::ChaCha20Poly1305,
ENC_AES256_GCM_P256 | ENC_AES256_GCM_MLKEM768 | ENC_AES256_GCM_MLKEM1024 => Cryptor::Aes256Gcm,
ENC_CHACHA20_POLY1305_P256 | ENC_CHACHA20_POLY1305_MLKEM768 | ENC_CHACHA20_POLY1305_MLKEM1024 => 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()?;
@@ -569,14 +580,22 @@ fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
return simple_error!("Not enough arguments for: {}", &envelop.kid);
}
#[cfg(feature = "macos")]
let private_key_base64 = if let Ok(keychain_key) = KeychainKey::parse(&config_envelop_args[0]) {
let key = opt_value_result!(keychain_key.get_password()?, "Key: {} not found", &keychain_key.to_str());
opt_result!(String::from_utf8(key), "Parse key failed: {}")
} else {
config_envelop_args[0].clone()
};
#[cfg(not(feature = "macos"))]
let private_key_base64 = if config_envelop_args[0].starts_with("keychain:") {
return simple_error!("Require macos feature: {}", &config_envelop_args[0]);
} else {
config_envelop_args[0].clone()
};
let shared_secret = opt_result!(util_keychainkey::decrypt_data(
envelop.r#type,
&private_key_base64,
&e_pub_key_bytes
), "Decrypt via secure enclave failed: {}");
@@ -589,6 +608,48 @@ fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
Ok(decrypted_key)
}
fn try_decrypt_ext_key_ecdh(config: &Option<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
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 |
ENC_AES256_GCM_MLKEM768 | ENC_AES256_GCM_MLKEM1024 => Cryptor::Aes256Gcm,
ENC_CHACHA20_POLY1305_P256 | ENC_CHACHA20_POLY1305_P384 |
ENC_CHACHA20_POLY1305_MLKEM768 | ENC_CHACHA20_POLY1305_MLKEM1024 => 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<String>) -> XResult<Vec<u8>> {
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
let cryptor = match wrap_key.header.enc.as_str() {

View File

@@ -12,23 +12,20 @@ use rust_util::{debugging, failure, iff, information, opt_result, simple_error,
use crate::compress::GzStreamEncoder;
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
use crate::consts::{
ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519,
ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_P256, ENC_CHACHA20_POLY1305_P384,
ENC_CHACHA20_POLY1305_X25519, SALT_COMMENT, TINY_ENC_FILE_EXT, TINY_ENC_PEM_FILE_EXT,
TINY_ENC_PEM_NAME,
};
use crate::consts::{ENC_AES256_GCM_KYBER1204, ENC_AES256_GCM_MLKEM1024, ENC_AES256_GCM_MLKEM768, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, ENC_CHACHA20_POLY1305_KYBER1204, ENC_CHACHA20_POLY1305_MLKEM1024, ENC_CHACHA20_POLY1305_MLKEM768, ENC_CHACHA20_POLY1305_P256, ENC_CHACHA20_POLY1305_P384, ENC_CHACHA20_POLY1305_X25519, SALT_COMMENT, TINY_ENC_FILE_EXT, TINY_ENC_PEM_FILE_EXT, TINY_ENC_PEM_NAME};
use crate::crypto_cryptor::{Cryptor, KeyNonce};
use crate::spec::{
EncEncryptedMeta, EncMetadata,
TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta,
};
use crate::util::{is_tiny_enc_file, to_pem};
use crate::util::{decode_base64, is_tiny_enc_file, to_pem};
use crate::util_ecdh::{ecdh_kyber1024, ecdh_p256, ecdh_p384, ecdh_x25519};
use crate::util_progress::Progress;
use crate::util_rsa;
use crate::{util_mlkem, util_rsa};
use crate::wrap_key::{WrapKey, WrapKeyHeader};
use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env, util_gpg};
use crate::temporary_key::parse_temporary_keys;
use crate::util_mlkem::MlKemAlgo;
#[derive(Debug, Args)]
pub struct CmdEncrypt {
@@ -48,6 +45,10 @@ pub struct CmdEncrypt {
#[arg(long, short = 'k')]
pub key_filter: Option<String>,
/// Temporary key
#[arg(long)]
pub temporary_key: Option<Vec<String>>,
/// Compress before encrypt
#[arg(long, short = 'x')]
pub compress: bool,
@@ -76,16 +77,29 @@ pub struct CmdEncrypt {
#[arg(long, short = 'A')]
pub encryption_algorithm: Option<String>,
/// Config file or based64 encoded (starts with: base64:)
#[arg(long)]
pub config: Option<String>,
/// Files need to be decrypted
pub paths: Vec<PathBuf>,
}
pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
let config = TinyEncryptConfig::load_default()?;
let config = TinyEncryptConfig::load_default(&cmd_encrypt.config)?;
debugging!("Found tiny encrypt config: {:?}", config);
let envelops = config.find_envelops(&cmd_encrypt.profile, &cmd_encrypt.key_filter)?;
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
let mut envelops = config.find_envelops(&cmd_encrypt.profile, &cmd_encrypt.key_filter)?;
debugging!("Found envelops: {:?}", envelops);
let temporary_envelops = parse_temporary_keys(&cmd_encrypt.temporary_key)?;
if !temporary_envelops.is_empty() {
for t_envelop in &temporary_envelops {
envelops.push(t_envelop)
}
debugging!("Final envelops: {:?}", envelops);
}
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
let envelop_tkids: Vec<_> = envelops.iter()
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
.collect();
@@ -313,15 +327,19 @@ 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 => {
encrypted_envelops.push(encrypt_envelop_ecdh_kyber1204(cryptor, key, envelop)?);
}
TinyEncryptEnvelopType::KeyMlKem768 | TinyEncryptEnvelopType::KeyMlKem1024 |
TinyEncryptEnvelopType::ExtMlKem768 | TinyEncryptEnvelopType::ExtMlKem1024 => {
encrypted_envelops.push(encrypt_envelop_ecdh_ml_kem(cryptor, key, envelop)?);
}
_ => return simple_error!("Not supported type: {:?}", envelop.r#type),
}
}
@@ -368,6 +386,19 @@ fn encrypt_envelop_ecdh_kyber1204(cryptor: Cryptor, key: &[u8], envelop: &TinyEn
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ephemeral_spki, enc_type, envelop)
}
fn encrypt_envelop_ecdh_ml_kem(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
let public_key_base64 = &envelop.public_part;
let public_key = opt_result!(decode_base64(public_key_base64), "Decode ML-KEM public key from base64 failed: {}");
let (shared_secret, ciphertext, ml_kem_algo) = util_mlkem::try_ml_kem_encapsulate(&public_key)?;
let enc_type = match (cryptor, ml_kem_algo) {
(Cryptor::Aes256Gcm, MlKemAlgo::MlKem768) => ENC_AES256_GCM_MLKEM768,
(Cryptor::Aes256Gcm, MlKemAlgo::MlKem1024) => ENC_AES256_GCM_MLKEM1024,
(Cryptor::ChaCha20Poly1305, MlKemAlgo::MlKem768) => ENC_CHACHA20_POLY1305_MLKEM768,
(Cryptor::ChaCha20Poly1305, MlKemAlgo::MlKem1024) => ENC_CHACHA20_POLY1305_MLKEM1024,
};
encrypt_envelop_shared_secret(cryptor, key, &shared_secret, &ciphertext, enc_type, envelop)
}
fn encrypt_envelop_shared_secret(cryptor: Cryptor,
key: &[u8],
shared_secret: &[u8],

View File

@@ -29,6 +29,10 @@ pub struct CmdExecEnv {
#[arg(long, short = 's')]
pub slot: Option<String>,
/// Config file or based64 encoded (starts with: base64:)
#[arg(long)]
pub config: Option<String>,
/// Tiny encrypt file name
pub file_name: String,
@@ -45,7 +49,7 @@ impl Drop for CmdExecEnv {
pub fn exec_env(cmd_exec_env: CmdExecEnv) -> XResult<()> {
util_msg::set_logger_std_out(false);
debugging!("Cmd exec env: {:?}", cmd_exec_env);
let config = TinyEncryptConfig::load_default().ok();
let config = TinyEncryptConfig::load_default(&cmd_exec_env.config).ok();
if cmd_exec_env.command_arguments.is_empty() {
return simple_error!("No commands assigned.");
}

View File

@@ -23,12 +23,16 @@ pub struct CmdInfo {
#[arg(long, short = 'M', default_value_t = false)]
pub raw_meta: bool,
/// Config file or based64 encoded (starts with: base64:)
#[arg(long)]
pub config: Option<String>,
/// File
pub paths: Vec<PathBuf>,
}
pub fn info(cmd_info: CmdInfo) -> XResult<()> {
let config = TinyEncryptConfig::load_default().ok();
let config = TinyEncryptConfig::load_default(&cmd_info.config).ok();
for (i, path) in cmd_info.paths.iter().enumerate() {
let path = config::resolve_path_namespace(&config, path, true);
if i > 0 { println!("{}", "-".repeat(88)); }

View File

@@ -1,7 +1,7 @@
use clap::Args;
use pqcrypto_traits::kem::PublicKey;
use rust_util::{debugging, information, opt_result, simple_error, success, warning, XResult};
use swift_secure_enclave_tool_rs::ControlFlag;
use crate::config::TinyEncryptConfigEnvelop;
use crate::spec::TinyEncryptEnvelopType;
use crate::util_keychainkey;
@@ -14,6 +14,10 @@ pub struct CmdInitKeychain {
#[arg(long, short = 'S')]
pub secure_enclave: bool,
/// Secure Enclave control flag, e.g. none, user-presence, device-passcode, biometry-any, biometry-current-set
#[arg(long, short = 'C')]
pub secure_enclave_control_flag: Option<String>,
/// Expose secure enclave private key data
#[arg(long, short = 'E')]
pub expose_secure_enclave_private_key: bool,
@@ -54,7 +58,19 @@ pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
let service_name = cmd_init_keychain.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME);
let key_name = &cmd_init_keychain.key_name;
let (public_key_hex, private_key_base64) = util_keychainkey::generate_se_p256_keypair()?;
let control_flag = match &cmd_init_keychain.secure_enclave_control_flag {
None => return simple_error!("Parameter --secure-enclave-control-flag required"),
Some(control_flag) => match control_flag.as_str() {
"none" => ControlFlag::None,
"user-presence" | "up" => ControlFlag::UserPresence,
"device-passcode" | "passcode" | "pass" => ControlFlag::DevicePasscode,
"biometry-any" | "bio-any" => ControlFlag::BiometryAny,
"biometry-current-set" | "bio-current" => ControlFlag::BiometryCurrentSet,
_ => return simple_error!("Invalid control flag: {}", control_flag),
}
};
let (public_key_hex, private_key_base64) = util_keychainkey::generate_se_p256_keypair(control_flag)?;
let public_key_compressed_hex = public_key_hex.chars()
.skip(2).take(public_key_hex.len() / 2 - 1).collect::<String>();
let saved_arg0 = if cmd_init_keychain.expose_secure_enclave_private_key {
@@ -72,6 +88,7 @@ pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
desc: Some("Keychain Secure Enclave".to_string()),
args: Some(vec![saved_arg0]),
public_part: public_key_hex,
profiles: None,
};
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
@@ -159,6 +176,7 @@ pub fn keychain_key_static(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
desc: Some("Keychain static".to_string()),
args: Some(vec![keychain_key.to_str()]),
public_part: public_key_hex,
profiles: None,
};
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());

View File

@@ -69,6 +69,7 @@ pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> {
slot_id_hex.clone()
]),
public_part: public_key_point_hex,
profiles: None,
};
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
@@ -84,6 +85,7 @@ pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> {
slot_id_hex.clone()
]),
public_part: util::to_pem(&spki, "PUBLIC KEY"),
profiles: None,
};
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());

View File

@@ -9,6 +9,7 @@ use serde::Serialize;
use std::io;
use std::io::Write;
use std::process::exit;
use crate::temporary_key::parse_temporary_keys;
use crate::util_simple_pbe::SimplePbkdfEncryptionV1;
// Reference: https://git.hatter.ink/hatter/tiny-encrypt-rs/issues/3
@@ -25,6 +26,10 @@ pub struct CmdSimpleEncrypt {
#[arg(long, short = 'k')]
pub key_filter: Option<String>,
/// Temporary key
#[arg(long)]
pub temporary_key: Option<Vec<String>>,
/// Encrypt value from stdin
#[arg(long)]
pub value_stdin: bool,
@@ -45,10 +50,22 @@ pub struct CmdSimpleEncrypt {
#[arg(long, short = 'P')]
pub with_pbkdf_encryption: bool,
/// PBKDF iterations (default: 10000)
#[arg(long, short = 'i')]
pub pbkdf_iterations: Option<u32>,
/// PBKDF encryption password
#[arg(long, short = 'A')]
pub password: Option<String>,
/// Config file or based64 encoded (starts with: base64:)
#[arg(long)]
pub config: Option<String>,
/// Direct output result value
#[arg(long)]
pub outputs_password: bool,
/// Direct output result value
#[arg(long)]
pub direct_output: bool,
@@ -84,6 +101,14 @@ pub struct CmdSimpleDecrypt {
#[arg(long, short = 'A')]
pub password: Option<String>,
/// Config file or based64 encoded (starts with: base64:)
#[arg(long)]
pub config: Option<String>,
/// Direct output result value
#[arg(long)]
pub outputs_password: bool,
/// Direct output result value
#[arg(long)]
pub direct_output: bool,
@@ -122,6 +147,8 @@ pub struct CmdResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
}
@@ -130,14 +157,16 @@ impl CmdResult {
Self {
code,
message: Some(message.to_string()),
password: None,
result: None,
}
}
pub fn success(result: &str) -> Self {
pub fn success(result: &str, password: Option<String>) -> Self {
Self {
code: 0,
message: None,
password,
result: Some(result.to_string()),
}
}
@@ -176,11 +205,23 @@ pub fn simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
}
pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()> {
let config = TinyEncryptConfig::load_default()?;
let config = TinyEncryptConfig::load_default(&cmd_simple_encrypt.config)?;
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"); }
let mut envelops = config.find_envelops(
&cmd_simple_encrypt.profile,
&cmd_simple_encrypt.key_filter)?;
debugging!("Found envelops: {:?}", envelops);
let temporary_envelops = parse_temporary_keys(&cmd_simple_encrypt.temporary_key)?;
if !temporary_envelops.is_empty() {
for t_envelop in &temporary_envelops {
envelops.push(t_envelop)
}
debugging!("Final envelops: {:?}", envelops);
}
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
let envelop_tkids: Vec<_> = envelops.iter()
.map(|e| format!("{}:{}", e.r#type.get_name(), e.kid))
.collect();
@@ -205,17 +246,22 @@ pub fn inner_simple_encrypt(cmd_simple_encrypt: CmdSimpleEncrypt) -> XResult<()>
);
let with_pbkdf_encryption = cmd_simple_encrypt.with_pbkdf_encryption || cmd_simple_encrypt.password.is_some();
let mut outputs_password = None;
if with_pbkdf_encryption {
let password = util::read_password(&cmd_simple_encrypt.password)?;
simple_encrypt_result = SimplePbkdfEncryptionV1::encrypt(&password, simple_encrypt_result.as_bytes())?.to_string();
simple_encrypt_result = SimplePbkdfEncryptionV1::encrypt(&password, simple_encrypt_result.as_bytes(),
&cmd_simple_encrypt.pbkdf_iterations)?.to_string();
if cmd_simple_encrypt.outputs_password {
outputs_password = Some(password);
}
}
CmdResult::success(&simple_encrypt_result).print_exit(cmd_simple_encrypt.direct_output);
CmdResult::success(&simple_encrypt_result, outputs_password).print_exit(cmd_simple_encrypt.direct_output);
}
#[cfg(feature = "decrypt")]
pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()> {
let config = TinyEncryptConfig::load_default().ok();
let config = TinyEncryptConfig::load_default(&cmd_simple_decrypt.config).ok();
let pin = cmd_simple_decrypt.pin.clone().or_else(util_env::get_pin);
let slot = cmd_simple_decrypt.slot.clone();
@@ -231,11 +277,15 @@ pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()>
Some(value) => value,
};
let mut outputs_password = None;
if SimplePbkdfEncryptionV1::matches(&value) {
let simple_pbkdf_encryption_v1: SimplePbkdfEncryptionV1 = value.as_str().try_into()?;
let password = util::read_password(&cmd_simple_decrypt.password)?;
let plaintext_bytes = simple_pbkdf_encryption_v1.decrypt(&password)?;
value = opt_result!(String::from_utf8(plaintext_bytes), "Decrypt PBKDF encryption failed: {}");
if cmd_simple_decrypt.outputs_password {
outputs_password = Some(password);
}
}
let value_parts = value.trim().split(SIMPLE_ENCRYPTION_DOT).collect::<Vec<_>>();
@@ -280,5 +330,5 @@ pub fn inner_simple_decrypt(cmd_simple_decrypt: CmdSimpleDecrypt) -> XResult<()>
"base64" => STANDARD.encode(&value),
_ => return simple_error!("not supported output format: {}", output_format),
};
CmdResult::success(&value).print_exit(cmd_simple_decrypt.direct_output);
CmdResult::success(&value, outputs_password).print_exit(cmd_simple_decrypt.direct_output);
}

View File

@@ -3,13 +3,13 @@ use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::{env, fs};
use rust_util::{util_env as rust_util_env};
use rust_util::util_file::resolve_file_path;
use rust_util::{debugging, opt_result, simple_error, warning, XResult};
use rust_util::{debugging, opt_result, warning, XResult};
use serde::{Deserialize, Serialize};
use crate::consts::{TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE_2, TINY_ENC_FILE_EXT};
use crate::consts::{ENV_TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE, TINY_ENC_CONFIG_FILE_2, TINY_ENC_CONFIG_FILE_3, TINY_ENC_FILE_EXT};
use crate::spec::TinyEncryptEnvelopType;
use crate::util::decode_base64;
/// Config file sample:
/// ~/.tinyencrypt/config-rs.json
@@ -39,8 +39,18 @@ use crate::spec::TinyEncryptEnvelopType;
pub struct TinyEncryptConfig {
pub environment: Option<HashMap<String, StringOrVecString>>,
pub namespaces: Option<HashMap<String, String>>,
pub includes: Option<String>, // find all *.tinyencrypt.json
pub envelops: Vec<TinyEncryptConfigEnvelop>,
pub profiles: HashMap<String, Vec<String>>,
pub profiles: Option<HashMap<String, Vec<String>>>,
}
impl TinyEncryptConfig {
fn get_profile(&self, profile: &str) -> Option<&Vec<String>> {
match &self.profiles {
Some(profiles) => profiles.get(profile),
None => None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -62,51 +72,60 @@ pub struct TinyEncryptConfigEnvelop {
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<Vec<String>>,
pub public_part: String,
pub profiles: Option<Vec<String>>,
}
impl TinyEncryptConfig {
pub fn load_default() -> XResult<Self> {
let resolved_file = resolve_file_path(TINY_ENC_CONFIG_FILE);
pub fn load_default(config: &Option<String>) -> XResult<Self> {
let resolved_file0 = config.clone().or_else(|| rust_util_env::env_var(ENV_TINY_ENC_CONFIG_FILE));
let resolved_file_1 = resolve_file_path(TINY_ENC_CONFIG_FILE);
let resolved_file_2 = resolve_file_path(TINY_ENC_CONFIG_FILE_2);
let config_file = if fs::metadata(&resolved_file).is_ok() {
debugging!("Load config from: {resolved_file}");
resolved_file
let resolved_file_3 = resolve_file_path(TINY_ENC_CONFIG_FILE_3);
if let Some(resolved_file) = resolved_file0 {
if resolved_file.starts_with("base64:") {
let decoded_resolved_bytes_result = decode_base64(&resolved_file.chars().skip(7).collect::<String>());
let decoded_resolved_bytes = opt_result!(decoded_resolved_bytes_result, "Decode base64 failed: {}");
let decoded_resolved_content = opt_result!(String::from_utf8(decoded_resolved_bytes), "Decode UTF-8 string failed: {}");
return Self::load_content(&decoded_resolved_content, "<env>");
}
debugging!("Found tiny encrypt config file: {}", &resolved_file);
return Self::load_file(&resolved_file)
}
let config_file = if fs::metadata(&resolved_file_1).is_ok() {
debugging!("Load config from: {resolved_file_1}");
resolved_file_1
} else if fs::metadata(&resolved_file_2).is_ok() {
debugging!("Load config from: {resolved_file_2}");
resolved_file_2
} else if fs::metadata(&resolved_file_3).is_ok() {
debugging!("Load config from: {resolved_file_3}");
resolved_file_3
} else {
warning!("Cannot find config file from:\n- {resolved_file}\n- {resolved_file_2}");
resolved_file
warning!("Cannot find config file from:\n- {resolved_file_1}\n- {resolved_file_2}\n- {resolved_file_3}");
resolved_file_1
};
Self::load(&config_file)
Self::load_file(&config_file)
}
pub fn load(file: &str) -> XResult<Self> {
pub fn load_file(file: &str) -> XResult<Self> {
let resolved_file = resolve_file_path(file);
let config_contents = opt_result!(
let config_content = opt_result!(
fs::read_to_string(resolved_file),
"Read config file: {}, failed: {}",
file
);
let mut config: TinyEncryptConfig = opt_result!(
serde_json::from_str(&config_contents),
Self::load_content(&config_content, file)
}
pub fn load_content(config_content: &str, file: &str) -> XResult<Self> {
let config: TinyEncryptConfig = opt_result!(
serde_json::from_str(&config_content),
"Parse config file: {}, failed: {}",
file
);
let mut splited_profiles = HashMap::new();
for (k, v) in config.profiles.into_iter() {
if !k.contains(',') {
splited_profiles.insert(k, v);
} else {
k.split(',')
.map(|k| k.trim())
.filter(|k| !k.is_empty())
.for_each(|k| {
splited_profiles.insert(k.to_string(), v.clone());
});
}
}
config.profiles = splited_profiles;
debugging!("Config: {:#?}", config);
let config = load_includes_and_merge(config);
debugging!("Final config: {:#?}", config);
if let Some(environment) = &config.environment {
for (k, v) in environment {
@@ -220,7 +239,7 @@ impl TinyEncryptConfig {
self.envelops.iter().for_each(|e| {
key_ids.push(e.kid.to_string());
});
} else if let Some(kids) = self.profiles.get(profile) {
} else if let Some(kids) = self.get_profile(profile) {
kids.iter().for_each(|k| key_ids.push(k.to_string()));
}
}
@@ -233,7 +252,8 @@ impl TinyEncryptConfig {
});
}
if key_ids.is_empty() {
return simple_error!("Profile or key filter cannot find any valid envelopes");
// return simple_error!("Profile or key filter cannot find any valid envelopes");
return Ok(vec![]);
}
for key_id in &key_ids {
for envelop in self.find_by_kid_or_type(key_id) {
@@ -243,7 +263,8 @@ impl TinyEncryptConfig {
let mut envelops: Vec<_> = matched_envelops_map.values().copied().collect();
if envelops.is_empty() {
return simple_error!("Profile or key filter cannot find any valid envelopes");
// return simple_error!("Profile or key filter cannot find any valid envelopes");
return Ok(vec![]);
}
envelops.sort_by(|e1, e2| {
if e1.r#type < e2.r#type {
@@ -274,3 +295,172 @@ pub fn resolve_path_namespace(
Some(config) => config.resolve_path_namespace(path, append_te),
}
}
pub fn load_includes_and_merge(mut config: TinyEncryptConfig) -> TinyEncryptConfig {
debugging!("Config includes: {:?}", &config.includes);
if let Some(includes) = &config.includes {
let sub_configs = search_include_configs(includes);
debugging!(
"Found {} sub configs, detail {:?}",
sub_configs.len(),
sub_configs
);
for sub_config in &sub_configs {
// merge environment
if let Some(sub_environment) = &sub_config.environment {
match &mut config.environment {
None => {
config.environment = Some(sub_environment.clone());
}
Some(env) => {
for (k, v) in sub_environment {
match env.get_mut(k) {
None => {
env.insert(k.clone(), v.clone());
}
Some(env_val) => {
match (env_val, v) {
(StringOrVecString::Vec(env_value_vec), StringOrVecString::Vec(v_vec)) => {
for vv in v_vec {
if !env_value_vec.contains(vv) {
env_value_vec.push(vv.clone());
}
}
}
_ => {
warning!("Duplicate or mis-match environment value, key: {}", k);
}
}
}
}
}
}
}
}
// merge profiles
for sub_envelop in &sub_config.envelops {
let filter_envelops = config.envelops.iter().filter(|e| {
e.kid == sub_envelop.kid || (e.sid.is_some() && e.sid == sub_envelop.sid)
}).collect::<Vec<_>>();
if !filter_envelops.is_empty() {
warning!("Duplication kid: {} or sid: {:?}", sub_envelop.kid, sub_envelop.sid);
continue;
}
config.envelops.push(sub_envelop.clone());
}
// deal with envelop profiles
let mut sub_profiles: HashMap<String, Vec<String>> = match &sub_config.profiles {
None => HashMap::new(),
Some(sub_profiles) => sub_profiles.clone(),
};
for envelop in &sub_config.envelops {
if let Some(profiles) = &envelop.profiles {
let kid = envelop.kid.clone();
for profile in profiles {
match sub_profiles.get_mut(profile) {
None => {
sub_profiles.insert(profile.clone(), vec![kid.clone()]);
}
Some(kids) => {
if !kids.contains(&kid) {
kids.push(kid.clone());
}
}
}
}
}
}
// merge profiles
match &mut config.profiles {
None => {
config.profiles = Some(sub_profiles.clone());
}
Some(profiles) => {
for (k, v) in &sub_profiles {
match profiles.get_mut(k) {
None => {
profiles.insert(k.clone(), v.clone());
}
Some(env_val) => {
for vv in v {
env_val.push(vv.clone());
}
}
}
}
}
}
}
}
if let Some(profiles) = &mut config.profiles {
let all_key_ids = config.envelops.iter().map(|e| e.kid.clone()).collect::<Vec<_>>();
if profiles.contains_key("__all__") {
warning!("Key __all__ in profiles exists")
} else {
profiles.insert("__all__".to_string(), all_key_ids);
}
}
config
}
pub fn search_include_configs(includes_path: &str) -> Vec<TinyEncryptConfig> {
let includes_path = if includes_path.starts_with("$") {
let includes_path_env_var = includes_path.chars().skip(1).collect::<String>();
match rust_util_env::env_var(&includes_path_env_var) {
Some(includes_path) => includes_path,
None => {
warning!("Cannot find env var: {}", &includes_path_env_var);
return vec![];
}
}
} else {
includes_path.to_string()
};
let includes_path = &includes_path;
let mut sub_configs = vec![];
let read_dir = match fs::read_dir(includes_path) {
Ok(read_dir) => read_dir,
Err(e) => {
warning!("Read dir: {}, failed: {}", includes_path, e);
return sub_configs;
}
};
for entry in read_dir {
let entry = match entry {
Ok(entry) => entry,
Err(e) => {
warning!("Read dir: {} entry, failed: {}", includes_path, e);
continue;
}
};
let file_name = entry.file_name();
let file_name = file_name.to_str();
let file_name = match file_name {
Some(file_name) => file_name,
None => continue,
};
if file_name.ends_with(".tinyencrypt.json") {
debugging!("Matches config file: {}", file_name);
let file_path = entry.path();
let content = match fs::read_to_string(entry.path()) {
Ok(content) => content,
Err(e) => {
warning!("Read config file: {:?}, failed: {}", file_path, e);
continue;
}
};
let config = match serde_json::from_str::<TinyEncryptConfig>(&content) {
Ok(config) => config,
Err(e) => {
warning!("Parse config file: {:?}, failed: {}", file_path, e);
continue;
}
};
sub_configs.push(config);
}
}
sub_configs
}

View File

@@ -3,16 +3,22 @@ pub const ENC_AES256_GCM_P256: &str = "aes256-gcm-p256";
pub const ENC_AES256_GCM_P384: &str = "aes256-gcm-p384";
pub const ENC_AES256_GCM_X25519: &str = "aes256-gcm-x25519";
pub const ENC_AES256_GCM_KYBER1204: &str = "aes256-gcm-kyber1204";
pub const ENC_AES256_GCM_MLKEM768: &str = "aes256-gcm-mlkem768";
pub const ENC_AES256_GCM_MLKEM1024: &str = "aes256-gcm-mlkem1024";
pub const ENC_CHACHA20_POLY1305_P256: &str = "chacha20-poly1305-p256";
pub const ENC_CHACHA20_POLY1305_P384: &str = "chacha20-poly1305-p384";
pub const ENC_CHACHA20_POLY1305_X25519: &str = "chacha20-poly1305-x25519";
pub const ENC_CHACHA20_POLY1305_KYBER1204: &str = "chacha20-poly1305-kyber1204";
pub const ENC_CHACHA20_POLY1305_MLKEM768: &str = "chacha20-poly1305-mlkem768";
pub const ENC_CHACHA20_POLY1305_MLKEM1024: &str = "chacha20-poly1305-mlkem1024";
// Extend and config file
pub const TINY_ENC_FILE_EXT: &str = ".tinyenc";
pub const TINY_ENC_PEM_FILE_EXT: &str = ".tinyenc.pem";
pub const ENV_TINY_ENC_CONFIG_FILE: &str = "TINY_ENCRYPT_CONFIG_FILE";
pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json";
pub const TINY_ENC_CONFIG_FILE_2: &str = "/etc/tinyencrypt/config-rs.json";
pub const TINY_ENC_CONFIG_FILE_2: &str = "~/.config/tinyencrypt-rs.json";
pub const TINY_ENC_CONFIG_FILE_3: &str = "/etc/tinyencrypt/config-rs.json";
pub const TINY_ENC_PEM_NAME: &str = "TINY ENCRYPT";

View File

@@ -35,6 +35,7 @@ pub use cmd_initpiv::init_piv;
pub use cmd_version::CmdVersion;
pub use cmd_version::version;
pub use config::TinyEncryptConfig;
pub use util_log::init_tiny_encrypt_log;
mod consts;
mod util;
@@ -75,4 +76,7 @@ mod util_keychainstatic;
mod cmd_execenv;
mod util_keychainkey;
mod util_simple_pbe;
mod util_log;
mod temporary_key;
mod util_mlkem;

View File

@@ -11,7 +11,12 @@ 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};
use tiny_encrypt::{init_tiny_encrypt_log, CmdConfig, CmdDirectDecrypt, CmdEncrypt, CmdInfo, CmdSimpleDecrypt, CmdSimpleEncrypt, CmdVersion};
use zeroizing_alloc::ZeroAlloc;
#[global_allocator]
static ALLOC: ZeroAlloc<std::alloc::System> = ZeroAlloc(std::alloc::System);
#[derive(Debug, Parser)]
#[command(name = "tiny-encrypt-rs")]
@@ -27,11 +32,11 @@ enum Commands {
#[command(arg_required_else_help = true, short_flag = 'e')]
Encrypt(CmdEncrypt),
/// Simple encrypt message
#[command(arg_required_else_help = true)]
#[command(arg_required_else_help = true, short_flag = 'E')]
SimpleEncrypt(CmdSimpleEncrypt),
#[cfg(feature = "decrypt")]
/// Simple decrypt message
#[command(arg_required_else_help = true)]
#[command(arg_required_else_help = true, short_flag = 'D')]
SimpleDecrypt(CmdSimpleDecrypt),
#[cfg(feature = "decrypt")]
/// Decrypt file(s)
@@ -64,6 +69,8 @@ enum Commands {
}
fn main() -> XResult<()> {
init_tiny_encrypt_log();
let args = Cli::parse();
match args.command {
Commands::Encrypt(cmd_encrypt) => tiny_encrypt::encrypt(cmd_encrypt),

View File

@@ -86,12 +86,30 @@ pub enum TinyEncryptEnvelopType {
// Secure Enclave ECDH P256
#[serde(rename = "key-p256")]
KeyP256,
// Secure Enclave ML-KEM 768
#[serde(rename = "key-mlkem768")]
KeyMlKem768,
// Secure Enclave ML-KEM 1024
#[serde(rename = "key-mlkem1024")]
KeyMlKem1024,
// PIV ECDH P256
#[serde(rename = "piv-p256", alias = "ecdh")]
PivP256,
// 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,
// External ML-KEM 768
#[serde(rename = "ext-mlkem768")]
ExtMlKem768,
// External ML-KEM 1024
#[serde(rename = "ext-mlkem1024")]
ExtMlKem1024,
// PIV RSA
#[serde(rename = "piv-rsa")]
PivRsa,
@@ -116,6 +134,12 @@ impl TinyEncryptEnvelopType {
TinyEncryptEnvelopType::StaticX25519 => "static-x25519",
TinyEncryptEnvelopType::StaticKyber1024 => "static-kyber1024",
TinyEncryptEnvelopType::KeyP256 => "key-p256",
TinyEncryptEnvelopType::KeyMlKem768 => "key-mlkem768",
TinyEncryptEnvelopType::KeyMlKem1024 => "key-mlkem1024",
TinyEncryptEnvelopType::ExtP256 => "ext-p256",
TinyEncryptEnvelopType::ExtP384 => "ext-p384",
TinyEncryptEnvelopType::ExtMlKem768 => "ext-mlkem768",
TinyEncryptEnvelopType::ExtMlKem1024 => "ext-mlkem1024",
TinyEncryptEnvelopType::PivP256 => "piv-p256",
TinyEncryptEnvelopType::PivP384 => "piv-p384",
TinyEncryptEnvelopType::PivRsa => "piv-rsa",
@@ -124,15 +148,44 @@ impl TinyEncryptEnvelopType {
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"pgp-rsa" => Some(TinyEncryptEnvelopType::PgpRsa),
"pgp-x25519" => Some(TinyEncryptEnvelopType::PgpX25519),
"gpg" => Some(TinyEncryptEnvelopType::Gpg),
"static-x25519" => Some(TinyEncryptEnvelopType::StaticX25519),
"static-kyber1024" => Some(TinyEncryptEnvelopType::StaticKyber1024),
"key-p256" => Some(TinyEncryptEnvelopType::KeyP256),
"key-mlkem768" => Some(TinyEncryptEnvelopType::KeyMlKem768),
"key-mlkem1024" => Some(TinyEncryptEnvelopType::KeyMlKem1024),
"ext-p256" => Some(TinyEncryptEnvelopType::ExtP256),
"ext-p384" => Some(TinyEncryptEnvelopType::ExtP384),
"ext-mlkem768" => Some(TinyEncryptEnvelopType::ExtMlKem768),
"ext-mlkem1024" => Some(TinyEncryptEnvelopType::ExtMlKem1024),
"piv-p256" => Some(TinyEncryptEnvelopType::PivP256),
"piv-p384" => Some(TinyEncryptEnvelopType::PivP384),
"piv-rsa" => Some(TinyEncryptEnvelopType::PivRsa),
"age" => Some(TinyEncryptEnvelopType::Age),
"kms" => Some(TinyEncryptEnvelopType::Kms),
_ => None,
}
}
pub fn auto_select(&self) -> bool {
match self {
TinyEncryptEnvelopType::StaticX25519
| TinyEncryptEnvelopType::StaticKyber1024
| TinyEncryptEnvelopType::KeyP256
| TinyEncryptEnvelopType::KeyMlKem768
| TinyEncryptEnvelopType::KeyMlKem1024
| TinyEncryptEnvelopType::Gpg
| TinyEncryptEnvelopType::Kms => true,
TinyEncryptEnvelopType::PgpRsa
| TinyEncryptEnvelopType::PgpX25519
| TinyEncryptEnvelopType::ExtP256
| TinyEncryptEnvelopType::ExtP384
| TinyEncryptEnvelopType::ExtMlKem768
| TinyEncryptEnvelopType::ExtMlKem1024
| TinyEncryptEnvelopType::PivP256
| TinyEncryptEnvelopType::PivP384
| TinyEncryptEnvelopType::PivRsa
@@ -145,6 +198,8 @@ impl TinyEncryptEnvelopType {
TinyEncryptEnvelopType::PgpRsa
| TinyEncryptEnvelopType::PgpX25519
| TinyEncryptEnvelopType::KeyP256
| TinyEncryptEnvelopType::KeyMlKem768
| TinyEncryptEnvelopType::KeyMlKem1024
| TinyEncryptEnvelopType::PivP256
| TinyEncryptEnvelopType::PivP384
| TinyEncryptEnvelopType::PivRsa
@@ -153,7 +208,11 @@ impl TinyEncryptEnvelopType {
| TinyEncryptEnvelopType::StaticKyber1024
| TinyEncryptEnvelopType::Kms => Some(false),
// GPG is unknown(hardware/software)
TinyEncryptEnvelopType::Gpg => None,
TinyEncryptEnvelopType::Gpg
| TinyEncryptEnvelopType::ExtP256
| TinyEncryptEnvelopType::ExtP384
| TinyEncryptEnvelopType::ExtMlKem768
| TinyEncryptEnvelopType::ExtMlKem1024 => None,
}
}
}

69
src/temporary_key.rs Normal file
View File

@@ -0,0 +1,69 @@
// syntax
// tiny-encrypt-key:type:sid:key_id:public_part[?key=value]
// e.g.
// tiny-encrypt-key:ext-p256:ext-key-1:02536aef5742b4288f1b44b3cc96f1556c35e4fac4e8e117e1f7ae091e42d0835b:04536aef5742b4288f1b44b3cc96f1556c35e4fac4e8e117e1f7ae091e42d0835bf3f95d932c22a74a91859bd7fdd8829a02d38cf4ec598b1cf6e02fa09f707a6f
use crate::config::TinyEncryptConfigEnvelop;
use crate::spec::TinyEncryptEnvelopType;
use rust_util::{debugging, iff, opt_result, opt_value_result, simple_error, XResult};
const TINY_ENCRYPT_KEY_PREFIX: &str = "tiny-encrypt-key:";
pub fn serialize_config_envelop(config_envelop: &TinyEncryptConfigEnvelop) -> String {
let mut s = String::new();
s.push_str(TINY_ENCRYPT_KEY_PREFIX);
s.push_str(config_envelop.r#type.get_name());
s.push(':');
s.push_str(&encode(config_envelop.sid.as_deref().unwrap_or("")));
s.push(':');
s.push_str(&encode(&config_envelop.kid));
s.push(':');
s.push_str(&encode(&config_envelop.public_part));
s
}
pub fn parse_temporary_keys(temporary_keys: &Option<Vec<String>>) -> XResult<Vec<TinyEncryptConfigEnvelop>> {
let mut temporary_envelops = vec![];
if let Some(temporary_key) = temporary_keys {
for t_key in temporary_key {
let envelop = opt_result!(deserialize_config_envelop(t_key), "Parse temporary key: {} failed: {}", t_key);
temporary_envelops.push(envelop);
}
debugging!("Temporary envelops: {:?}", temporary_envelops);
}
Ok(temporary_envelops)
}
pub fn deserialize_config_envelop(k: &str) -> XResult<TinyEncryptConfigEnvelop> {
if !k.starts_with(TINY_ENCRYPT_KEY_PREFIX) {
return simple_error!("invalid temporary key");
}
let k_parts = k.split(":").collect::<Vec<_>>();
if k_parts.len() != 5 {
return simple_error!("invalid temporary key (parts)");
}
let envelop_type = opt_value_result!(
TinyEncryptEnvelopType::from_name(k_parts[1]), "Unknown envelop type: {}", k_parts[1]);
Ok(TinyEncryptConfigEnvelop {
r#type: envelop_type,
sid: iff!(k_parts[2].is_empty(), None, Some(decode(k_parts[2])?)),
kid: decode(k_parts[3])?,
desc: None,
args: None,
public_part: decode(k_parts[4])?,
profiles: None,
})
}
fn encode(s: &str) -> String {
percent_encoding::utf8_percent_encode(s, percent_encoding::NON_ALPHANUMERIC).to_string()
}
fn decode(s: &str) -> XResult<String> {
Ok(opt_result!(
percent_encoding::percent_decode_str(s).decode_utf8(),
"decode: {} failed: {}",
s
)
.to_string())
}

View File

@@ -40,12 +40,19 @@ pub fn read_stdin() -> XResult<Vec<u8>> {
}
pub fn read_pin(pin: &Option<String>) -> XResult<String> {
let rpin = match pin {
Some(pin) => pin.to_string(),
None => if is_use_default_pin() {
"123456".into()
let mut ask_use_default_pin = true;
if let Some(pin) = pin {
if pin == "#INPUT#" {
ask_use_default_pin = false;
} else {
let pin_entry = util_env::get_pin_entry().unwrap_or_else(|| "pinentry".to_string());
return Ok(pin.to_string());
}
}
if ask_use_default_pin && is_use_default_pin() {
return Ok("123456".into());
}
let rpin = {
let pin_entry = util_env::get_default_pin_entry().unwrap_or_else(|| "pinentry".to_string());
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
let secret = input
.with_description("Please input your PIN.")
@@ -57,7 +64,6 @@ pub fn read_pin(pin: &Option<String>) -> XResult<String> {
} else {
opt_result!(rpassword::prompt_password("Please input PIN: "), "Read PIN failed: {}")
}
}
};
Ok(rpin)
}
@@ -66,7 +72,7 @@ pub fn read_password(password: &Option<String>) -> XResult<String> {
let rpassword = match password {
Some(pin) => pin.to_string(),
None => {
let pin_entry = util_env::get_pin_entry().unwrap_or_else(|| "pinentry".to_string());
let pin_entry = util_env::get_default_pin_entry().unwrap_or_else(|| "pinentry".to_string());
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
let secret = input
.with_description("Please input your password.")

View File

@@ -1,4 +1,5 @@
pub mod ecdh_p256 {
use std::ops::Deref;
use p256::{EncodedPoint, PublicKey};
use p256::ecdh::EphemeralSecret;
use p256::elliptic_curve::sec1::FromEncodedPoint;
@@ -15,11 +16,12 @@ pub mod ecdh_p256 {
let epk = esk.public_key();
let shared_secret = esk.diffie_hellman(&public_key);
let epk_public_key_der = opt_result!(epk.to_public_key_der(), "Convert epk to SPKI failed: {}");
Ok((shared_secret.raw_secret_bytes().as_slice().to_vec(), epk_public_key_der.to_vec()))
Ok((shared_secret.raw_secret_bytes().deref().to_vec(), epk_public_key_der.to_vec()))
}
}
pub mod ecdh_p384 {
use std::ops::Deref;
use p384::{EncodedPoint, PublicKey};
use p384::ecdh::EphemeralSecret;
use p384::elliptic_curve::sec1::FromEncodedPoint;
@@ -36,7 +38,7 @@ pub mod ecdh_p384 {
let epk = esk.public_key();
let shared_secret = esk.diffie_hellman(&public_key);
let epk_public_key_der = opt_result!(epk.to_public_key_der(), "Convert epk to SPKI failed: {}");
Ok((shared_secret.raw_secret_bytes().as_slice().to_vec(), epk_public_key_der.to_vec()))
Ok((shared_secret.raw_secret_bytes().deref().to_vec(), epk_public_key_der.to_vec()))
}
}

View File

@@ -1,4 +1,4 @@
use std::env;
use std::{env, fs};
use rust_util::util_env as rust_util_env;
use rust_util::{debugging, util_env, warning};
@@ -16,9 +16,10 @@ 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 = env::var(TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM).ok();
let env_default_algorithm = rust_util_env::env_var(TINY_ENCRYPT_ENV_DEFAULT_ALGORITHM);
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();
@@ -36,15 +37,25 @@ pub fn get_pin() -> Option<String> {
}
pub fn get_key_id() -> Option<String> {
env::var(TINY_ENCRYPT_ENV_KEY_ID).ok()
rust_util_env::env_var(TINY_ENCRYPT_ENV_KEY_ID)
}
pub fn get_gpg_cmd() -> Option<String> {
env::var(TINY_ENCRYPT_ENV_GPG_COMMAND).ok()
rust_util_env::env_var(TINY_ENCRYPT_ENV_GPG_COMMAND)
}
pub fn get_default_pin_entry() -> Option<String> {
let default_pin_entry = "/usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac";
if let Ok(meta) = fs::metadata(default_pin_entry) {
if meta.is_file() {
return Some(default_pin_entry.to_string());
}
}
get_pin_entry()
}
pub fn get_pin_entry() -> Option<String> {
env::var(TINY_ENCRYPT_ENV_PIN_ENTRY).ok()
rust_util_env::env_var(TINY_ENCRYPT_ENV_PIN_ENTRY)
}
pub fn get_auto_select_key_ids() -> Option<Vec<String>> {
@@ -56,7 +67,7 @@ pub fn get_auto_compress_file_exts() -> Option<Vec<String>> {
}
pub fn get_default_compress() -> Option<bool> {
env::var(TINY_ENCRYPT_ENV_DEFAULT_COMPRESS).ok().map(|val| util_env::is_on(&val))
rust_util_env::env_var(TINY_ENCRYPT_ENV_DEFAULT_COMPRESS).map(|val| util_env::is_on(&val))
}
pub fn get_no_progress() -> bool {
@@ -72,7 +83,7 @@ pub fn get_use_dialoguer() -> bool {
}
fn get_env_with_split(env_name: &str) -> Option<Vec<String>> {
let val = env::var(env_name).ok();
let val = rust_util_env::env_var(env_name);
debugging!("Environment variable {} = {:?}", env_name, &val);
val.map(|env_values| {
env_values.split(',').map(ToString::to_string).collect::<Vec<_>>()

View File

@@ -68,7 +68,7 @@ pub fn gpg_encrypt(key_id: &str, message: &[u8]) -> XResult<String> {
let stderr = String::from_utf8_lossy(&encrypt_output.stderr).to_string();
if !encrypt_output.status.success() {
return simple_error!(
"GPG encrypt failed: {:?}\n- stdout: {}\n- stderr: {}",
"GPG encrypt failed:\n- exit code: [{:?}]\n- stdout: [{}]\n- stderr: [{}]",
encrypt_output.status.code(), stdout, stderr
);
}
@@ -94,7 +94,7 @@ pub fn gpg_decrypt(message: &str) -> XResult<Vec<u8>> {
let stderr = String::from_utf8_lossy(&decrypt_output.stderr).to_string();
if !decrypt_output.status.success() {
return simple_error!(
"GPG decrypt failed: {:?}\n- stdout: {}\n- stderr: {}",
"GPG decrypt failed:\n- exit code: [{:?}]\n- stdout: [{}]\n- stderr: [{}]",
decrypt_output.status.code(), stdout, stderr
);
}

View File

@@ -1,30 +1,45 @@
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use rust_util::{simple_error, XResult};
use swift_secure_enclave_tool_rs::KeyPurpose;
use swift_secure_enclave_tool_rs::{ControlFlag, KeyMlKem, KeyPurpose};
use crate::spec::TinyEncryptEnvelopType;
pub fn is_support_se() -> bool {
swift_secure_enclave_tool_rs::is_secure_enclave_supported().unwrap_or(false)
}
pub fn decrypt_data(
envelop_type: TinyEncryptEnvelopType,
private_key_base64: &str,
ephemeral_public_key_bytes: &[u8],
) -> XResult<Vec<u8>> {
let private_key_representation = STANDARD.decode(private_key_base64)?;
let shared_secret = swift_secure_enclave_tool_rs::private_key_ecdh(
let shared_secret = match envelop_type {
TinyEncryptEnvelopType::KeyP256 => swift_secure_enclave_tool_rs::private_key_ecdh(
&private_key_representation,
ephemeral_public_key_bytes,
)?;
)?,
TinyEncryptEnvelopType::KeyMlKem768 => swift_secure_enclave_tool_rs::private_key_mlkem_ecdh(
KeyMlKem::MlKem768,
&private_key_representation,
ephemeral_public_key_bytes,
)?,
TinyEncryptEnvelopType::KeyMlKem1024 => swift_secure_enclave_tool_rs::private_key_mlkem_ecdh(
KeyMlKem::MlKem1024,
&private_key_representation,
ephemeral_public_key_bytes,
)?,
_ => return simple_error!("Invalid envelop type: {:?}", envelop_type),
};
Ok(shared_secret)
}
pub fn generate_se_p256_keypair() -> XResult<(String, String)> {
pub fn generate_se_p256_keypair(control_flag: ControlFlag) -> XResult<(String, String)> {
if !is_support_se() {
return simple_error!("Secure enclave is not supported.");
}
let key_material =
swift_secure_enclave_tool_rs::generate_ecdsa_keypair(KeyPurpose::KeyAgreement, true)?;
swift_secure_enclave_tool_rs::generate_keypair(KeyPurpose::KeyAgreement, control_flag)?;
Ok((
hex::encode(&key_material.public_key_point),
STANDARD.encode(&key_material.private_key_representation),

25
src/util_log.rs Normal file
View File

@@ -0,0 +1,25 @@
use rust_util::{util_env, util_msg, util_time};
use std::fs::File;
use std::io::Write;
use std::sync::mpsc::channel;
use std::{env, thread};
pub fn init_tiny_encrypt_log() {
if let Some(file_log) = util_env::env_var("TINY_ENCRYPT_FILE_LOG") {
let log_file_name = format!("{}-{}.log", file_log, util_time::get_current_millis());
if let Ok(mut log_file) = File::create(&log_file_name) {
env::set_var("LOGGER_LEVEL", "*"); // set logger to debug
log_file.write_all("Start logging...\n".as_bytes()).ok();
let (sender, receiver) = channel::<String>();
util_msg::set_logger_sender(sender);
thread::spawn(move || loop {
let m = match receiver.recv() {
Ok(msg) => format!("{}\n", msg),
Err(e) => format!("{}\n", e),
};
log_file.write_all(m.as_bytes()).ok();
});
}
}
}

49
src/util_mlkem.rs Normal file
View File

@@ -0,0 +1,49 @@
use ml_kem::kem::Encapsulate;
use ml_kem::{Encoded, EncodedSizeUser, KemCore, MlKem1024, MlKem768};
use rust_util::{opt_result, simple_error, XResult};
#[derive(Clone, Copy, Debug)]
pub enum MlKemAlgo {
MlKem768,
MlKem1024,
}
pub fn ml_kem_768_encapsulate(public_key: &[u8]) -> XResult<(Vec<u8>, Vec<u8>)> {
let encapsulation_key_encoded: Encoded<<MlKem768 as KemCore>::EncapsulationKey> = opt_result!(
public_key.try_into(),
"Parse ML-KEM 768 encapsulation key failed: {}"
);
let encapsulation_key =
<MlKem768 as KemCore>::EncapsulationKey::from_bytes(&encapsulation_key_encoded);
let mut rng = rand::rngs::OsRng;
let (ciphertext, shared_key) = opt_result!(
encapsulation_key.encapsulate(&mut rng),
"Encapsulate shared key failed: {:?}"
);
Ok((shared_key.0.to_vec(), ciphertext.0.to_vec()))
}
pub fn ml_kem_1024_encapsulate(public_key: &[u8]) -> XResult<(Vec<u8>, Vec<u8>)> {
let encapsulation_key_encoded: Encoded<<MlKem1024 as KemCore>::EncapsulationKey> = opt_result!(
public_key.try_into(),
"Parse ML-KEM 1024 encapsulation key failed: {}"
);
let encapsulation_key =
<MlKem1024 as KemCore>::EncapsulationKey::from_bytes(&encapsulation_key_encoded);
let mut rng = rand::rngs::OsRng;
let (ciphertext, shared_key) = opt_result!(
encapsulation_key.encapsulate(&mut rng),
"Encapsulate shared key failed: {:?}"
);
Ok((shared_key.0.to_vec(), ciphertext.0.to_vec()))
}
pub fn try_ml_kem_encapsulate(public_key: &[u8]) -> XResult<(Vec<u8>, Vec<u8>, MlKemAlgo)> {
if let Ok((shared_key, ciphertext)) = ml_kem_768_encapsulate(public_key) {
return Ok((shared_key, ciphertext, MlKemAlgo::MlKem768));
}
if let Ok((shared_key, ciphertext)) = ml_kem_1024_encapsulate(public_key) {
return Ok((shared_key, ciphertext, MlKemAlgo::MlKem1024));
}
simple_error!("Only supports ML-KEM 768 or ML-KEM 1024.")
}

View File

@@ -24,10 +24,10 @@ impl SimplePbkdfEncryptionV1 {
enc.starts_with(&format!("{SIMPLE_PBKDF_ENCRYPTION_PREFIX}."))
}
pub fn encrypt(password: &str, plaintext: &[u8]) -> XResult<SimplePbkdfEncryptionV1> {
pub fn encrypt(password: &str, plaintext: &[u8], iterations: &Option<u32>) -> XResult<SimplePbkdfEncryptionV1> {
let salt: [u8; 12] = random();
let repetition = 1000;
let iterations = 10000;
let iterations = iterations.unwrap_or(10000);
let key = simple_pbkdf(password.as_bytes(), &salt, repetition, iterations);
let key_bytes: [u8; 32] = opt_result!(key.try_into(), "Bad AES 256 key: {:?}");
@@ -166,7 +166,7 @@ fn simple_pbkdf(password: &[u8], salt: &[u8], repetition: u32, iterations: u32)
#[test]
fn test() {
let enc = SimplePbkdfEncryptionV1::encrypt("helloworld", "test".as_bytes()).unwrap();
let enc = SimplePbkdfEncryptionV1::encrypt("helloworld", "test".as_bytes(), &None).unwrap();
let enc_str = enc.to_string();
let enc2: SimplePbkdfEncryptionV1 = enc_str.try_into().unwrap();
assert_eq!(enc.to_string(), enc2.to_string());