feat: v0.1.0, encrypt works
This commit is contained in:
@@ -2,6 +2,7 @@ use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Instant;
|
||||
|
||||
use clap::Args;
|
||||
use openpgp_card::crypto_data::Cryptogram;
|
||||
@@ -25,10 +26,10 @@ pub struct CmdDecrypt {
|
||||
/// Files need to be decrypted
|
||||
pub paths: Vec<PathBuf>,
|
||||
/// PIN
|
||||
#[arg(long)]
|
||||
#[arg(long, short = 'p')]
|
||||
pub pin: Option<String>,
|
||||
/// SLOT
|
||||
#[arg(long)]
|
||||
#[arg(long, short = 's')]
|
||||
pub slot: Option<String>,
|
||||
}
|
||||
|
||||
@@ -49,11 +50,11 @@ pub fn decrypt_single(path: &PathBuf, pin: &Option<String>, slot: &Option<String
|
||||
|
||||
let mut file_in = opt_result!(File::open(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);
|
||||
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
||||
|
||||
let path_out = &path_display[0..path_display.len() - TINY_ENC_FILE_EXT.len()];
|
||||
util::require_file_not_exists(path_out)?;
|
||||
|
||||
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
||||
let selected_envelop = select_envelop(&meta)?;
|
||||
|
||||
let key = try_decrypt_key(selected_envelop, pin, slot)?;
|
||||
@@ -63,7 +64,11 @@ pub fn decrypt_single(path: &PathBuf, pin: &Option<String>, slot: &Option<String
|
||||
debugging!("Decrypt nonce: {}", hex::encode(&nonce));
|
||||
|
||||
let mut file_out = File::create(path_out)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let _ = decrypt_file(&mut file_in, &mut file_out, &key, &nonce, meta.compress)?;
|
||||
let encrypt_duration = start.elapsed();
|
||||
debugging!("Encrypt file: {} elapsed: {} ms", path_display, encrypt_duration.as_millis());
|
||||
|
||||
util::zeroize(key);
|
||||
util::zeroize(nonce);
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
use clap::Args;
|
||||
use rsa::Pkcs1v15Encrypt;
|
||||
use rust_util::{debugging, failure, opt_result, simple_error, success, XResult};
|
||||
|
||||
use crate::compress::GzStreamEncoder;
|
||||
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
|
||||
use crate::crypto_aes::aes_gcm_encrypt;
|
||||
use crate::crypto_rsa::parse_spki;
|
||||
use crate::spec::{EncMetadata, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
|
||||
use crate::util::{ENC_AES256_GCM_P256, encode_base64, make_key256_and_nonce, simple_kdf, TINY_ENC_CONFIG_FILE, zeroize};
|
||||
use crate::util;
|
||||
use crate::util::{ENC_AES256_GCM_P256, encode_base64, encode_base64_url_no_pad, make_key256_and_nonce, simple_kdf, TINY_ENC_CONFIG_FILE, zeroize};
|
||||
use crate::util_ecdh::compute_shared_secret;
|
||||
use crate::wrap_key::{WrapKey, WrapKeyHeader};
|
||||
|
||||
@@ -18,21 +23,28 @@ pub struct CmdEncrypt {
|
||||
/// Files need to be decrypted
|
||||
pub paths: Vec<PathBuf>,
|
||||
// Comment
|
||||
#[arg(long, short = 'c')]
|
||||
pub comment: Option<String>,
|
||||
// Comment
|
||||
#[arg(long, short = 'C')]
|
||||
pub encrypted_comment: Option<String>,
|
||||
// Encryption profile
|
||||
#[arg(long, short = 'p')]
|
||||
pub profile: Option<String>,
|
||||
#[arg(long, short = 'x')]
|
||||
pub compress: bool,
|
||||
}
|
||||
|
||||
pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
|
||||
let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?;
|
||||
debugging!("Found tiny encrypt config: {:?}", config);
|
||||
let envelops = config.find_envelops(&cmd_encrypt.profile)?;
|
||||
if envelops.is_empty() { return simple_error!("Cannot find any valid envelops"); }
|
||||
debugging!("Found envelops: {:?}", envelops);
|
||||
|
||||
debugging!("Cmd encrypt: {:?}", cmd_encrypt);
|
||||
for path in &cmd_encrypt.paths {
|
||||
match encrypt_single(path, &envelops) {
|
||||
match encrypt_single(path, &envelops, &cmd_encrypt) {
|
||||
Ok(_) => success!("Encrypt {} succeed", path.to_str().unwrap_or("N/A")),
|
||||
Err(e) => failure!("Encrypt {} failed: {}", path.to_str().unwrap_or("N/A"), e),
|
||||
}
|
||||
@@ -40,27 +52,92 @@ pub fn encrypt(cmd_encrypt: CmdEncrypt) -> XResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encrypt_single(path: &PathBuf, envelops: &[&TinyEncryptConfigEnvelop]) -> XResult<()> {
|
||||
fn encrypt_single(path: &PathBuf, envelops: &[&TinyEncryptConfigEnvelop], cmd_encrypt: &CmdEncrypt) -> XResult<()> {
|
||||
let path_display = format!("{}", path.display());
|
||||
util::require_none_tiny_enc_file_and_exists(path)?;
|
||||
|
||||
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
||||
|
||||
let path_out = format!("{}{}", path_display, util::TINY_ENC_FILE_EXT);
|
||||
util::require_file_not_exists(path_out.as_str())?;
|
||||
|
||||
let (key, nonce) = make_key256_and_nonce();
|
||||
let envelops = encrypt_envelops(&key, &envelops)?;
|
||||
|
||||
let encrypted_comment = match &cmd_encrypt.encrypted_comment {
|
||||
None => None,
|
||||
Some(encrypted_comment) => Some(encode_base64(
|
||||
&aes_gcm_encrypt(&key, &nonce, encrypted_comment.as_bytes())?))
|
||||
};
|
||||
|
||||
let file_metadata = opt_result!(fs::metadata(path), "Read file: {} meta failed: {}", path.display());
|
||||
let enc_metadata = EncMetadata {
|
||||
comment: None,
|
||||
encrypted_comment: None,
|
||||
comment: cmd_encrypt.comment.clone(),
|
||||
encrypted_comment,
|
||||
encrypted_meta: None,
|
||||
compress: false,
|
||||
compress: cmd_encrypt.compress,
|
||||
};
|
||||
|
||||
let encrypt_meta = TinyEncryptMeta::new(&file_metadata, &enc_metadata, &nonce, envelops);
|
||||
debugging!("Encrypted meta: {:?}", encrypt_meta);
|
||||
|
||||
// TODO write to file and do encrypt
|
||||
|
||||
let mut file_out = File::create(&path_out)?;
|
||||
opt_result!(file_out.write_all(&util::TINY_ENC_MAGIC_TAG.to_be_bytes()), "Write tag failed: {}");
|
||||
let encrypted_meta_bytes = opt_result!(serde_json::to_vec(&encrypt_meta), "Generate meta json bytes failed: {}");
|
||||
let encrypted_meta_bytes_len = encrypted_meta_bytes.len() as u32;
|
||||
opt_result!(file_out.write_all(&encrypted_meta_bytes_len.to_be_bytes()), "Write meta len failed: {}");
|
||||
opt_result!(file_out.write_all(&encrypted_meta_bytes), "Write meta failed: {}");
|
||||
|
||||
let start = Instant::now();
|
||||
encrypt_file(&mut file_in, &mut file_out, &key, &nonce, cmd_encrypt.compress)?;
|
||||
let encrypt_duration = start.elapsed();
|
||||
debugging!("Encrypt file: {} elapsed: {} ms", path_display, encrypt_duration.as_millis());
|
||||
|
||||
zeroize(key);
|
||||
zeroize(nonce);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn encrypt_file(file_in: &mut File, file_out: &mut File, key: &[u8], nonce: &[u8], compress: bool) -> XResult<usize> {
|
||||
let mut total_len = 0;
|
||||
let mut buffer = [0u8; 1024 * 8];
|
||||
let key = opt_result!(key.try_into(), "Key is not 32 bytes: {}");
|
||||
let mut gz_encoder = GzStreamEncoder::new_default();
|
||||
let mut encryptor = aes_gcm_stream::Aes256GcmStreamEncryptor::new(key, &nonce);
|
||||
loop {
|
||||
let len = opt_result!(file_in.read(&mut buffer), "Read file failed: {}");
|
||||
if len == 0 {
|
||||
let last_block = if compress {
|
||||
let last_compressed_buffer = opt_result!(gz_encoder.finalize(), "Decompress file failed: {}");
|
||||
let mut encrypted_block = encryptor.update(&last_compressed_buffer);
|
||||
let (last_block, tag) = encryptor.finalize();
|
||||
encrypted_block.extend_from_slice(&last_block);
|
||||
encrypted_block.extend_from_slice(&tag);
|
||||
encrypted_block
|
||||
} else {
|
||||
let (mut last_block, tag) = encryptor.finalize();
|
||||
last_block.extend_from_slice(&tag);
|
||||
last_block
|
||||
};
|
||||
opt_result!(file_out.write_all(&last_block), "Write file failed: {}");
|
||||
success!("Decrypt finished, total bytes: {}", total_len);
|
||||
break;
|
||||
} else {
|
||||
total_len += len;
|
||||
let encrypted = if compress {
|
||||
let compressed = opt_result!(gz_encoder.update(&buffer[0..len]), "Decompress file failed: {}");
|
||||
encryptor.update(&compressed)
|
||||
} else {
|
||||
encryptor.update(&buffer[0..len])
|
||||
};
|
||||
opt_result!(file_out.write_all(&encrypted), "Write file failed: {}");
|
||||
}
|
||||
}
|
||||
Ok(total_len)
|
||||
}
|
||||
|
||||
fn encrypt_envelops(key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResult<Vec<TinyEncryptEnvelop>> {
|
||||
let mut encrypted_envelops = vec![];
|
||||
for envelop in envelops {
|
||||
@@ -89,7 +166,7 @@ fn encrypt_envelop_ecdh(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResu
|
||||
header: WrapKeyHeader {
|
||||
kid: Some(envelop.kid.clone()),
|
||||
enc: ENC_AES256_GCM_P256.to_string(),
|
||||
e_pub_key: encode_base64(&ephemeral_spki),
|
||||
e_pub_key: encode_base64_url_no_pad(&ephemeral_spki),
|
||||
},
|
||||
nonce,
|
||||
encrypted_data: encrypted_key,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
use rust_util::{debugging, opt_result, simple_error, XResult};
|
||||
use rust_util::util_file::resolve_file_path;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::spec::TinyEncryptEnvelopType;
|
||||
@@ -15,7 +16,7 @@ use crate::spec::TinyEncryptEnvelopType;
|
||||
/// "type": "pgp",
|
||||
/// "kid": "KID-1",
|
||||
/// "desc": "this is key 001",
|
||||
/// "public_key": "----- BEGIN OPENPGP ..."
|
||||
/// "publicPart": "----- BEGIN OPENPGP ..."
|
||||
/// },
|
||||
/// {
|
||||
/// "type": "ecdh",
|
||||
@@ -71,10 +72,17 @@ impl TinyEncryptConfig {
|
||||
});
|
||||
}
|
||||
}
|
||||
let envelops: Vec<_> = matched_envelops_map.values().map(|envelop| *envelop).collect();
|
||||
let mut envelops: Vec<_> = matched_envelops_map.values().map(|envelop| *envelop).collect();
|
||||
if envelops.is_empty() {
|
||||
return simple_error!("Profile: {} has no valid envelopes found", profile);
|
||||
}
|
||||
envelops.sort_by(|e1, e2| {
|
||||
if e1.r#type < e2.r#type { return Ordering::Less; }
|
||||
if e1.r#type > e2.r#type { return Ordering::Greater; }
|
||||
if e1.kid < e2.kid { return Ordering::Less; }
|
||||
if e1.kid > e2.kid { return Ordering::Greater; }
|
||||
Ordering::Equal
|
||||
});
|
||||
debugging!("Found envelopes: {:#?}", envelops);
|
||||
Ok(envelops)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::io::{Read, Write};
|
||||
use rust_util::{opt_result, simple_error, XResult};
|
||||
|
||||
use crate::spec::TinyEncryptMeta;
|
||||
use crate::util;
|
||||
|
||||
pub fn _write_tiny_encrypt_meta<W: Write>(w: &mut W, meta: &TinyEncryptMeta) -> XResult<usize> {
|
||||
let meta_json = opt_result!( serde_json::to_string(meta), "Meta to JSON failed: {}");
|
||||
@@ -26,7 +27,7 @@ pub fn read_tiny_encrypt_meta<R: Read>(r: &mut R) -> XResult<TinyEncryptMeta> {
|
||||
let mut tag_buff = [0_u8; 2];
|
||||
opt_result!(r.read_exact(&mut tag_buff), "Read tag failed: {}");
|
||||
let tag = u16::from_be_bytes(tag_buff);
|
||||
if tag != 0x01 {
|
||||
if tag != util::TINY_ENC_MAGIC_TAG {
|
||||
return simple_error!("Tag is not 0x01, but is: 0x{:x}", tag);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extern crate core;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use rust_util::XResult;
|
||||
use rust_util::{debugging, XResult};
|
||||
|
||||
use crate::cmd_decrypt::CmdDecrypt;
|
||||
use crate::cmd_encrypt::CmdEncrypt;
|
||||
@@ -46,12 +46,15 @@ fn main() -> XResult<()> {
|
||||
let args = Cli::parse();
|
||||
match args.command {
|
||||
Commands::Encrypt(cmd_encrypt) => {
|
||||
debugging!("Encrypt: {:?}", cmd_encrypt);
|
||||
cmd_encrypt::encrypt(cmd_encrypt)
|
||||
}
|
||||
Commands::Decrypt(cmd_decrypt) => {
|
||||
debugging!("Decrypt: {:?}", cmd_decrypt);
|
||||
cmd_decrypt::decrypt(cmd_decrypt)
|
||||
}
|
||||
Commands::Info(cmd_info) => {
|
||||
debugging!("Info: {:?}", cmd_info);
|
||||
cmd_info::info(cmd_info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ pub struct TinyEncryptEnvelop {
|
||||
}
|
||||
|
||||
/// NOTICE: Kms and Age is not being supported in the future
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||
pub enum TinyEncryptEnvelopType {
|
||||
#[serde(rename = "pgp")]
|
||||
Pgp,
|
||||
@@ -96,7 +96,7 @@ impl TinyEncryptMeta {
|
||||
ecdh_point: None,
|
||||
envelop: None,
|
||||
envelops: Some(envelops),
|
||||
encryption_algorithm: None,
|
||||
encryption_algorithm: None, // use none default
|
||||
nonce: encode_base64(nonce),
|
||||
file_length: metadata.len(),
|
||||
file_last_modified: match metadata.modified() {
|
||||
|
||||
12
src/util.rs
12
src/util.rs
@@ -12,6 +12,8 @@ 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 const TINY_ENC_MAGIC_TAG: u16 = 0x01;
|
||||
|
||||
pub fn require_tiny_enc_file_and_exists(path: impl AsRef<Path>) -> XResult<()> {
|
||||
let path = path.as_ref();
|
||||
let path_display = format!("{}", path.display());
|
||||
@@ -22,6 +24,16 @@ pub fn require_tiny_enc_file_and_exists(path: impl AsRef<Path>) -> XResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_none_tiny_enc_file_and_exists(path: impl AsRef<Path>) -> XResult<()> {
|
||||
let path = path.as_ref();
|
||||
let path_display = format!("{}", path.display());
|
||||
if path_display.ends_with(TINY_ENC_FILE_EXT) {
|
||||
return simple_error!("File is already tiny encrypt file: {}", &path_display);
|
||||
}
|
||||
require_file_exists(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_file_exists(path: impl AsRef<Path>) -> XResult<()> {
|
||||
let path = path.as_ref();
|
||||
match fs::metadata(path) {
|
||||
|
||||
@@ -4,22 +4,23 @@ use rust_util::{opt_result, XResult};
|
||||
|
||||
use p256::pkcs8::EncodePublicKey;
|
||||
use p256::{EncodedPoint, PublicKey};
|
||||
use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
||||
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
||||
// use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EphemeralKeyBytes(EncodedPoint);
|
||||
|
||||
impl EphemeralKeyBytes {
|
||||
pub fn from_public_key(epk: &PublicKey) -> Self {
|
||||
EphemeralKeyBytes(epk.to_encoded_point(true))
|
||||
}
|
||||
|
||||
pub fn decompress(&self) -> EncodedPoint {
|
||||
// EphemeralKeyBytes is a valid compressed encoding by construction.
|
||||
let p = PublicKey::from_encoded_point(&self.0).unwrap();
|
||||
p.to_encoded_point(false)
|
||||
}
|
||||
}
|
||||
// #[derive(Debug)]
|
||||
// pub struct EphemeralKeyBytes(EncodedPoint);
|
||||
//
|
||||
// impl EphemeralKeyBytes {
|
||||
// pub fn from_public_key(epk: &PublicKey) -> Self {
|
||||
// EphemeralKeyBytes(epk.to_encoded_point(true))
|
||||
// }
|
||||
//
|
||||
// pub fn decompress(&self) -> EncodedPoint {
|
||||
// // EphemeralKeyBytes is a valid compressed encoding by construction.
|
||||
// let p = PublicKey::from_encoded_point(&self.0).unwrap();
|
||||
// p.to_encoded_point(false)
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn compute_shared_secret(public_key_point_hex: &str) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||
let public_key_point_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse public key point hex failed: {}");
|
||||
|
||||
Reference in New Issue
Block a user