Compare commits
39 Commits
84ee9d1b34
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
0604745f82
|
|||
|
1f4db9d1b0
|
|||
|
cdec79e4dc
|
|||
|
3812001474
|
|||
| 3708781390 | |||
|
a6397fc45a
|
|||
| 0e6b590f32 | |||
|
b0ee1f2c59
|
|||
|
c176021c81
|
|||
|
d75c589b66
|
|||
|
0c4663f7f0
|
|||
|
c446a52462
|
|||
|
9b0ecef9a0
|
|||
|
783b3e1962
|
|||
|
7b7878e2c1
|
|||
|
403eaf1669
|
|||
|
75ed193d86
|
|||
|
c8175e2654
|
|||
|
7c752eab41
|
|||
|
96ebf7f592
|
|||
|
b18a5ec3e2
|
|||
|
813b8b1b24
|
|||
|
6a84ae7f5c
|
|||
|
ff30fd42dd
|
|||
|
0ad8f83092
|
|||
|
1741c335db
|
|||
|
93cb3309bf
|
|||
|
6a24388a19
|
|||
|
b94acf9c31
|
|||
|
b91b29e22d
|
|||
|
12e828732e
|
|||
|
d98005d799
|
|||
|
e308809b20
|
|||
|
920aa92b0e
|
|||
|
20c54350ee
|
|||
|
044daaad7d
|
|||
|
f0f505bde3
|
|||
|
4ad735d840
|
|||
|
d831b606cd
|
978
Cargo.lock
generated
978
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@@ -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
|
||||
|
||||
63
README.md
63
README.md
@@ -114,17 +114,23 @@ 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 |
|
||||
| static-x25519 | ECDH(X25519) | Key Stored in macOS Keychain Access |
|
||||
| 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 |
|
||||
| piv-rsa | PKCS1-v1.5 | PIV Slot |
|
||||
| Type | Algorithm | Description |
|
||||
|------------------|---------------------|--------------------------------------------------------|
|
||||
| pgp-rsa | PKCS1-v1.5 | OpenPGP Encryption Key (Previous `pgp`) |
|
||||
| pgp-x25519 | ECDH(X25519) | OpenPGP Encryption Key |
|
||||
| gpg | OpenPGP | GnuPG Command |
|
||||
| static-x25519 | ECDH(X25519) | Key Stored in macOS Keychain Access |
|
||||
| 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 (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:
|
||||
|
||||
@@ -151,19 +157,24 @@ Smart Card(Yubikey) protected ECDH Encryption description as below:
|
||||
|
||||
Environment
|
||||
|
||||
| KEY | Comment |
|
||||
|----------------------------------|---------------------------------------------|
|
||||
| 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 |
|
||||
| TINY_ENCRYPT_NO_DEFAULT_PIN_HINT | Do not display default PIN hint |
|
||||
| TINY_ENCRYPT_USE_DIALOGUER | Use dialoguer |
|
||||
| TINY_ENCRYPT_PIN | PIV Card PIN |
|
||||
| TINY_ENCRYPT_KEY_ID | Default Key ID |
|
||||
| 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 |
|
||||
| EDITOR | Editor (Plaintext) |
|
||||
|
||||
| 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 |
|
||||
| TINY_ENCRYPT_NO_DEFAULT_PIN_HINT | Do not display default PIN hint |
|
||||
| TINY_ENCRYPT_USE_DIALOGUER | Use dialoguer |
|
||||
| TINY_ENCRYPT_PIN | PIV Card PIN |
|
||||
| TINY_ENCRYPT_KEY_ID | Default Key ID |
|
||||
| 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 |
|
||||
| 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
|
||||
```
|
||||
5
USAGE.md
5
USAGE.md
@@ -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>
|
||||
```
|
||||
|
||||
|
||||
5
justfile
5
justfile
@@ -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
|
||||
|
||||
@@ -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__");
|
||||
}
|
||||
println!("{}", serde_json::to_string_pretty(&config)?);
|
||||
return Ok(());
|
||||
}
|
||||
config_profiles(&cmd_version, &config)
|
||||
|
||||
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,15 +132,17 @@ 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 {
|
||||
let mut v2 = v.clone();
|
||||
v2.sort();
|
||||
let vs = v2.join(",");
|
||||
match reverse_map.get_mut(&vs) {
|
||||
None => { reverse_map.insert(vs, vec![(p, v)]); }
|
||||
Some(vec) => { vec.push((p, v)); }
|
||||
if let Some(profiles) = &config.profiles {
|
||||
for (p, v) in profiles {
|
||||
let mut v2 = v.clone();
|
||||
v2.sort();
|
||||
let vs = v2.join(",");
|
||||
match reverse_map.get_mut(&vs) {
|
||||
None => { reverse_map.insert(vs, vec![(p, v)]); }
|
||||
Some(vec) => { vec.push((p, v)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +151,9 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu
|
||||
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()
|
||||
@@ -153,4 +191,4 @@ fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResu
|
||||
println!("{}", table);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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)); }
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
258
src/config.rs
258
src/config.rs
@@ -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
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
13
src/main.rs
13
src/main.rs
@@ -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),
|
||||
|
||||
61
src/spec.rs
61
src/spec.rs
@@ -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
69
src/temporary_key.rs
Normal 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())
|
||||
}
|
||||
40
src/util.rs
40
src/util.rs
@@ -40,23 +40,29 @@ 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());
|
||||
if let Some(mut input) = PassphraseInput::with_binary(pin_entry) {
|
||||
let secret = input
|
||||
.with_description("Please input your PIN.")
|
||||
.with_prompt("PIN:")
|
||||
.interact();
|
||||
opt_result!(secret, "Read PIN from pinentry failed: {}")
|
||||
.expose_secret()
|
||||
.to_string()
|
||||
} else {
|
||||
opt_result!(rpassword::prompt_password("Please input PIN: "), "Read PIN failed: {}")
|
||||
}
|
||||
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.")
|
||||
.with_prompt("PIN:")
|
||||
.interact();
|
||||
opt_result!(secret, "Read PIN from pinentry failed: {}")
|
||||
.expose_secret()
|
||||
.to_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.")
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<_>>()
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
&private_key_representation,
|
||||
ephemeral_public_key_bytes,
|
||||
)?;
|
||||
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
25
src/util_log.rs
Normal 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
49
src/util_mlkem.rs
Normal 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.")
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user