use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::path::Path;
use std::ffi::OsString;
use std::fs;
use std::time::SystemTime;
use crate::{log_debug, new_err, Checksum, Key, Nonce, Result};
use crate::core::data::io;
use crate::core::utils;
use super::cipher;
mod header_info {
pub const VERSION: u8 = 1;
pub const MAGIC: [u8; 4] = [b'B', b'O', b'X', VERSION];
}
#[derive(Serialize, Deserialize)]
pub struct Boxfile {
pub header: BoxfileHeader,
body: Box<[u8]>,
checksum: Checksum,
}
impl Boxfile {
pub fn new(file_path: &Path) -> Result<Self> {
log_debug!("Initializing boxfile from {:?}", file_path);
let file_data = io::read_bytes(&file_path)?;
let padding_len: u8 = (file_data.len() as u8 / 8) + 1;
let padding = Self::generate_padding(padding_len);
let body: Box<[u8]> = [file_data, padding].concat().into();
log_debug!("Boxfile body generated");
let header = BoxfileHeader::new(
file_path,
padding_len,
cipher::generate_nonce()
)?;
log_debug!("Boxfile header generated: {:?}", &header);
let mut hasher = Sha256::new();
hasher.update(&header.as_bytes()?);
hasher.update(&body);
let result = hasher.finalize();
let mut checksum = [0u8; 32];
checksum.copy_from_slice(&result);
log_debug!("Checksum generated: {:?}", utils::hex::bytes_to_string(&checksum));
Ok(Self {
header,
body,
checksum
})
}
pub fn parse(file_path: &Path) -> Result<Self> {
log_debug!("Parsing boxfile from {:?}", file_path);
if let Some(extension) = file_path.extension() {
if extension != "box" {
return Err(new_err!(InvalidInput: InvalidFile, "Not encrypted"))
}
} else {
return Err(new_err!(InvalidInput: InvalidFile, "Not encrypted"))
}
let bytes = io::read_bytes(file_path)?;
let boxfile: Boxfile = bincode::deserialize(&bytes)
.map_err(|err| new_err!(SerializeError: BoxfileParseError, err))?;
log_debug!("Boxfile deserialized");
Ok(boxfile)
}
pub fn file_info(&self) -> (&OsString, &Option<OsString>) {
(&self.header.name, &self.header.extension)
}
pub fn verify_checksum(&self) -> Result<bool> {
let mut hasher = Sha256::new();
hasher.update(&self.header.as_bytes()?);
hasher.update(&self.body);
let result = hasher.finalize();
let mut checksum = [0u8; 32];
checksum.copy_from_slice(&result);
log_debug!("Boxfile checksum: {:?}", utils::hex::bytes_to_string(&self.checksum));
log_debug!("Updated checksum: {:?}", utils::hex::bytes_to_string(&checksum));
Ok(checksum == self.checksum)
}
pub fn save_to(&self, path: &Path) -> Result<()> {
log_debug!("Serializing and saving boxfile to {:?}", path);
let bytes = bincode::serialize(&self)
.map_err(|err| new_err!(SerializeError: BoxfileParseError, err))?;
io::write_bytes(path, &bytes, true)?;
Ok(())
}
pub fn encrypt_data(&mut self, key: &Key) -> Result<()> {
log_debug!("Encrypting boxfile");
let encrypted_body = cipher::encrypt(key, &self.header.nonce, &self.body)?;
self.body = encrypted_body.into();
Ok(())
}
pub fn decrypt_data(&mut self, key: &Key) -> Result<()> {
log_debug!("Decrypting boxfile");
let decrypted_body = cipher::decrypt(key, &self.header.nonce, &self.body)?;
self.body = decrypted_body.into();
Ok(())
}
pub fn file_data(&self) -> Result<Box<[u8]>> {
log_debug!("Retrieving file data from boxfile");
let padding_len = self.header.padding_len;
let data_len = self.body.len() as i32 - padding_len as i32;
if data_len < 0 {
return Err(new_err!(SerializeError: BoxfileParseError, "Invalid file data length"))
}
let file_data = &self.body[..data_len as usize];
Ok(file_data.into())
}
fn generate_padding(padding_len: u8) -> Vec<u8> {
vec![0u8; padding_len as usize]
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BoxfileHeader {
magic: [u8; 4],
padding_len: u8,
pub name: OsString,
pub extension: Option<OsString>,
pub create_time: Option<SystemTime>,
pub modify_time: Option<SystemTime>,
pub access_time: Option<SystemTime>,
nonce: Nonce
}
impl BoxfileHeader {
pub fn new(
file_path: &Path,
padding_len: u8,
nonce: Nonce
) -> Result<Self> {
let name = match file_path.file_stem() {
None => OsString::from("unknown"),
Some(name) => OsString::from(name)
};
let extension = file_path.extension().map(|ext| ext.to_os_string());
let metadata = fs::metadata(file_path)?;
Ok(BoxfileHeader {
magic: header_info::MAGIC,
name,
extension,
create_time: metadata.created().ok(),
modify_time: metadata.modified().ok(),
access_time: metadata.accessed().ok(),
padding_len,
nonce
})
}
pub fn as_bytes(&self) -> Result<Vec<u8>> {
log_debug!("Serializing Boxfile header");
let bytes = bincode::serialize(&self)
.map_err(|err| new_err!(SerializeError: HeaderParseError, err))?;
Ok(bytes)
}
}