Skip to main content

sal_vault/symmetric/
implementation.rs

1//! Implementation of symmetric encryption functionality.
2
3use chacha20poly1305::aead::Aead;
4use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
5use rand::{rngs::OsRng, RngCore};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use crate::error::CryptoError;
10use crate::keyspace::KeySpace;
11
12/// The size of the nonce in bytes.
13const NONCE_SIZE: usize = 12;
14
15/// Generates a random 32-byte symmetric key.
16///
17/// # Returns
18///
19/// A 32-byte array containing the random key.
20pub fn generate_symmetric_key() -> [u8; 32] {
21    let mut key = [0u8; 32];
22    OsRng.fill_bytes(&mut key);
23    key
24}
25
26/// Derives a 32-byte key from a password.
27///
28/// # Arguments
29///
30/// * `password` - The password to derive the key from.
31///
32/// # Returns
33///
34/// A 32-byte array containing the derived key.
35pub fn derive_key_from_password(password: &str) -> [u8; 32] {
36    let mut hasher = Sha256::default();
37    hasher.update(password.as_bytes());
38    let result = hasher.finalize();
39
40    let mut key = [0u8; 32];
41    key.copy_from_slice(&result);
42    key
43}
44
45/// Encrypts data using ChaCha20Poly1305 with an internally generated nonce.
46///
47/// The nonce is appended to the ciphertext so it can be extracted during decryption.
48///
49/// # Arguments
50///
51/// * `key` - The encryption key (should be 32 bytes).
52/// * `message` - The message to encrypt.
53///
54/// # Returns
55///
56/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
57/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
58/// * `Err(CryptoError::EncryptionFailed)` if encryption fails.
59pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
60    // Create cipher
61    let cipher =
62        ChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::InvalidKeyLength)?;
63
64    // Generate random nonce
65    let mut nonce_bytes = [0u8; NONCE_SIZE];
66    OsRng.fill_bytes(&mut nonce_bytes);
67    let nonce = Nonce::from_slice(&nonce_bytes);
68
69    // Encrypt message
70    let ciphertext = cipher
71        .encrypt(nonce, message)
72        .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
73
74    // Append nonce to ciphertext
75    let mut result = ciphertext;
76    result.extend_from_slice(&nonce_bytes);
77
78    Ok(result)
79}
80
81/// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext.
82///
83/// # Arguments
84///
85/// * `key` - The decryption key (should be 32 bytes).
86/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended.
87///
88/// # Returns
89///
90/// * `Ok(Vec<u8>)` containing the decrypted message.
91/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
92/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short.
93pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> {
94    // Check if ciphertext is long enough to contain a nonce
95    if ciphertext_with_nonce.len() <= NONCE_SIZE {
96        return Err(CryptoError::DecryptionFailed(
97            "Ciphertext too short".to_string(),
98        ));
99    }
100
101    // Extract nonce from the end of ciphertext
102    let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE;
103    let ciphertext = &ciphertext_with_nonce[0..ciphertext_len];
104    let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..];
105
106    // Create cipher
107    let cipher =
108        ChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::InvalidKeyLength)?;
109
110    let nonce = Nonce::from_slice(nonce_bytes);
111
112    // Decrypt message
113    cipher
114        .decrypt(nonce, ciphertext)
115        .map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
116}
117
118/// Encrypts data using a key directly (for internal use).
119///
120/// # Arguments
121///
122/// * `key` - The encryption key.
123/// * `message` - The message to encrypt.
124///
125/// # Returns
126///
127/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
128/// * `Err(CryptoError)` if encryption fails.
129pub fn encrypt_with_key(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
130    encrypt_symmetric(key, message)
131}
132
133/// Decrypts data using a key directly (for internal use).
134///
135/// # Arguments
136///
137/// * `key` - The decryption key.
138/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended.
139///
140/// # Returns
141///
142/// * `Ok(Vec<u8>)` containing the decrypted message.
143/// * `Err(CryptoError)` if decryption fails.
144pub fn decrypt_with_key(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> {
145    decrypt_symmetric(key, ciphertext_with_nonce)
146}
147
148/// Metadata for an encrypted key space.
149#[derive(Serialize, Deserialize, Debug)]
150pub struct EncryptedKeySpaceMetadata {
151    pub name: String,
152    pub created_at: u64,
153    pub last_accessed: u64,
154}
155
156/// An encrypted key space with metadata.
157#[derive(Serialize, Deserialize, Debug)]
158pub struct EncryptedKeySpace {
159    pub metadata: EncryptedKeySpaceMetadata,
160    pub encrypted_data: Vec<u8>,
161}
162
163/// Encrypts a key space using a password.
164///
165/// # Arguments
166///
167/// * `space` - The key space to encrypt.
168/// * `password` - The password to encrypt with.
169///
170/// # Returns
171///
172/// * `Ok(EncryptedKeySpace)` containing the encrypted key space.
173/// * `Err(CryptoError)` if encryption fails.
174pub fn encrypt_key_space(
175    space: &KeySpace,
176    password: &str,
177) -> Result<EncryptedKeySpace, CryptoError> {
178    // Serialize the key space
179    let serialized = match serde_json::to_vec(space) {
180        Ok(data) => data,
181        Err(e) => {
182            log::error!("Serialization error during encryption: {}", e);
183            return Err(CryptoError::SerializationError(e.to_string()));
184        }
185    };
186
187    // Derive key from password
188    let key = derive_key_from_password(password);
189
190    // Encrypt the serialized data
191    let encrypted_data = encrypt_symmetric(&key, &serialized)?;
192
193    // Create metadata
194    let now = std::time::SystemTime::now()
195        .duration_since(std::time::UNIX_EPOCH)
196        .unwrap_or_default()
197        .as_millis() as u64;
198    let metadata = EncryptedKeySpaceMetadata {
199        name: space.name.clone(),
200        created_at: now,
201        last_accessed: now,
202    };
203
204    Ok(EncryptedKeySpace {
205        metadata,
206        encrypted_data,
207    })
208}
209
210/// Decrypts a key space using a password.
211///
212/// # Arguments
213///
214/// * `encrypted_space` - The encrypted key space.
215/// * `password` - The password to decrypt with.
216///
217/// # Returns
218///
219/// * `Ok(KeySpace)` containing the decrypted key space.
220/// * `Err(CryptoError)` if decryption fails.
221pub fn decrypt_key_space(
222    encrypted_space: &EncryptedKeySpace,
223    password: &str,
224) -> Result<KeySpace, CryptoError> {
225    // Derive key from password
226    let key = derive_key_from_password(password);
227
228    // Decrypt the data
229    let decrypted_data = decrypt_symmetric(&key, &encrypted_space.encrypted_data)?;
230
231    // Deserialize the key space
232    let space: KeySpace = match serde_json::from_slice(&decrypted_data) {
233        Ok(space) => space,
234        Err(e) => {
235            log::error!("Deserialization error: {}", e);
236            return Err(CryptoError::SerializationError(e.to_string()));
237        }
238    };
239
240    Ok(space)
241}
242
243/// Serializes an encrypted key space to a JSON string.
244///
245/// # Arguments
246///
247/// * `encrypted_space` - The encrypted key space to serialize.
248///
249/// # Returns
250///
251/// * `Ok(String)` containing the serialized encrypted key space.
252/// * `Err(CryptoError)` if serialization fails.
253pub fn serialize_encrypted_space(
254    encrypted_space: &EncryptedKeySpace,
255) -> Result<String, CryptoError> {
256    serde_json::to_string(encrypted_space)
257        .map_err(|e| CryptoError::SerializationError(e.to_string()))
258}
259
260/// Deserializes an encrypted key space from a JSON string.
261///
262/// # Arguments
263///
264/// * `serialized` - The serialized encrypted key space.
265///
266/// # Returns
267///
268/// * `Ok(EncryptedKeySpace)` containing the deserialized encrypted key space.
269/// * `Err(CryptoError)` if deserialization fails.
270pub fn deserialize_encrypted_space(serialized: &str) -> Result<EncryptedKeySpace, CryptoError> {
271    match serde_json::from_str(serialized) {
272        Ok(space) => Ok(space),
273        Err(e) => {
274            log::error!("Error deserializing encrypted space: {}", e);
275            Err(CryptoError::SerializationError(e.to_string()))
276        }
277    }
278}