use std::fs::File;
use std::io::{BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
use crate::container::{self, KeyDerivation, PayloadType};
use crate::error::{Error, Result};
pub fn encrypt_file(password: &str, input: &Path, output: Option<&Path>) -> Result<PathBuf> {
let input_path = input
.canonicalize()
.map_err(|_| Error::invalid_name(input.display()))?;
if !input_path.is_file() {
return Err(Error::invalid_name(input.display()));
}
let original_name = input_path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".into());
let mut argon_salt = [0u8; container::SALT_LEN];
let mut object_salt = [0u8; container::SALT_LEN];
crate::crypto::fill_random(&mut argon_salt);
crate::crypto::fill_random(&mut object_salt);
let kd = KeyDerivation::Password {
argon_salt,
object_salt,
params: crate::kdf::KdfParams::new(),
};
let output_path = match output {
Some(p) => p.to_path_buf(),
None => {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(argon_salt);
hasher.update(object_salt);
PathBuf::from(format!("{:x}.krf", hasher.finalize()))
}
};
let tmp_path = crate::fsutil::sibling_temp_path(&output_path);
let result = (|| -> Result<()> {
let mut out_file = File::create(&tmp_path)?;
crate::fsutil::restrict_perms(&tmp_path);
let mut src = File::open(&input_path)?;
{
let mut w = BufWriter::new(&mut out_file);
container::write_container(
&mut w,
&kd,
Some(password),
None,
PayloadType::File,
b"",
Some(&mut src),
Some(&original_name),
)?;
w.flush()?;
}
out_file.sync_all()?;
drop(out_file);
#[cfg(windows)]
if output_path.exists() {
std::fs::remove_file(&output_path)?;
}
std::fs::rename(&tmp_path, &output_path)?;
crate::fsutil::restrict_perms(&output_path);
crate::fsutil::sync_dir(output_path.parent().unwrap_or_else(|| Path::new(".")));
Ok(())
})();
match result {
Ok(()) => Ok(output_path),
Err(e) => {
let _ = std::fs::remove_file(&tmp_path);
Err(e)
}
}
}
pub fn decrypt_file(password: &str, input: &Path, output: &Path) -> Result<String> {
let file = BufReader::new(File::open(input)?);
let tmp_path = crate::fsutil::sibling_temp_path(output);
let outcome = (|| -> Result<String> {
let tmp = File::create(&tmp_path)?;
crate::fsutil::restrict_perms(&tmp_path);
let mut sink = BufWriter::new(tmp);
let trailer = container::read_container(
file,
Some(password),
None,
PayloadType::File,
b"",
|chunk| sink.write_all(chunk).map_err(Error::from),
)?;
sink.flush()?;
drop(sink);
let name = trailer.name.ok_or(Error::MalformedPayload)?;
#[cfg(windows)]
if output.exists() {
std::fs::remove_file(output)?;
}
std::fs::rename(&tmp_path, output)?;
crate::fsutil::sync_dir(output.parent().unwrap_or_else(|| Path::new(".")));
Ok(name)
})();
match outcome {
Ok(name) => Ok(name),
Err(e) => {
let _ = std::fs::remove_file(&tmp_path);
Err(e)
}
}
}