feat: updates

This commit is contained in:
2023-09-09 17:39:03 +08:00
parent bce9f616fa
commit bc55d84978
10 changed files with 249 additions and 39 deletions

4
Cargo.lock generated
View File

@@ -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",

View File

@@ -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

View File

@@ -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
<br>
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"]
}
}
```

View File

@@ -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<String>,
}
pub fn decrypt(path: &PathBuf, pin: &Option<String>, slot: &Option<String>) -> 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<String>, slot: &Option<String>) -> 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);

77
src/cmd_encrypt.rs Normal file
View File

@@ -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<PathBuf>,
// Comment
pub comment: Option<String>,
// Encryption profile
pub profile: Option<String>,
}
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<Vec<TinyEncryptEnvelop>> {
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<TinyEncryptEnvelop> {
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<TinyEncryptEnvelop> {
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<u8>, Vec<u8>) {
// TODO use random
let key = [0u8; 32];
let nonce = [0u8; 12];
(key.into(), nonce.into())
}

View File

@@ -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);

View File

@@ -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<String>,
pub envelops: Vec<TinyEncryptConfigEnvelop>,
pub profiles: HashMap<String, Vec<String>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TinyEncryptConfigEnvelop {
pub r#type: TinyEncryptEnvelopType,
pub kid: String,
pub desc: Option<String>,
pub public_part: String,
}
impl TinyEncryptConfig {
pub fn load(file: &str) -> XResult<Self> {
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<String>) -> 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()
}
}

View File

@@ -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<PathBuf>,
},
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)
}
}
}

View File

@@ -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<String>,
pub encrypted_comment: Option<String>,
pub encrypted_meta: Option<String>,
pub compress: bool,
}
impl TinyEncryptMeta {
pub fn new(metadata: &Metadata, enc_metadata: &EncMetadata, nonce: &[u8], envelops: Vec<TinyEncryptEnvelop>) -> 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(),
});

View File

@@ -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<u8> {
let input = hex::decode(sha256::digest(input)).unwrap();
@@ -24,6 +25,10 @@ pub fn decode_base64(input: &str) -> XResult<Vec<u8>> {
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<Vec<u8>> {
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"