Skip to main content

vyn_core/
blob.rs

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}