1use crate::crypto::{EncryptedData, SecretBytes, encrypt, secret_bytes};
2use ring::aead::NONCE_LEN;
3use sha2::{Digest, Sha256};
4use std::fs;
5use std::path::{Path, PathBuf};
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9pub enum BlobError {
10 #[error("failed to read source file: {0}")]
11 ReadSource(#[from] std::io::Error),
12 #[error("failed to encrypt file contents: {0}")]
13 Encrypt(#[from] crate::crypto::CryptoError),
14 #[error("invalid encrypted blob format")]
15 InvalidBlobFormat,
16 #[error("failed to decrypt blob: {0}")]
17 Decrypt(#[source] crate::crypto::CryptoError),
18}
19
20pub fn encrypt_file_to_blob(
21 source_file: &Path,
22 blobs_dir: &Path,
23 key: &SecretBytes,
24) -> Result<String, BlobError> {
25 let content = fs::read(source_file)?;
26 let hash = sha256_hex(&content);
27 let encrypted = encrypt(key, &secret_bytes(content))?;
28
29 fs::create_dir_all(blobs_dir)?;
30 let blob_path = blob_path(blobs_dir, &hash);
31 let payload = encode_blob(&encrypted);
32 fs::write(blob_path, payload)?;
33
34 Ok(hash)
35}
36
37pub fn blob_path(blobs_dir: &Path, hash: &str) -> PathBuf {
38 blobs_dir.join(format!("{hash}.enc"))
39}
40
41pub fn decode_blob(payload: &[u8]) -> Result<EncryptedData, BlobError> {
42 if payload.len() < NONCE_LEN {
43 return Err(BlobError::InvalidBlobFormat);
44 }
45
46 let mut nonce = [0u8; NONCE_LEN];
47 nonce.copy_from_slice(&payload[..NONCE_LEN]);
48
49 Ok(EncryptedData {
50 nonce,
51 ciphertext: payload[NONCE_LEN..].to_vec(),
52 })
53}
54
55pub fn decrypt_blob_bytes(blob_file: &Path, key: &SecretBytes) -> Result<SecretBytes, BlobError> {
56 let payload = fs::read(blob_file)?;
57 let encrypted = decode_blob(&payload)?;
58 crate::crypto::decrypt(key, &encrypted).map_err(BlobError::Decrypt)
59}
60
61pub fn decrypt_blob_by_hash(
62 blobs_dir: &Path,
63 hash: &str,
64 key: &SecretBytes,
65) -> Result<SecretBytes, BlobError> {
66 let path = blob_path(blobs_dir, hash);
67 decrypt_blob_bytes(&path, key)
68}
69
70fn encode_blob(data: &EncryptedData) -> Vec<u8> {
71 let mut out = Vec::with_capacity(data.nonce.len() + data.ciphertext.len());
72 out.extend_from_slice(&data.nonce);
73 out.extend_from_slice(&data.ciphertext);
74 out
75}
76
77fn sha256_hex(data: &[u8]) -> String {
78 let digest = Sha256::digest(data);
79 let mut output = String::with_capacity(digest.len() * 2);
80 const HEX: &[u8; 16] = b"0123456789abcdef";
81
82 for byte in digest {
83 output.push(HEX[(byte >> 4) as usize] as char);
84 output.push(HEX[(byte & 0x0f) as usize] as char);
85 }
86
87 output
88}