Rust · 19658 bytes Raw Blame History
1 //! Enhanced storage manager with encryption support
2 //!
3 //! Integrates encrypted chunk storage with existing storage operations
4 //! while maintaining backward compatibility and zero-knowledge security.
5
6 use anyhow::{Context, Result};
7 use sha2::{Digest, Sha256};
8 use std::path::{Path, PathBuf};
9 use std::sync::Arc;
10 use tokio::sync::RwLock;
11 use tracing::{debug, info, warn, error};
12
13 use crate::crypto::{ZephyrCrypto, EncryptedData, ContentId};
14 use crate::storage::{
15 StorageManager, StorageConfig, CapacityInfo,
16 EncryptedChunkStore, EncryptedChunkMetadata, EncryptedFileMetadata,
17 EncryptionMetadata, FileCapability, EncryptedStorageStats,
18 };
19
20 /// Enhanced storage manager supporting both encrypted and plaintext operations
21 ///
22 /// Zero-knowledge: Encrypted files never expose plaintext to storage nodes
23 /// Backward compatibility: Supports existing plaintext storage operations
24 pub struct EnhancedStorageManager {
25 /// Traditional plaintext storage manager
26 plaintext_storage: Arc<StorageManager>,
27
28 /// Encrypted chunk storage
29 encrypted_storage: Arc<EncryptedChunkStore>,
30
31 /// Storage configuration
32 config: StorageConfig,
33
34 /// Base storage path
35 base_path: PathBuf,
36
37 /// Combined capacity tracking
38 combined_capacity: Arc<RwLock<CombinedCapacityInfo>>,
39 }
40
41 /// Combined capacity information for both encrypted and plaintext storage
42 #[derive(Debug, Clone)]
43 pub struct CombinedCapacityInfo {
44 /// Traditional storage info
45 pub plaintext_capacity: CapacityInfo,
46
47 /// Encrypted storage info
48 pub encrypted_stats: EncryptedStorageStats,
49
50 /// Combined totals
51 pub total_used_space: u64,
52 pub total_files: u64,
53 pub total_chunks: u64,
54 pub combined_efficiency: f64,
55 }
56
57 /// File storage result containing information about how the file was stored
58 #[derive(Debug)]
59 pub struct FileStorageResult {
60 pub file_hash: String,
61 pub is_encrypted: bool,
62 pub chunk_count: usize,
63 pub content_id: Option<String>,
64 pub capability_id: Option<String>,
65 }
66
67 impl EnhancedStorageManager {
68 /// Create a new enhanced storage manager
69 ///
70 /// Zero-knowledge: Initializes both plaintext and encrypted storage backends
71 pub async fn new<P: AsRef<Path>>(base_path: P, config: StorageConfig) -> Result<Self> {
72 let base_path = base_path.as_ref().to_path_buf();
73 info!("Initializing EnhancedStorageManager with encryption support at: {:?}", base_path);
74
75 // Create subdirectories
76 let plaintext_path = base_path.join("plaintext");
77 let encrypted_path = base_path.join("encrypted");
78
79 std::fs::create_dir_all(&plaintext_path)
80 .context("Failed to create plaintext storage directory")?;
81 std::fs::create_dir_all(&encrypted_path)
82 .context("Failed to create encrypted storage directory")?;
83
84 // Initialize storage backends
85 let plaintext_storage = Arc::new(StorageManager::new(&plaintext_path, config.clone()).await
86 .context("Failed to initialize plaintext storage manager")?);
87
88 let encrypted_storage = Arc::new(EncryptedChunkStore::new(&encrypted_path)
89 .context("Failed to initialize encrypted chunk store")?);
90
91 // Initialize combined capacity tracking
92 let combined_capacity = Arc::new(RwLock::new(CombinedCapacityInfo {
93 plaintext_capacity: plaintext_storage.get_capacity_info().await,
94 encrypted_stats: encrypted_storage.get_encrypted_stats().await,
95 total_used_space: 0,
96 total_files: 0,
97 total_chunks: 0,
98 combined_efficiency: 1.0,
99 }));
100
101 let manager = Self {
102 plaintext_storage,
103 encrypted_storage,
104 config,
105 base_path,
106 combined_capacity,
107 };
108
109 // Update combined capacity information
110 manager.refresh_combined_capacity().await?;
111
112 info!("EnhancedStorageManager initialized with zero-knowledge encryption support");
113 Ok(manager)
114 }
115
116 /// Store a file with automatic encryption (if crypto context provided)
117 ///
118 /// Zero-knowledge: When crypto is provided, file is encrypted before storage
119 pub async fn store_file_with_crypto(
120 &self,
121 file_id: &str,
122 data: &[u8],
123 filename: &str,
124 crypto: Option<&ZephyrCrypto>
125 ) -> Result<FileStorageResult> {
126 info!("Storing file: {} ({} bytes) with encryption: {}",
127 filename, data.len(), crypto.is_some());
128
129 match crypto {
130 Some(crypto_ctx) => {
131 self.store_encrypted_file(file_id, data, filename, crypto_ctx).await
132 }
133 None => {
134 self.store_plaintext_file(file_id, data, filename).await
135 }
136 }
137 }
138
139 /// Store an encrypted file using zero-knowledge encryption
140 ///
141 /// Zero-knowledge: File is encrypted before storage, storage nodes never see plaintext
142 async fn store_encrypted_file(
143 &self,
144 file_id: &str,
145 data: &[u8],
146 filename: &str,
147 crypto: &ZephyrCrypto,
148 ) -> Result<FileStorageResult> {
149 debug!("Storing encrypted file: {}", file_id);
150
151 // Encrypt file data into segments
152 let encrypted_segments = crypto.encrypt_file(data)
153 .context("Failed to encrypt file data")?;
154
155 // Generate content ID for encrypted file
156 let content_id = crypto.content_id(data);
157
158 // Store encrypted chunks
159 let mut chunk_hashes = Vec::new();
160 for (index, encrypted_data) in encrypted_segments.iter().enumerate() {
161 let chunk_id = format!("{}:{}", file_id, index);
162 let chunk_hash = self.encrypted_storage.store_encrypted_chunk(&chunk_id, encrypted_data).await
163 .context("Failed to store encrypted chunk")?;
164 chunk_hashes.push(chunk_hash);
165 }
166
167 // Create encrypted file metadata
168 let encrypted_metadata = EncryptedFileMetadata {
169 encrypted_name: self.encrypt_filename(filename, crypto)?,
170 encrypted_size_info: self.encrypt_size_info(data.len(), crypto)?,
171 encrypted_file_hash: content_id.to_hex(),
172 encrypted_chunk_ids: chunk_hashes,
173 created_at: std::time::SystemTime::now()
174 .duration_since(std::time::UNIX_EPOCH)?
175 .as_secs(),
176 modified_at: std::time::SystemTime::now()
177 .duration_since(std::time::UNIX_EPOCH)?
178 .as_secs(),
179 encryption_metadata: EncryptionMetadata {
180 version: 1,
181 segment_count: encrypted_segments.len() as u32,
182 chunk_size_mb: 1, // TODO: Get from config
183 content_hash_algorithm: "blake3".to_string(),
184 verification_hash_algorithm: "blake3".to_string(),
185 master_nonce: [0u8; 12], // TODO: Generate proper nonce
186 encrypted_content_verification: self.encrypt_content_verification(&content_id, crypto)?,
187 },
188 capabilities: vec![], // TODO: Generate initial capability
189 };
190
191 // Store encrypted file metadata
192 self.encrypted_storage.store_encrypted_file_metadata(file_id, &encrypted_metadata).await
193 .context("Failed to store encrypted file metadata")?;
194
195 // Create file capability for access control
196 let capability = self.create_file_capability(file_id, crypto).await?;
197 let capability_id = capability.capability_id.clone();
198
199 self.encrypted_storage.store_capability(&capability).await
200 .context("Failed to store file capability")?;
201
202 // Update capacity tracking
203 self.refresh_combined_capacity().await?;
204
205 info!("Successfully stored encrypted file: {} with capability: {}", file_id, capability_id);
206 Ok(FileStorageResult {
207 file_hash: content_id.to_hex(),
208 is_encrypted: true,
209 chunk_count: encrypted_segments.len(),
210 content_id: Some(content_id.to_hex()),
211 capability_id: Some(capability_id),
212 })
213 }
214
215 /// Store a plaintext file using existing storage manager
216 async fn store_plaintext_file(
217 &self,
218 file_id: &str,
219 data: &[u8],
220 filename: &str,
221 ) -> Result<FileStorageResult> {
222 debug!("Storing plaintext file: {}", file_id);
223
224 let file_hash = self.plaintext_storage.store_file(file_id, data, filename).await
225 .context("Failed to store plaintext file")?;
226
227 // Update capacity tracking
228 self.refresh_combined_capacity().await?;
229
230 info!("Successfully stored plaintext file: {}", file_id);
231 Ok(FileStorageResult {
232 file_hash,
233 is_encrypted: false,
234 chunk_count: 1, // Plaintext files are stored as single chunks
235 content_id: None,
236 capability_id: None,
237 })
238 }
239
240 /// Retrieve a file with automatic detection of encryption
241 ///
242 /// Zero-knowledge: Encrypted files are decrypted using provided crypto context
243 pub async fn retrieve_file_with_crypto(
244 &self,
245 file_id: &str,
246 crypto: Option<&ZephyrCrypto>,
247 ) -> Result<Option<(Vec<u8>, bool)>> {
248 debug!("Retrieving file: {} with crypto: {}", file_id, crypto.is_some());
249
250 // First try encrypted storage
251 if let Some(encrypted_metadata) = self.encrypted_storage.get_encrypted_file_metadata(file_id).await? {
252 if let Some(crypto_ctx) = crypto {
253 let data = self.retrieve_encrypted_file(file_id, &encrypted_metadata, crypto_ctx).await?;
254 return Ok(Some((data, true)));
255 } else {
256 return Err(anyhow::anyhow!(
257 "File {} is encrypted but no crypto context provided for decryption", file_id
258 ));
259 }
260 }
261
262 // Try plaintext storage
263 if let Some(data) = self.plaintext_storage.retrieve_file(file_id).await? {
264 return Ok(Some((data, false)));
265 }
266
267 Ok(None)
268 }
269
270 /// Retrieve an encrypted file using zero-knowledge decryption
271 ///
272 /// Zero-knowledge: File is decrypted client-side, storage nodes never see plaintext
273 async fn retrieve_encrypted_file(
274 &self,
275 file_id: &str,
276 metadata: &EncryptedFileMetadata,
277 crypto: &ZephyrCrypto,
278 ) -> Result<Vec<u8>> {
279 debug!("Retrieving encrypted file: {}", file_id);
280
281 // Retrieve all encrypted chunks
282 let mut encrypted_segments = Vec::new();
283 for (index, chunk_hash) in metadata.encrypted_chunk_ids.iter().enumerate() {
284 let mut encrypted_data = self.encrypted_storage.retrieve_encrypted_chunk(chunk_hash).await?
285 .ok_or_else(|| anyhow::anyhow!("Missing encrypted chunk: {}", chunk_hash))?;
286
287 // Set correct segment index
288 encrypted_data.segment_index = index as u64;
289 encrypted_segments.push(encrypted_data);
290 }
291
292 // Decrypt file
293 let decrypted_data = crypto.decrypt_file(&encrypted_segments)
294 .context("Failed to decrypt file")?;
295
296 // Verify content integrity
297 let computed_content_id = crypto.content_id(&decrypted_data);
298 let expected_content_id = ContentId::from_hex(
299 crate::crypto::HashAlgorithm::Blake3,
300 &metadata.encrypted_file_hash
301 ).context("Invalid content ID in encrypted file metadata")?;
302
303 if !crypto.verify_content(&decrypted_data, &expected_content_id) {
304 return Err(anyhow::anyhow!(
305 "Content verification failed for encrypted file: {}", file_id
306 ));
307 }
308
309 info!("Successfully retrieved and decrypted file: {}", file_id);
310 Ok(decrypted_data)
311 }
312
313 /// Check if a file exists in either storage backend
314 pub async fn file_exists(&self, file_id: &str) -> Result<(bool, bool)> {
315 let encrypted_exists = self.encrypted_storage.get_encrypted_file_metadata(file_id).await?.is_some();
316 let plaintext_exists = self.plaintext_storage.file_exists(file_id).await?;
317 Ok((plaintext_exists, encrypted_exists))
318 }
319
320 /// Get combined storage capacity information
321 pub async fn get_combined_capacity(&self) -> CombinedCapacityInfo {
322 let capacity = self.combined_capacity.read().await;
323 capacity.clone()
324 }
325
326 /// Get file capability for encrypted files
327 pub async fn get_file_capability(&self, capability_id: &str) -> Result<Option<FileCapability>> {
328 self.encrypted_storage.get_capability(capability_id).await
329 }
330
331 /// Grant access to an encrypted file by creating a new capability
332 pub async fn grant_file_access(
333 &self,
334 file_id: &str,
335 permissions: &[u8],
336 crypto: &ZephyrCrypto,
337 ) -> Result<String> {
338 let capability = FileCapability {
339 capability_id: self.generate_capability_id(),
340 file_id: file_id.to_string(),
341 encrypted_permissions: permissions.to_vec(),
342 encrypted_key_material: self.encrypt_key_material_for_capability(crypto)?,
343 expires_at: None,
344 created_at: std::time::SystemTime::now()
345 .duration_since(std::time::UNIX_EPOCH)?
346 .as_secs(),
347 signature: vec![], // TODO: Generate proper signature
348 };
349
350 let capability_id = capability.capability_id.clone();
351 self.encrypted_storage.store_capability(&capability).await?;
352
353 info!("Created file capability: {} for file: {}", capability_id, file_id);
354 Ok(capability_id)
355 }
356
357 /// Helper methods for encryption operations
358 fn encrypt_filename(&self, filename: &str, crypto: &ZephyrCrypto) -> Result<Vec<u8>> {
359 // TODO: Implement filename encryption
360 Ok(filename.as_bytes().to_vec())
361 }
362
363 fn encrypt_size_info(&self, size: usize, crypto: &ZephyrCrypto) -> Result<Vec<u8>> {
364 // TODO: Implement size info encryption
365 Ok(size.to_le_bytes().to_vec())
366 }
367
368 fn encrypt_content_verification(&self, content_id: &ContentId, crypto: &ZephyrCrypto) -> Result<Vec<u8>> {
369 // TODO: Implement content verification encryption
370 Ok(content_id.to_hex().as_bytes().to_vec())
371 }
372
373 async fn create_file_capability(&self, file_id: &str, crypto: &ZephyrCrypto) -> Result<FileCapability> {
374 let capability_id = self.generate_capability_id();
375
376 Ok(FileCapability {
377 capability_id,
378 file_id: file_id.to_string(),
379 encrypted_permissions: vec![0x01], // Basic read permission
380 encrypted_key_material: self.encrypt_key_material_for_capability(crypto)?,
381 expires_at: None,
382 created_at: std::time::SystemTime::now()
383 .duration_since(std::time::UNIX_EPOCH)?
384 .as_secs(),
385 signature: vec![], // TODO: Generate proper signature
386 })
387 }
388
389 fn encrypt_key_material_for_capability(&self, crypto: &ZephyrCrypto) -> Result<Vec<u8>> {
390 // TODO: Implement proper key material encryption for capabilities
391 crypto.get_capability().context("Failed to get capability key material")
392 }
393
394 fn generate_capability_id(&self) -> String {
395 use rand::Rng;
396 let mut rng = rand::thread_rng();
397 let bytes: [u8; 16] = rng.gen();
398 hex::encode(bytes)
399 }
400
401 async fn refresh_combined_capacity(&self) -> Result<()> {
402 let plaintext_capacity = self.plaintext_storage.get_capacity_info().await;
403 let encrypted_stats = self.encrypted_storage.get_encrypted_stats().await;
404
405 let total_used_space = plaintext_capacity.used_space + encrypted_stats.total_encrypted_size;
406 let total_files = plaintext_capacity.file_count + encrypted_stats.total_encrypted_files;
407 let total_chunks = plaintext_capacity.chunk_count + encrypted_stats.total_encrypted_chunks;
408
409 // Calculate combined efficiency (deduplication benefits)
410 let combined_efficiency = if total_used_space > 0 {
411 // This is a simplified calculation - in practice, we'd need more sophisticated metrics
412 (plaintext_capacity.efficiency_ratio + 1.0) / 2.0
413 } else {
414 1.0
415 };
416
417 let mut capacity = self.combined_capacity.write().await;
418 *capacity = CombinedCapacityInfo {
419 plaintext_capacity,
420 encrypted_stats,
421 total_used_space,
422 total_files,
423 total_chunks,
424 combined_efficiency,
425 };
426
427 debug!("Updated combined capacity: {} bytes used, {} files, {} chunks",
428 total_used_space, total_files, total_chunks);
429
430 Ok(())
431 }
432 }
433
434 #[cfg(test)]
435 mod tests {
436 use super::*;
437 use tempfile::tempdir;
438 use crate::crypto::{CryptoParams, ZephyrCrypto};
439
440 #[tokio::test]
441 async fn test_enhanced_storage_manager_creation() {
442 let temp_dir = tempdir().unwrap();
443 let config = StorageConfig::default();
444
445 let manager = EnhancedStorageManager::new(temp_dir.path(), config).await.unwrap();
446 let capacity = manager.get_combined_capacity().await;
447
448 assert_eq!(capacity.total_used_space, 0);
449 assert_eq!(capacity.total_files, 0);
450 assert_eq!(capacity.total_chunks, 0);
451 }
452
453 #[tokio::test]
454 async fn test_plaintext_file_storage() {
455 let temp_dir = tempdir().unwrap();
456 let config = StorageConfig::default();
457 let manager = EnhancedStorageManager::new(temp_dir.path(), config).await.unwrap();
458
459 let file_id = "test-file-1";
460 let filename = "test.txt";
461 let data = b"Hello, ZephyrFS! This is a test file.";
462
463 // Store plaintext file
464 let result = manager.store_file_with_crypto(file_id, data, filename, None).await.unwrap();
465 assert!(!result.is_encrypted);
466 assert!(result.capability_id.is_none());
467
468 // Retrieve plaintext file
469 let (retrieved_data, is_encrypted) = manager.retrieve_file_with_crypto(file_id, None).await.unwrap().unwrap();
470 assert_eq!(retrieved_data, data);
471 assert!(!is_encrypted);
472 }
473
474 #[tokio::test]
475 async fn test_encrypted_file_storage() {
476 let temp_dir = tempdir().unwrap();
477 let config = StorageConfig::default();
478 let manager = EnhancedStorageManager::new(temp_dir.path(), config).await.unwrap();
479
480 // Create crypto context
481 let mut crypto = ZephyrCrypto::new();
482 crypto.init_from_password("test_password_123").unwrap();
483
484 let file_id = "encrypted-file-1";
485 let filename = "secret.txt";
486 let data = b"This is confidential data that should be encrypted.";
487
488 // Store encrypted file
489 let result = manager.store_file_with_crypto(file_id, data, filename, Some(&crypto)).await.unwrap();
490 assert!(result.is_encrypted);
491 assert!(result.capability_id.is_some());
492
493 // Retrieve encrypted file
494 let (retrieved_data, is_encrypted) = manager.retrieve_file_with_crypto(file_id, Some(&crypto)).await.unwrap().unwrap();
495 assert_eq!(retrieved_data, data);
496 assert!(is_encrypted);
497 }
498 }