diff --git a/Cargo.lock b/Cargo.lock
index ac5b218..f976d2c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1696,9 +1696,9 @@ dependencies = [
[[package]]
name = "rust_util"
-version = "0.6.41"
+version = "0.6.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df24005feacce81f4ae340464b39c380f7e01e7225bfdef62d40cb44cb1c11d7"
+checksum = "cc01275355fe567d95aacf1c243d0c2c51fe9f94271a498c3fe8335d3c2b1a01"
dependencies = [
"lazy_static",
"libc",
diff --git a/Cargo.toml b/Cargo.toml
index af2d8b0..a386cef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ openpgp-card = "0.3.7"
openpgp-card-pcsc = "0.3.0"
reqwest = { version = "0.11.14", features = ["blocking", "rustls", "rustls-tls"] }
rpassword = "7.2.0"
-rust_util = "0.6.41"
+rust_util = "0.6.42"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
sha256 = "1.4.0"
@@ -31,3 +31,5 @@ yubikey = { version = "0.8.0", features = ["untested"] }
codegen-units = 1
opt-level = 'z'
lto = true
+panic = 'abort'
+strip = true
diff --git a/README.md b/README.md
index bd2b840..ba4e082 100644
--- a/README.md
+++ b/README.md
@@ -4,3 +4,33 @@ Tiny encrypt for Rust
> Tiny encrypt spec see: https://git.hatter.ink/hatter/tiny-encrypt-java
+TODOs:
+* Decrypt supports compress
+* Encrypt subcommand
+
+
+
+
+Encrypt config `~/.tinyencrypt/config-rs.json`:
+```json
+{
+ "envelops": [
+ {
+ "type": "pgp",
+ "kid": "KID-1",
+ "desc": "this is key 001",
+ "publicPart": "----- BEGIN OPENPGP ..."
+ },
+ {
+ "type": "ecdh",
+ "kid": "KID-2",
+ "desc": "this is key 002",
+ "publicPart": "04..."
+ }
+ ],
+ "profiles": {
+ "default": ["KID-1", "KID-2"],
+ "leve2": ["KID-2"]
+ }
+ }
+```
diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs
index 365945c..286f2a5 100644
--- a/src/cmd_decrypt.rs
+++ b/src/cmd_decrypt.rs
@@ -3,8 +3,8 @@ use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::str::FromStr;
-use clap::Args;
+use clap::Args;
use openpgp_card::crypto_data::Cryptogram;
use openpgp_card::OpenPgp;
use rust_util::{debugging, failure, opt_result, simple_error, success, util_term, XResult};
@@ -32,7 +32,17 @@ pub struct CmdDecrypt {
pub slot: Option,
}
-pub fn decrypt(path: &PathBuf, pin: &Option, slot: &Option) -> XResult<()> {
+pub fn decrypt(cmd_decrypt: CmdDecrypt) -> XResult<()> {
+ for path in &cmd_decrypt.paths {
+ match decrypt_single(path, &cmd_decrypt.pin, &cmd_decrypt.slot) {
+ Ok(_) => success!("Decrypt {} succeed", path.to_str().unwrap_or("N/A")),
+ Err(e) => failure!("Decrypt {} failed: {}", path.to_str().unwrap_or("N/A"), e),
+ }
+ }
+ Ok(())
+}
+
+pub fn decrypt_single(path: &PathBuf, pin: &Option, slot: &Option) -> XResult<()> {
let path_display = format!("{}", path.display());
if !path_display.ends_with(TINY_ENC_FILE_EXT) {
return simple_error!("File is not tiny encrypt file: {}", &path_display);
diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs
new file mode 100644
index 0000000..1c0be8f
--- /dev/null
+++ b/src/cmd_encrypt.rs
@@ -0,0 +1,77 @@
+use std::path::PathBuf;
+
+use clap::Args;
+use rust_util::{debugging, simple_error, XResult};
+
+use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
+use crate::spec::{TinyEncryptEnvelop, TinyEncryptEnvelopType};
+use crate::util::TINY_ENC_CONFIG_FILE;
+
+#[derive(Debug, Args)]
+pub struct CmdEncrypt {
+ /// Files need to be decrypted
+ pub paths: Vec,
+ // Comment
+ pub comment: Option,
+ // Encryption profile
+ pub profile: Option,
+}
+
+pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
+ let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?;
+ let envelops = config.find_envelops(&cmd_encrypt.profile);
+ if envelops.is_empty() {
+ return simple_error!("Cannot find any valid envelops");
+ }
+
+ let (key, nonce) = make_key_and_nonce();
+ let envelops = encrypt_envelops(&key, &envelops)?;
+
+ debugging!("Envelops: {:?}", envelops);
+
+ println!("Cmd encrypt: {:?}", cmd_encrypt);
+ Ok(())
+}
+
+fn encrypt_envelops(key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResult> {
+ let mut encrypted_envelops = vec![];
+ for envelop in envelops {
+ match envelop.r#type {
+ TinyEncryptEnvelopType::Pgp => {
+ encrypted_envelops.push(encrypt_envelop_pgp(key, envelop)?);
+ }
+ TinyEncryptEnvelopType::Ecdh => {
+ encrypted_envelops.push(encrypt_envelop_ecdh(key, envelop)?);
+ }
+ _ => return simple_error!("Not supported type: {:?}", envelop.r#type),
+ }
+ }
+ Ok(encrypted_envelops)
+}
+
+fn encrypt_envelop_ecdh(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult {
+ Ok(TinyEncryptEnvelop {
+ r#type: envelop.r#type,
+ kid: envelop.kid.clone(),
+ desc: envelop.desc.clone(),
+ encrypted_key: "".to_string(), // TODO ...
+ })
+}
+
+
+fn encrypt_envelop_pgp(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult {
+ Ok(TinyEncryptEnvelop {
+ r#type: envelop.r#type,
+ kid: envelop.kid.clone(),
+ desc: envelop.desc.clone(),
+ encrypted_key: "".to_string(), // TODO ...
+ })
+}
+
+fn make_key_and_nonce() -> (Vec, Vec) {
+ // TODO use random
+ let key = [0u8; 32];
+ let nonce = [0u8; 12];
+
+ (key.into(), nonce.into())
+}
\ No newline at end of file
diff --git a/src/cmd_info.rs b/src/cmd_info.rs
index 92a8c67..f134ddd 100644
--- a/src/cmd_info.rs
+++ b/src/cmd_info.rs
@@ -20,7 +20,7 @@ pub struct CmdInfo {
pub raw_meta: bool,
}
-pub fn info(cmd_info: &CmdInfo) -> XResult<()> {
+pub fn info(cmd_info: CmdInfo) -> XResult<()> {
let path_display = format!("{}", cmd_info.path.display());
let mut file_in = opt_result!(File::open(&cmd_info.path), "Open file: {} failed: {}", &path_display);
let meta = opt_result!(file::read_tiny_encrypt_meta_and_normalize(&mut file_in), "Read file: {}, failed: {}", &path_display);
diff --git a/src/config.rs b/src/config.rs
index 00de0b4..b823167 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,13 +1,72 @@
+use std::collections::HashMap;
+use std::fs;
+use rust_util::{opt_result, XResult};
+use rust_util::util_file::resolve_file_path;
+
use serde::{Deserialize, Serialize};
+use crate::spec::TinyEncryptEnvelopType;
+
+/// Config file sample:
+/// ~/.tinyencrypt/config-rs.json
+/// {
+/// "envelops": [
+/// {
+/// "type": "pgp",
+/// "kid": "KID-1",
+/// "desc": "this is key 001",
+/// "public_key": "----- BEGIN OPENPGP ..."
+/// },
+/// {
+/// "type": "ecdh",
+/// "kid": "KID-2",
+/// "desc": "this is key 002",
+/// "publicPart": "04..."
+/// }
+/// ],
+/// "profiles": {
+/// "default": ["KID-1", "KID-2"],
+/// "leve2": ["KID-2"]
+/// }
+/// }
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TinyEncryptConfig {
- // card cli is not used by tiny-encrypt-rs
- pub card_cli: String,
- pub default_key_name: String,
- pub local_private_key_pem_challenge: String,
- pub local_private_key_pem_encrypted: String,
- pub local_public_key_pem: String,
- pub pgp_encrypt_public_key_pem: Option,
+ pub envelops: Vec,
+ pub profiles: HashMap>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct TinyEncryptConfigEnvelop {
+ pub r#type: TinyEncryptEnvelopType,
+ pub kid: String,
+ pub desc: Option,
+ pub public_part: String,
+}
+
+impl TinyEncryptConfig {
+ pub fn load(file: &str) -> XResult {
+ let resolved_file = resolve_file_path(file);
+ let config_contents = opt_result!(fs::read_to_string(&resolved_file), "Read file: {}, failed: {}", file);
+ // TODO replace with Human JSON
+ Ok(opt_result!(serde_json::from_str(&config_contents), "Parse file: {}, failed: {}", file))
+ }
+
+ pub fn find_envelops(&self, profile: &Option) -> Vec<&TinyEncryptConfigEnvelop> {
+ let profile = profile.as_ref().map(String::as_str).unwrap_or("default");
+ let mut matched_envelops_map = HashMap::new();
+ if let Some(key_ids) = self.profiles.get(profile) {
+ for key_id in key_ids {
+ self.envelops.iter().for_each(|envelop| {
+ let is_matched = (&envelop.kid == key_id)
+ || key_id == &format!("type:{}", &envelop.r#type.get_name());
+ if is_matched {
+ matched_envelops_map.insert(&envelop.kid, envelop);
+ }
+ });
+ }
+ }
+ matched_envelops_map.values().map(|envelop| *envelop).collect()
+ }
}
diff --git a/src/main.rs b/src/main.rs
index 9a457e0..4bb0e0e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,10 @@
extern crate core;
-use std::path::PathBuf;
-
use clap::{Parser, Subcommand};
-use rust_util::{failure, information, success, XResult};
+use rust_util::XResult;
use crate::cmd_decrypt::CmdDecrypt;
+use crate::cmd_encrypt::CmdEncrypt;
use crate::cmd_info::CmdInfo;
mod util;
@@ -17,6 +16,7 @@ mod file;
mod card;
mod cmd_info;
mod cmd_decrypt;
+mod cmd_encrypt;
#[derive(Debug, Parser)]
#[command(name = "tiny-encrypt-rs")]
@@ -30,10 +30,7 @@ struct Cli {
enum Commands {
/// Encrypt file(s)
#[command(arg_required_else_help = true, short_flag = 'e')]
- Encrypt {
- /// Files need to be encrypted
- paths: Vec,
- },
+ Encrypt(CmdEncrypt),
/// Decrypt file(s)
#[command(arg_required_else_help = true, short_flag = 'd')]
Decrypt(CmdDecrypt),
@@ -45,21 +42,14 @@ enum Commands {
fn main() -> XResult<()> {
let args = Cli::parse();
match args.command {
- Commands::Encrypt { paths } => {
- paths.iter().for_each(|f| information!("{:?}", f));
- Ok(())
+ Commands::Encrypt(cmd_encrypt) => {
+ cmd_encrypt::encrypt(cmd_encrypt)
}
Commands::Decrypt(cmd_decrypt) => {
- for path in &cmd_decrypt.paths {
- match cmd_decrypt::decrypt(path, &cmd_decrypt.pin, &cmd_decrypt.slot) {
- Ok(_) => success!("Decrypt {} succeed", path.to_str().unwrap_or("N/A")),
- Err(e) => failure!("Decrypt {} failed: {}", path.to_str().unwrap_or("N/A"), e),
- }
- }
- Ok(())
+ cmd_decrypt::decrypt(cmd_decrypt)
}
- Commands::Info(command_info) => {
- cmd_info::info(&command_info)
+ Commands::Info(cmd_info) => {
+ cmd_info::info(cmd_info)
}
}
}
\ No newline at end of file
diff --git a/src/spec.rs b/src/spec.rs
index e2771e7..9e547c6 100644
--- a/src/spec.rs
+++ b/src/spec.rs
@@ -1,11 +1,13 @@
+use std::fs::Metadata;
+
+use rust_util::util_time;
+use rust_util::util_time::get_millis;
use serde::{Deserialize, Serialize};
-// pub const TINY_ENCRYPT_VERSION: &'static str = "1.0";
+use crate::util::{encode_base64, get_user_agent};
-// pub const ENVELOP_TYPE_KMS: &'static str = "kms";
-// pub const ENVELOP_TYPE_PGP: &'static str = "pgp";
-// pub const ENVELOP_TYPE_AGE: &'static str = "age";
-// pub const ENVELOP_TYPE_ECDH: &'static str = "ecdh";
+// pub const TINY_ENCRYPT_VERSION_10: &'static str = "1.0";
+pub const TINY_ENCRYPT_VERSION_11: &'static str = "1.1";
/// Specification: [Tiny Encrypt Spec V1.1](https://git.hatter.ink/hatter/tiny-encrypt-java/src/branch/master/TinyEncryptSpecV1.1.md)
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -43,6 +45,7 @@ pub struct TinyEncryptEnvelop {
pub encrypted_key: String,
}
+/// NOTICE: Kms and Age is not being supported in the future
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TinyEncryptEnvelopType {
#[serde(rename = "pgp")]
@@ -69,7 +72,41 @@ impl TinyEncryptEnvelopType {
}
}
+pub struct EncMetadata {
+ pub comment: Option,
+ pub encrypted_comment: Option,
+ pub encrypted_meta: Option,
+ pub compress: bool,
+}
+
impl TinyEncryptMeta {
+ pub fn new(metadata: &Metadata, enc_metadata: &EncMetadata, nonce: &[u8], envelops: Vec) -> Self {
+ TinyEncryptMeta {
+ version: TINY_ENCRYPT_VERSION_11.to_string(),
+ created: util_time::get_current_millis() as u64,
+ user_agent: get_user_agent(),
+ comment: enc_metadata.comment.to_owned(),
+ encrypted_comment: enc_metadata.encrypted_comment.to_owned(),
+ encrypted_meta: enc_metadata.encrypted_meta.to_owned(),
+ pgp_envelop: None,
+ pgp_fingerprint: None,
+ age_envelop: None,
+ age_recipient: None,
+ ecdh_envelop: None,
+ ecdh_point: None,
+ envelop: None,
+ envelops: Some(envelops),
+ encryption_algorithm: None,
+ nonce: encode_base64(nonce),
+ file_length: metadata.len(),
+ file_last_modified: match metadata.modified() {
+ Ok(modified) => get_millis(&modified) as u64,
+ Err(_) => 0,
+ },
+ compress: enc_metadata.compress,
+ }
+ }
+
pub fn normalize(&mut self) {
if self.envelops.is_none() {
self.envelops = Some(vec![]);
@@ -84,7 +121,7 @@ impl TinyEncryptMeta {
if let (Some(envelop), Some(envelops)) = (&self.envelop, &mut self.envelops) {
envelops.push(TinyEncryptEnvelop {
r#type: TinyEncryptEnvelopType::Kms,
- kid: "".into(),
+ kid: "".to_string(),
desc: None,
encrypted_key: envelop.into(),
});
diff --git a/src/util.rs b/src/util.rs
index 2ce470d..076eae1 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -7,6 +7,7 @@ use rust_util::{warning, XResult};
pub const ENC_AES256_GCM_P256: &str = "aes256-gcm-p256";
pub const TINY_ENC_FILE_EXT: &str = ".tinyenc";
+pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json";
pub fn simple_kdf(input: &[u8]) -> Vec {
let input = hex::decode(sha256::digest(input)).unwrap();
@@ -24,6 +25,10 @@ pub fn decode_base64(input: &str) -> XResult> {
Ok(general_purpose::STANDARD.decode(input)?)
}
+pub fn encode_base64(input: &[u8]) -> String {
+ general_purpose::STANDARD.encode(input)
+}
+
pub fn decode_base64_url_no_pad(input: &str) -> XResult> {
Ok(general_purpose::URL_SAFE_NO_PAD.decode(input)?)
}
@@ -46,7 +51,7 @@ pub fn read_number(hint: &str, from: usize, to: usize) -> usize {
}
}
-pub fn _get_user_agent() -> String {
+pub fn get_user_agent() -> String {
format!("TinyEncrypt-rs v{}@{}", env!("CARGO_PKG_VERSION"),
if cfg!(target_os = "macos") {
"MacOS"