use crate::Result;
use age::Encryptor;
use futures::io::{AsyncReadExt, BufReader};
use hex;
use secrecy::SecretString;
use sha2::{Digest, Sha256};
use sos_core::{ExternalFileName, Paths, VaultId};
use sos_external_files::EncryptedFile;
use sos_filesystem::write_exclusive;
use sos_vault::secret::SecretId;
use sos_vfs::{self as vfs, File};
use std::path::{Path, PathBuf};
use tokio_util::compat::TokioAsyncReadCompatExt;
pub struct FileStorage;
impl FileStorage {
pub async fn encrypt_file_passphrase<S: AsRef<Path>, T: AsRef<Path>>(
input: S,
target: T,
passphrase: SecretString,
) -> Result<(Vec<u8>, u64)> {
let file = File::open(input.as_ref()).await?;
let encryptor = Encryptor::with_user_passphrase(passphrase);
let mut encrypted = Vec::new();
let mut writer = encryptor.wrap_async_output(&mut encrypted).await?;
futures::io::copy(&mut file.compat(), &mut writer).await?;
writer.finish()?;
let mut hasher = Sha256::new();
hasher.update(&encrypted);
let digest = hasher.finalize();
let file_name = hex::encode(digest);
let dest = PathBuf::from(target.as_ref()).join(file_name);
let size = encrypted.len() as u64;
write_exclusive(dest, encrypted).await?;
Ok((digest.to_vec(), size))
}
pub async fn decrypt_file_passphrase<P: AsRef<Path>>(
input: P,
passphrase: &SecretString,
) -> Result<Vec<u8>> {
let mut file =
BufReader::new(File::open(input.as_ref()).await?.compat());
let decryptor = age::Decryptor::new_async_buffered(&mut file).await?;
let mut decrypted = vec![];
let mut reader = decryptor.decrypt_async(std::iter::once(
&age::scrypt::Identity::new(passphrase.clone()) as _,
))?;
reader.read_to_end(&mut decrypted).await?;
Ok(decrypted)
}
pub async fn encrypt_file_storage<P: AsRef<Path>>(
password: SecretString,
path: P,
paths: &Paths,
vault_id: &VaultId,
secret_id: &SecretId,
) -> Result<EncryptedFile> {
let target = paths.into_file_secret_path(vault_id, secret_id);
if !vfs::try_exists(&target).await? {
vfs::create_dir_all(&target).await?;
}
let (digest, size) =
Self::encrypt_file_passphrase(path, target, password).await?;
Ok(EncryptedFile { digest, size })
}
pub async fn decrypt_file_storage(
password: &SecretString,
paths: &Paths,
vault_id: &VaultId,
secret_id: &SecretId,
file_name: &ExternalFileName,
) -> Result<Vec<u8>> {
let path = paths.into_file_path_parts(vault_id, secret_id, file_name);
Self::decrypt_file_passphrase(path, password).await
}
}