Skip to main content

shadow_crypt_core/v2/
file.rs

1use crate::{
2    errors::{FileError, HeaderError},
3    file::PlaintextFile,
4    memory::SecureKey,
5    v2::crypt,
6};
7
8use super::header::{AadPurpose, FileHeader, HeaderBinding};
9
10/// Represents a complete encrypted file with header and content
11#[derive(Debug)]
12pub struct EncryptedFile {
13    header: FileHeader,
14    ciphertext: Vec<u8>,
15}
16
17impl EncryptedFile {
18    pub fn new(header: FileHeader, ciphertext: Vec<u8>) -> Self {
19        Self { header, ciphertext }
20    }
21
22    /// Encrypts a plaintext file into a complete v2 encrypted file.
23    ///
24    /// This owns the v2 AEAD choreography: the fixed header fields are bound
25    /// as associated data to both ciphertexts, with distinct domains for
26    /// filename and content, so neither the header nor the pairing of the two
27    /// ciphertexts can be tampered with undetected.
28    ///
29    /// The caller supplies the key (derived from `kdf_params` and `salt`) and
30    /// fresh random salt/nonces, keeping this function deterministic.
31    pub fn seal(
32        plaintext_file: &PlaintextFile,
33        key: &SecureKey,
34        kdf_params: super::key::KeyDerivationParams,
35        salt: [u8; 16],
36        content_nonce: [u8; 24],
37        filename_nonce: [u8; 24],
38    ) -> Result<Self, FileError> {
39        let binding = HeaderBinding::new(&salt, &kdf_params, &content_nonce, &filename_nonce);
40
41        let (filename_ciphertext, _) = crypt::encrypt_bytes(
42            plaintext_file.filename().as_str().as_bytes(),
43            key.as_bytes(),
44            &filename_nonce,
45            &binding.aad(AadPurpose::Filename),
46        )?;
47
48        let (content_ciphertext, _) = crypt::encrypt_bytes(
49            plaintext_file.content().as_slice(),
50            key.as_bytes(),
51            &content_nonce,
52            &binding.aad(AadPurpose::Content),
53        )?;
54
55        let header = FileHeader::new(
56            salt,
57            kdf_params,
58            content_nonce,
59            filename_nonce,
60            filename_ciphertext,
61        )?;
62
63        Ok(Self::new(header, content_ciphertext))
64    }
65
66    /// Parses a serialized v2 file into its header and content ciphertext.
67    pub fn from_bytes(bytes: &[u8]) -> Result<Self, HeaderError> {
68        let header = FileHeader::try_deserialize(bytes)?;
69        let ciphertext = bytes[header.header_length()..].to_vec();
70
71        Ok(Self::new(header, ciphertext))
72    }
73
74    /// Serializes the complete file (header followed by content ciphertext).
75    pub fn to_bytes(&self) -> Vec<u8> {
76        let mut bytes = self.header.serialize();
77        bytes.extend_from_slice(&self.ciphertext);
78        bytes
79    }
80
81    /// Decrypts the filename and content, verifying the header binding under
82    /// each ciphertext's own domain. The inverse of [`EncryptedFile::seal`].
83    pub fn decrypt(&self, key: &SecureKey) -> Result<PlaintextFile, FileError> {
84        let filename = self.header.decrypt_filename(key)?;
85        let content = self.header.decrypt_content(&self.ciphertext, key)?;
86
87        Ok(PlaintextFile::new(filename, content))
88    }
89
90    pub fn header(&self) -> &FileHeader {
91        &self.header
92    }
93    pub fn ciphertext(&self) -> &[u8] {
94        &self.ciphertext
95    }
96}