cloud_disk_sync/encryption/
mod.rs1pub mod types;
2
3use crate::config::EncryptionConfig;
4use crate::encryption::types::EncryptionAlgorithm;
5use crate::error::EncryptionError;
6use aes_gcm::aead::{Aead, Nonce};
7use aes_gcm::{Aes256Gcm, KeyInit};
8use std::collections::HashMap;
9use std::path::{Path, PathBuf};
10
11pub struct EncryptionManager {
12 key_store: HashMap<String, Vec<u8>>,
13}
14type Hmac = Vec<u8>;
15
16pub struct EncryptionMetadata {
17 pub algorithm: EncryptionAlgorithm,
18 pub key_id: String,
19 pub nonce: Vec<u8>,
20 pub hmac: Hmac,
21}
22
23impl EncryptionManager {
24 pub fn new() -> Self {
25 Self {
26 key_store: HashMap::new(),
27 }
28 }
29
30 fn get_key(&self, key_id: &str) -> Result<Vec<u8>, EncryptionError> {
31 self.key_store
32 .get(key_id)
33 .cloned()
34 .ok_or_else(|| EncryptionError::KeyNotFound(key_id.to_string()))
35 }
36
37 fn create_temp_path(&self) -> Result<PathBuf, EncryptionError> {
38 let name = format!("enc_{}.tmp", uuid::Uuid::new_v4());
39 let path = std::env::temp_dir().join(name);
40 Ok(path)
41 }
42
43 pub async fn encrypt_file(
44 &self,
45 path: &Path,
46 config: &EncryptionConfig,
47 ) -> Result<(Option<PathBuf>, Option<EncryptionMetadata>), EncryptionError> {
48 let key = self.get_key(&config.key_id)?;
49 let cipher = Aes256Gcm::new(aes_gcm::Key::<Aes256Gcm>::from_slice(&key));
50
51 let mut nonce_bytes = [0u8; 12];
53 for b in nonce_bytes.iter_mut() {
54 *b = rand::random();
55 }
56 let nonce = aes_gcm::Nonce::from_slice(&nonce_bytes);
57
58 let data = tokio::fs::read(path)
60 .await
61 .map_err(|e| EncryptionError::InvalidData)?;
62
63 let ciphertext = cipher
65 .encrypt(nonce, data.as_ref())
66 .map_err(|e| EncryptionError::EncryptionFailed(e.to_string()))?;
67
68 let temp_path = self.create_temp_path()?;
70 let mut file = tokio::fs::File::create(&temp_path)
71 .await
72 .map_err(|_| EncryptionError::InvalidData)?;
73
74 use tokio::io::AsyncWriteExt;
76 file.write_all(&nonce_bytes)
77 .await
78 .map_err(|_| EncryptionError::InvalidData)?;
79 file.write_all(&ciphertext)
80 .await
81 .map_err(|_| EncryptionError::InvalidData)?;
82
83 let metadata = EncryptionMetadata {
84 algorithm: config.algorithm.clone(),
85 key_id: config.key_id.clone(),
86 nonce: nonce_bytes.to_vec(),
87 hmac: self.calculate_hmac(&ciphertext),
88 };
89
90 Ok((Some(temp_path), Some(metadata)))
91 }
92
93 pub async fn decrypt_file(
94 &self,
95 encrypted_path: &Path,
96 metadata: &EncryptionMetadata,
97 ) -> Result<PathBuf, EncryptionError> {
98 let key = self.get_key(&metadata.key_id)?;
99 let cipher = Aes256Gcm::new(aes_gcm::Key::<Aes256Gcm>::from_slice(&key));
100
101 let data = tokio::fs::read(encrypted_path)
103 .await
104 .map_err(|_| EncryptionError::InvalidData)?;
105
106 if data.len() < 12 {
107 return Err(EncryptionError::InvalidData);
108 }
109
110 let (nonce_bytes, ciphertext) = data.split_at(12);
111 let nonce = aes_gcm::Nonce::from_slice(nonce_bytes);
112
113 let plaintext = cipher
115 .decrypt(nonce, ciphertext)
116 .map_err(|e| EncryptionError::DecryptionFailed(e.to_string()))?;
117
118 if metadata.hmac != self.calculate_hmac(ciphertext) {
120 return Err(EncryptionError::IntegrityCheckFailed);
121 }
122
123 let temp_path = self.create_temp_path()?;
125 tokio::fs::write(&temp_path, &plaintext)
126 .await
127 .map_err(|_| EncryptionError::InvalidData)?;
128
129 Ok(temp_path)
130 }
131
132 fn calculate_hmac(&self, ciphertext: &[u8]) -> Hmac {
133 use sha2::{Digest, Sha256};
134 let mut hasher = Sha256::new();
135 hasher.update(ciphertext);
136 hasher.finalize().to_vec()
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::config::EncryptionConfig;
144 use crate::encryption::types::{EncryptionAlgorithm, IvMode};
145
146 #[tokio::test]
147 async fn test_hmac_and_encrypt_decrypt() {
148 let mut mgr = EncryptionManager::new();
149 mgr.key_store.insert("test".to_string(), vec![0u8; 32]);
150 let tmp = std::env::temp_dir().join("enc_test.bin");
151 tokio::fs::write(&tmp, b"hello").await.unwrap();
152 let cfg = EncryptionConfig {
153 algorithm: EncryptionAlgorithm::Aes256Gcm,
154 key_id: "test".to_string(),
155 iv_mode: IvMode::Random,
156 };
157 let (enc_path_opt, metadata_opt) = mgr.encrypt_file(&tmp, &cfg).await.unwrap();
158 assert!(enc_path_opt.is_some());
159 assert!(metadata_opt.is_some());
160 let enc_path = enc_path_opt.unwrap();
161 let metadata = metadata_opt.unwrap();
162 let dec_path = mgr.decrypt_file(&enc_path, &metadata).await.unwrap();
163 let dec = tokio::fs::read(&dec_path).await.unwrap();
164 assert_eq!(dec, b"hello");
165 }
166}