feat: v0.3.2, store file create/modified time and restore

This commit is contained in:
2023-10-15 11:06:55 +08:00
parent 161b901829
commit 2624b0fa64
8 changed files with 140 additions and 65 deletions

View File

@@ -2,15 +2,17 @@ use std::{fs, io};
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::time::Instant;
use std::time::{Instant, SystemTime};
use clap::Args;
use fs_set_times::SystemTimeSpec;
use openpgp_card::{OpenPgp, OpenPgpTransaction};
use openpgp_card::crypto_data::Cryptogram;
use rust_util::{
debugging, failure, iff, information, opt_result, simple_error, success,
util_msg, util_term, warning, XResult,
};
use rust_util::util_time::UnixEpochTime;
use x509_parser::prelude::FromDer;
use x509_parser::x509::SubjectPublicKeyInfo;
use yubikey::piv::{AlgorithmId, decrypt_data};
@@ -20,9 +22,9 @@ use zeroize::Zeroize;
use crate::{card, file, util, util_piv};
use crate::compress::GzStreamDecoder;
use crate::config::TinyEncryptConfig;
use crate::consts::{DATE_TIME_FORMAT, ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT};
use crate::crypto_aes::{aes_gcm_decrypt, try_aes_gcm_decrypt_with_salt};
use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
use crate::consts::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT};
use crate::wrap_key::WrapKey;
#[derive(Debug, Args)]
@@ -102,46 +104,28 @@ pub fn decrypt_single(config: &Option<TinyEncryptConfig>,
let key = try_decrypt_key(config, selected_envelop, pin, slot)?;
let nonce = opt_result!(util::decode_base64(&meta.nonce), "Decode nonce failed: {}");
debugging!("Decrypt key: {}", hex::encode(&key));
// debugging!("Decrypt key: {}", hex::encode(&key));
debugging!("Decrypt nonce: {}", hex::encode(&nonce));
if let Some(enc_encrypted_meta) = &meta.encrypted_meta {
let enc_encrypted_meta_bytes = opt_result!(
util::decode_base64(enc_encrypted_meta), "Decode enc-encrypted-meta failed: {}");
let enc_meta = opt_result!(
EncEncryptedMeta::unseal(&key, &nonce, &enc_encrypted_meta_bytes), "Unseal enc-encrypted-meta failed: {}");
if let Some(filename) = &enc_meta.filename {
information!("Source filename: {}", filename);
}
}
if let Some(encrypted_comment) = &meta.encrypted_comment {
match util::decode_base64(encrypted_comment) {
Err(e) => warning!("Decode encrypted comment failed: {}", e),
Ok(ec_bytes) => match try_aes_gcm_decrypt_with_salt(&key, &nonce, SALT_COMMENT, &ec_bytes) {
Err(e) => warning!("Decrypt encrypted comment failed: {}", e),
Ok(decrypted_comment_bytes) => match String::from_utf8(decrypted_comment_bytes.clone()) {
Err(_) => success!("Encrypted message hex: {}", hex::encode(&decrypted_comment_bytes)),
Ok(message) => success!("Encrypted comment: {}", message),
}
}
}
}
let enc_meta = parse_encrypted_meta(&meta, &key, &nonce)?;
parse_encrypted_comment(&meta, &key, &nonce)?;
if cmd_decrypt.skip_decrypt_file {
information!("Decrypt file is skipped.");
} else {
let mut file_out = File::create(path_out)?;
let start = Instant::now();
util_msg::print_lastline(
&format!("Decrypting file: {}{} ...", path_display, iff!(meta.compress, " [compressed]", ""))
);
let mut file_out = File::create(path_out)?;
let _ = decrypt_file(&mut file_in, &mut file_out, &key, &nonce, meta.compress)?;
util_msg::clear_lastline();
let encrypt_duration = start.elapsed();
debugging!("Encrypt file: {} elapsed: {} ms", path_display, encrypt_duration.as_millis());
drop(file_out);
util_msg::clear_lastline();
update_out_file_time(enc_meta, path_out);
let encrypt_duration = start.elapsed();
debugging!("Inner decrypt file: {} elapsed: {} ms", path_display, encrypt_duration.as_millis());
}
util::zeroize(key);
@@ -193,6 +177,63 @@ fn decrypt_file(file_in: &mut File, file_out: &mut File, key: &[u8], nonce: &[u8
Ok(total_len)
}
fn update_out_file_time(enc_meta: Option<EncEncryptedMeta>, path_out: &str) {
if let Some(enc_meta) = &enc_meta {
let create_time = enc_meta.c_time.map(|t| SystemTime::from_millis(t));
let modify_time = enc_meta.m_time.map(|t| SystemTime::from_millis(t));
if create_time.is_some() || modify_time.is_some() {
let set_times_result = fs_set_times::set_times(
path_out,
create_time.map(|t| SystemTimeSpec::Absolute(t)),
modify_time.map(|t| SystemTimeSpec::Absolute(t)),
);
match set_times_result {
Ok(_) => information!("Set file time succeed."),
Err(e) => warning!("Set file time failed: {}", e),
}
}
}
}
fn parse_encrypted_comment(meta: &TinyEncryptMeta, key: &[u8], nonce: &[u8]) -> XResult<()> {
if let Some(encrypted_comment) = &meta.encrypted_comment {
match util::decode_base64(encrypted_comment) {
Err(e) => warning!("Decode encrypted comment failed: {}", e),
Ok(ec_bytes) => match try_aes_gcm_decrypt_with_salt(&key, &nonce, SALT_COMMENT, &ec_bytes) {
Err(e) => warning!("Decrypt encrypted comment failed: {}", e),
Ok(decrypted_comment_bytes) => match String::from_utf8(decrypted_comment_bytes.clone()) {
Err(_) => success!("Encrypted message hex: {}", hex::encode(&decrypted_comment_bytes)),
Ok(message) => success!("Encrypted comment: {}", message),
}
}
}
}
Ok(())
}
fn parse_encrypted_meta(meta: &TinyEncryptMeta, key: &[u8], nonce: &[u8]) -> XResult<Option<EncEncryptedMeta>> {
Ok(match &meta.encrypted_meta {
None => None,
Some(enc_encrypted_meta) => {
let enc_encrypted_meta_bytes = opt_result!(
util::decode_base64(enc_encrypted_meta), "Decode enc-encrypted-meta failed: {}");
let enc_meta = opt_result!(
EncEncryptedMeta::unseal(&key, &nonce, &enc_encrypted_meta_bytes), "Unseal enc-encrypted-meta failed: {}");
if let Some(filename) = &enc_meta.filename {
information!("Source filename: {}", filename);
}
let fmt = simpledateformat::fmt(DATE_TIME_FORMAT).unwrap();
if let Some(c_time) = &enc_meta.c_time {
information!("Source file create time: {}", fmt.format_local(SystemTime::from_millis(*c_time)));
}
if let Some(m_time) = &enc_meta.c_time {
information!("Source file modified time: {}", fmt.format_local(SystemTime::from_millis(*m_time)));
}
Some(enc_meta)
}
})
}
fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop,
pin: &Option<String>,

View File

@@ -8,15 +8,16 @@ use clap::Args;
use flate2::Compression;
use rsa::Pkcs1v15Encrypt;
use rust_util::{debugging, failure, information, opt_result, simple_error, success, util_msg, warning, XResult};
use rust_util::util_time::UnixEpochTime;
use zeroize::Zeroize;
use crate::{file, util, util_ecdh, util_p384, util_x25519};
use crate::compress::GzStreamEncoder;
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
use crate::consts::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT};
use crate::crypto_aes::{aes_gcm_encrypt, aes_gcm_encrypt_with_salt};
use crate::crypto_rsa::parse_spki;
use crate::spec::{EncEncryptedMeta, EncMetadata, TINY_ENCRYPT_VERSION_10, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
use crate::consts::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, SALT_COMMENT, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT};
use crate::wrap_key::{WrapKey, WrapKeyHeader};
#[derive(Debug, Args)]
@@ -121,12 +122,14 @@ fn encrypt_single(path: &PathBuf, envelops: &[&TinyEncryptConfigEnvelop], cmd_en
&aes_gcm_encrypt_with_salt(&key, &nonce, SALT_COMMENT, encrypted_comment.as_bytes())?))
};
let file_metadata = opt_result!(fs::metadata(path), "Read file: {} meta failed: {}", path.display());
let enc_encrypted_meta = EncEncryptedMeta {
filename: Some(util::get_file_name(path)),
c_time: file_metadata.created().ok().map(|t| t.to_millis()).flatten(),
m_time: file_metadata.modified().ok().map(|t| t.to_millis()).flatten(),
};
let enc_encrypted_meta_bytes = opt_result!(enc_encrypted_meta.seal(&key, &nonce), "Seal enc-encrypted-meta failed: {}");
let file_metadata = opt_result!(fs::metadata(path), "Read file: {} meta failed: {}", path.display());
let enc_metadata = EncMetadata {
comment: cmd_encrypt.comment.clone(),
encrypted_comment,

View File

@@ -1,15 +1,15 @@
use std::cmp::max;
use std::fs::File;
use std::ops::Add;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use clap::Args;
use rust_util::{iff, opt_result, simple_error, success, util_time, warning, XResult};
use rust_util::{util_time, iff, opt_result, simple_error, success, warning, XResult};
use rust_util::util_time::UnixEpochTime;
use simpledateformat::format_human2;
use crate::consts::{DATE_TIME_FORMAT, TINY_ENC_AES_GCM, TINY_ENC_FILE_EXT};
use crate::file;
use crate::consts::{TINY_ENC_AES_GCM, TINY_ENC_FILE_EXT};
#[derive(Debug, Args)]
pub struct CmdInfo {
@@ -57,15 +57,15 @@ pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo) -> XResult<()> {
);
let now_millis = util_time::get_current_millis() as u64;
let fmt = simpledateformat::fmt("EEE MMM dd HH:mm:ss z yyyy").unwrap();
let fmt = simpledateformat::fmt(DATE_TIME_FORMAT).unwrap();
infos.push(format!("{}: {}, {} ago",
header("Last modified"),
fmt.format_local(from_unix_epoch(meta.file_last_modified)),
fmt.format_local(SystemTime::from_millis(meta.file_last_modified)),
format_human2(Duration::from_millis(now_millis - meta.file_last_modified))
));
infos.push(format!("{}: {}, {} ago",
header("Created"),
fmt.format_local(from_unix_epoch(meta.created)),
fmt.format_local(SystemTime::from_millis(meta.created)),
format_human2(Duration::from_millis(now_millis - meta.created))
));
@@ -101,10 +101,6 @@ pub fn info_single(path: &PathBuf, cmd_info: &CmdInfo) -> XResult<()> {
Ok(())
}
fn from_unix_epoch(t: u64) -> SystemTime {
SystemTime::UNIX_EPOCH.add(Duration::from_millis(t))
}
fn header(h: &str) -> String {
let width = 21;
h.to_string() + ".".repeat(max(width - h.len(), 0)).as_str()

View File

@@ -16,3 +16,5 @@ pub const TINY_ENC_COMPRESSED_MAGIC_TAG: u16 = 0x02;
// Encryption nonce salt
pub const SALT_COMMENT: &[u8] = b"salt:comment";
pub const SALT_META: &[u8] = b"salt:meta";
pub const DATE_TIME_FORMAT: &str = "EEE MMM dd HH:mm:ss z yyyy";

View File

@@ -97,6 +97,10 @@ impl TinyEncryptEnvelopType {
pub struct EncEncryptedMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub c_time: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub m_time: Option<u64>,
}
impl EncEncryptedMeta {

View File

@@ -14,6 +14,7 @@ pub struct WrapKey {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WrapKeyHeader {
#[serde(skip_serializing_if = "Option::is_none")]
pub kid: Option<String>,
pub enc: String,
pub e_pub_key: String,