feat: updates
This commit is contained in:
@@ -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
77
src/cmd_encrypt.rs
Normal 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())
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
28
src/main.rs
28
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<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)
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/spec.rs
49
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<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(),
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user