Skip to main content

reddb_server/storage/encryption/
header.rs

1//! Encrypted Database Header
2//!
3//! Stores encryption parameters and key verification data.
4//! This header is stored in the clear (in Page 0, potentially) or a separate header file?
5//! The `Pager` uses Page 0 for `DatabaseHeader`.
6//! Usually, encryption parameters are part of the `DatabaseHeader` or stored alongside it.
7//!
8//! Since `DatabaseHeader` in `pager.rs` is fixed structure (u32 fields), we might need to extend it
9//! or use a separate page (e.g. Page 1?) or just reserved bytes in Page 0?
10//! `HEADER_SIZE` in `page.rs` is 32 bytes.
11//! A standard 4KB page has plenty of room.
12//!
13//! We will implement serialization for this header so it can be embedded in Page 0 after the main header.
14
15use super::key::SecureKey;
16use super::page_encryptor::PageEncryptor;
17use crate::crypto::uuid::Uuid;
18
19pub use reddb_file::{
20    PAGED_ENCRYPTION_KEY_CHECK_PLAINTEXT_SIZE as KEY_CHECK_LEN,
21    PAGED_ENCRYPTION_SALT_SIZE as SALT_SIZE,
22};
23
24/// Header containing encryption parameters
25#[derive(Debug, Clone)]
26pub struct EncryptionHeader {
27    /// Salt used for Key Derivation (32 bytes)
28    pub salt: [u8; SALT_SIZE],
29
30    /// Key verification data
31    /// Layout: [Nonce (12)] [Ciphertext (32)] [Tag (16)]
32    /// Total: 12 + 32 + 16 = 60 bytes
33    pub key_check: Vec<u8>,
34}
35
36impl EncryptionHeader {
37    /// Create a new encryption header
38    pub fn new(key: &SecureKey) -> Self {
39        // Generate random salt
40        let uuid = Uuid::new_v4();
41        let mut salt = [0u8; SALT_SIZE];
42        // Fill salt with random data (using uuid chunks for now as we don't have rand)
43        let b = uuid.as_bytes();
44        salt[0..16].copy_from_slice(b);
45        let uuid2 = Uuid::new_v4();
46        salt[16..32].copy_from_slice(uuid2.as_bytes());
47
48        // Create key check
49        // Encrypt a known value (e.g., 32 bytes of 0xAA)
50        let known_value = [0xAAu8; KEY_CHECK_LEN];
51        let encryptor = PageEncryptor::new(key.clone());
52
53        // Use a dummy page ID for key check (e.g., u32::MAX)
54        let check_blob = encryptor.encrypt(u32::MAX, &known_value);
55
56        Self {
57            salt,
58            key_check: check_blob,
59        }
60    }
61
62    /// Validate the key against this header
63    pub fn validate(&self, key: &SecureKey) -> bool {
64        let encryptor = PageEncryptor::new(key.clone());
65
66        match encryptor.decrypt(u32::MAX, &self.key_check) {
67            Ok(plaintext) => {
68                let expected = [0xAAu8; KEY_CHECK_LEN];
69                plaintext == expected
70            }
71            Err(_) => false,
72        }
73    }
74
75    /// Serialize to bytes
76    pub fn to_bytes(&self) -> Vec<u8> {
77        reddb_file::encode_paged_encryption_header(&reddb_file::PagedEncryptionHeader {
78            salt: self.salt,
79            key_check: self.key_check.clone(),
80        })
81    }
82
83    /// Deserialize from bytes
84    pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
85        let raw = reddb_file::decode_paged_encryption_header(data)
86            .map_err(|err| format!("Data too short for EncryptionHeader: {err}"))?;
87        Ok(Self {
88            salt: raw.salt,
89            key_check: raw.key_check,
90        })
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_header_validation() {
100        let key = SecureKey::new(&[0x11u8; 32]);
101        let header = EncryptionHeader::new(&key);
102
103        assert!(header.validate(&key));
104
105        let wrong_key = SecureKey::new(&[0x22u8; 32]);
106        assert!(!header.validate(&wrong_key));
107    }
108
109    #[test]
110    fn test_header_serialization() {
111        let key = SecureKey::new(&[0x33u8; 32]);
112        let header = EncryptionHeader::new(&key);
113
114        let bytes = header.to_bytes();
115        let loaded = EncryptionHeader::from_bytes(&bytes).unwrap();
116
117        assert_eq!(header.salt, loaded.salt);
118        assert_eq!(header.key_check, loaded.key_check);
119        assert!(loaded.validate(&key));
120    }
121}