Skip to main content

coven_crypto/
file_key.rs

1//! A file's independent key and indexed XChaCha20-Poly1305 chunks.
2
3use crate::{cipher, randomness, CryptoError, SecretBytes};
4use zeroize::Zeroizing;
5
6/// A random 32-byte key for one immutable file, erased on drop.
7/// Never reuse this key and a chunk index with different plaintext.
8pub struct FileKey(Zeroizing<[u8; 32]>);
9
10impl FileKey {
11    /// Generate a file key independently of store and circle keys.
12    pub fn generate() -> Result<Self, CryptoError> {
13        Ok(Self(randomness::secret_bytes()?))
14    }
15
16    /// Import the key carried in a row's where-column.
17    pub fn from_bytes(bytes: [u8; 32]) -> Self {
18        Self(Zeroizing::new(bytes))
19    }
20
21    /// Export the key for its where-column; the returned bytes erase on drop.
22    pub fn to_secret_bytes(&self) -> SecretBytes {
23        SecretBytes::new(self.0.to_vec())
24    }
25
26    /// Seal a chunk, binding the path, whole cleartext header and index.
27    /// The output is ciphertext and tag; its nonce is not stored.
28    /// Panics if the storage path is empty.
29    pub fn seal_chunk(&self, path: &str, header: &[u8], index: u64, plaintext: &[u8]) -> Vec<u8> {
30        cipher::seal(&self.0, &nonce(index), &aad(path, header, index), plaintext)
31    }
32
33    /// Open only at the authenticated file path, header and chunk index.
34    /// Panics if the storage path is empty.
35    pub fn open_chunk(
36        &self,
37        path: &str,
38        header: &[u8],
39        index: u64,
40        sealed: &[u8],
41    ) -> Result<Vec<u8>, CryptoError> {
42        cipher::open(&self.0, &nonce(index), &aad(path, header, index), sealed)
43    }
44}
45
46impl std::fmt::Debug for FileKey {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.write_str("FileKey([REDACTED])")
49    }
50}
51
52fn nonce(index: u64) -> [u8; 24] {
53    let mut nonce = [0; 24];
54    nonce[16..].copy_from_slice(&index.to_be_bytes());
55    nonce
56}
57
58fn aad(path: &str, header: &[u8], index: u64) -> Vec<u8> {
59    cipher::context(&[
60        b"coven/file-chunk/v1",
61        cipher::storage_path(path),
62        header,
63        &index.to_be_bytes(),
64    ])
65}
66
67#[cfg(test)]
68#[path = "file_key_tests.rs"]
69mod tests;