zephyrfs/zephyrfs-node / de09132

Browse files

2.4: Zero-knowledge encrypted storage with capability-based access control and deduplication

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
de09132af2ccd5b29618b0fec50324c27b184f3c
Parents
585cb7b
Tree
7d226cb

3 changed files

StatusFile+-
M src/storage.rs 10 1
A src/storage/encrypted_chunk_store.rs 539 0
A src/storage/enhanced_storage_manager.rs 498 0
src/storage.rsmodified
@@ -8,9 +8,18 @@ pub mod chunk_store;
8
 pub mod metadata_store;
8
 pub mod metadata_store;
9
 pub mod file_chunker;
9
 pub mod file_chunker;
10
 pub mod storage_manager;
10
 pub mod storage_manager;
11
+pub mod encrypted_chunk_store;
12
+pub mod enhanced_storage_manager;
11
 
13
 
12
 // Re-export main storage interfaces
14
 // Re-export main storage interfaces
13
 pub use chunk_store::{ChunkStore, ChunkMetadata, StorageStats};
15
 pub use chunk_store::{ChunkStore, ChunkMetadata, StorageStats};
14
 pub use metadata_store::{MetadataStore, FileMetadata};
16
 pub use metadata_store::{MetadataStore, FileMetadata};
15
 pub use file_chunker::{FileChunker, ChunkInfo};
17
 pub use file_chunker::{FileChunker, ChunkInfo};
16
 pub use storage_manager::{StorageManager, StorageConfig, CapacityInfo};
18
 pub use storage_manager::{StorageManager, StorageConfig, CapacityInfo};
19
+pub use encrypted_chunk_store::{
20
+    EncryptedChunkStore, EncryptedChunkMetadata, EncryptedFileMetadata, 
21
+    EncryptionMetadata, FileCapability, EncryptedStorageStats
22
+};
23
+pub use enhanced_storage_manager::{
24
+    EnhancedStorageManager, CombinedCapacityInfo, FileStorageResult
25
+};
src/storage/encrypted_chunk_store.rsadded
@@ -0,0 +1,539 @@
1
+//! Encrypted chunk storage for ZephyrFS
2
+//!
3
+//! Provides storage layer functionality for encrypted chunks while maintaining
4
+//! zero-knowledge security. Storage nodes never see plaintext data.
5
+
6
+use anyhow::{Context, Result};
7
+use rocksdb::{DB, Options, WriteBatch};
8
+use serde::{Deserialize, Serialize};
9
+use sha2::{Digest, Sha256};
10
+use std::collections::HashMap;
11
+use std::path::Path;
12
+use std::sync::Arc;
13
+use tokio::sync::RwLock;
14
+use tracing::{debug, info, warn};
15
+
16
+use crate::crypto::{EncryptedData, ContentId};
17
+
18
+/// Metadata for encrypted chunks stored in the system
19
+/// 
20
+/// Zero-knowledge: Only stores encrypted data and hashes, no plaintext metadata
21
+#[derive(Debug, Clone, Serialize, Deserialize)]
22
+pub struct EncryptedChunkMetadata {
23
+    /// Content hash of the encrypted chunk (for deduplication)
24
+    pub encrypted_hash: String,
25
+    
26
+    /// Size of the encrypted chunk in bytes
27
+    pub encrypted_size: u64,
28
+    
29
+    /// Timestamp when chunk was stored
30
+    pub stored_at: u64,
31
+    
32
+    /// Reference count (how many encrypted files reference this chunk)
33
+    pub ref_count: u32,
34
+    
35
+    /// Verification checksum for integrity (of encrypted data)
36
+    pub checksum: String,
37
+    
38
+    /// Content addressing hash (encrypted, for lookup)
39
+    pub content_id: Option<String>,
40
+    
41
+    /// Encryption nonce (safe to store)
42
+    pub nonce: [u8; 12],
43
+    
44
+    /// Additional authenticated data (encrypted metadata)
45
+    pub aad: Vec<u8>,
46
+    
47
+    /// Key derivation path (safe to store, no keys)
48
+    pub key_path: Vec<u32>,
49
+}
50
+
51
+/// Enhanced file metadata that includes encryption information
52
+/// 
53
+/// Zero-knowledge: Stores encrypted metadata and access patterns
54
+#[derive(Debug, Clone, Serialize, Deserialize)]
55
+pub struct EncryptedFileMetadata {
56
+    /// Original filename (encrypted)
57
+    pub encrypted_name: Vec<u8>,
58
+    
59
+    /// Encrypted file size info
60
+    pub encrypted_size_info: Vec<u8>,
61
+    
62
+    /// File hash of encrypted data
63
+    pub encrypted_file_hash: String,
64
+    
65
+    /// List of encrypted chunk IDs
66
+    pub encrypted_chunk_ids: Vec<String>,
67
+    
68
+    /// Timestamp (can be plaintext for sorting)
69
+    pub created_at: u64,
70
+    pub modified_at: u64,
71
+    
72
+    /// Encryption metadata
73
+    pub encryption_metadata: EncryptionMetadata,
74
+    
75
+    /// Access control capabilities (encrypted)
76
+    pub capabilities: Vec<u8>,
77
+}
78
+
79
+/// Encryption-specific metadata stored with files
80
+/// 
81
+/// Zero-knowledge: No sensitive key material stored
82
+#[derive(Debug, Clone, Serialize, Deserialize)]
83
+pub struct EncryptionMetadata {
84
+    /// Encryption algorithm version
85
+    pub version: u32,
86
+    
87
+    /// Number of encrypted segments
88
+    pub segment_count: u32,
89
+    
90
+    /// Chunk size used for encryption (MB)
91
+    pub chunk_size_mb: u32,
92
+    
93
+    /// Content addressing algorithm
94
+    pub content_hash_algorithm: String,
95
+    
96
+    /// Verification hash algorithm
97
+    pub verification_hash_algorithm: String,
98
+    
99
+    /// Master nonce for file-level operations
100
+    pub master_nonce: [u8; 12],
101
+    
102
+    /// Encrypted content verification data
103
+    pub encrypted_content_verification: Vec<u8>,
104
+}
105
+
106
+/// Capability token for secure file access
107
+/// 
108
+/// Zero-knowledge: Contains encrypted access permissions and keys
109
+#[derive(Debug, Clone, Serialize, Deserialize)]
110
+pub struct FileCapability {
111
+    /// Unique capability ID
112
+    pub capability_id: String,
113
+    
114
+    /// File ID this capability grants access to
115
+    pub file_id: String,
116
+    
117
+    /// Encrypted access permissions (read, write, share, etc.)
118
+    pub encrypted_permissions: Vec<u8>,
119
+    
120
+    /// Encrypted key material for this capability
121
+    pub encrypted_key_material: Vec<u8>,
122
+    
123
+    /// Capability expiration timestamp (optional)
124
+    pub expires_at: Option<u64>,
125
+    
126
+    /// Created timestamp
127
+    pub created_at: u64,
128
+    
129
+    /// Capability signature (for verification)
130
+    pub signature: Vec<u8>,
131
+}
132
+
133
+/// Enhanced chunk store that handles encrypted chunks
134
+/// 
135
+/// Zero-knowledge: Never processes or sees plaintext data
136
+pub struct EncryptedChunkStore {
137
+    /// Underlying chunk storage
138
+    db: Arc<DB>,
139
+    
140
+    /// Metadata cache for encrypted chunks
141
+    metadata_cache: Arc<RwLock<HashMap<String, EncryptedChunkMetadata>>>,
142
+    
143
+    /// File metadata storage
144
+    file_metadata_cache: Arc<RwLock<HashMap<String, EncryptedFileMetadata>>>,
145
+    
146
+    /// Capability storage
147
+    capability_cache: Arc<RwLock<HashMap<String, FileCapability>>>,
148
+    
149
+    /// Storage statistics
150
+    stats: Arc<RwLock<EncryptedStorageStats>>,
151
+}
152
+
153
+#[derive(Debug, Default, Clone)]
154
+pub struct EncryptedStorageStats {
155
+    pub total_encrypted_chunks: u64,
156
+    pub total_encrypted_size: u64,
157
+    pub total_encrypted_files: u64,
158
+    pub active_capabilities: u64,
159
+    pub cache_hits: u64,
160
+    pub cache_misses: u64,
161
+    pub deduplication_savings: u64,
162
+}
163
+
164
+impl EncryptedChunkStore {
165
+    /// Create a new encrypted chunk store
166
+    /// 
167
+    /// Zero-knowledge: Configures storage to handle only encrypted data
168
+    pub fn new<P: AsRef<Path>>(db_path: P) -> Result<Self> {
169
+        info!("Initializing EncryptedChunkStore for zero-knowledge storage");
170
+        
171
+        let mut opts = Options::default();
172
+        opts.create_if_missing(true);
173
+        opts.set_paranoid_checks(true);
174
+        opts.set_use_fsync(true);
175
+        
176
+        let db = DB::open(&opts, db_path)
177
+            .context("Failed to open encrypted chunk database")?;
178
+        
179
+        let store = Self {
180
+            db: Arc::new(db),
181
+            metadata_cache: Arc::new(RwLock::new(HashMap::new())),
182
+            file_metadata_cache: Arc::new(RwLock::new(HashMap::new())),
183
+            capability_cache: Arc::new(RwLock::new(HashMap::new())),
184
+            stats: Arc::new(RwLock::new(EncryptedStorageStats::default())),
185
+        };
186
+        
187
+        store.load_stats_from_db()?;
188
+        info!("EncryptedChunkStore initialized with zero-knowledge architecture");
189
+        Ok(store)
190
+    }
191
+    
192
+    /// Store an encrypted chunk with deduplication
193
+    /// 
194
+    /// Zero-knowledge: Only handles encrypted data, maintains content-based deduplication
195
+    pub async fn store_encrypted_chunk(&self, chunk_id: &str, encrypted_data: &EncryptedData) -> Result<String> {
196
+        debug!("Storing encrypted chunk: {} ({} bytes)", chunk_id, encrypted_data.ciphertext.len());
197
+        
198
+        // Calculate hash of encrypted content for deduplication
199
+        let mut hasher = Sha256::new();
200
+        hasher.update(&encrypted_data.ciphertext);
201
+        hasher.update(&encrypted_data.nonce);
202
+        hasher.update(&encrypted_data.aad);
203
+        let encrypted_hash = hex::encode(hasher.finalize());
204
+        
205
+        // Check if this encrypted chunk already exists (deduplication)
206
+        if let Some(existing_metadata) = self.get_encrypted_chunk_metadata(&encrypted_hash).await? {
207
+            // Increment reference count
208
+            let mut metadata = existing_metadata;
209
+            metadata.ref_count += 1;
210
+            self.update_encrypted_chunk_metadata(&encrypted_hash, &metadata).await?;
211
+            
212
+            debug!("Deduplicated encrypted chunk: {} (ref_count: {})", encrypted_hash, metadata.ref_count);
213
+            return Ok(encrypted_hash);
214
+        }
215
+        
216
+        // Create checksum for integrity verification
217
+        let checksum = self.calculate_encrypted_checksum(&encrypted_data.ciphertext, &encrypted_hash);
218
+        
219
+        let metadata = EncryptedChunkMetadata {
220
+            encrypted_hash: encrypted_hash.clone(),
221
+            encrypted_size: encrypted_data.ciphertext.len() as u64,
222
+            stored_at: std::time::SystemTime::now()
223
+                .duration_since(std::time::UNIX_EPOCH)?
224
+                .as_secs(),
225
+            ref_count: 1,
226
+            checksum,
227
+            content_id: None, // Set by caller if needed
228
+            nonce: encrypted_data.nonce,
229
+            aad: encrypted_data.aad.clone(),
230
+            key_path: encrypted_data.key_path.clone(),
231
+        };
232
+        
233
+        // Store encrypted chunk data and metadata atomically
234
+        let mut batch = WriteBatch::default();
235
+        
236
+        // Store the encrypted ciphertext
237
+        let chunk_key = format!("chunk:{}", encrypted_hash);
238
+        batch.put(&chunk_key, &encrypted_data.ciphertext);
239
+        
240
+        // Store metadata
241
+        let metadata_key = format!("meta:{}", encrypted_hash);
242
+        let metadata_bytes = bincode::serialize(&metadata)
243
+            .context("Failed to serialize encrypted chunk metadata")?;
244
+        batch.put(&metadata_key, &metadata_bytes);
245
+        
246
+        self.db.write(batch)
247
+            .context("Failed to store encrypted chunk atomically")?;
248
+        
249
+        // Update cache and stats
250
+        {
251
+            let mut cache = self.metadata_cache.write().await;
252
+            cache.insert(encrypted_hash.clone(), metadata);
253
+        }
254
+        
255
+        {
256
+            let mut stats = self.stats.write().await;
257
+            stats.total_encrypted_chunks += 1;
258
+            stats.total_encrypted_size += encrypted_data.ciphertext.len() as u64;
259
+        }
260
+        
261
+        info!("Stored new encrypted chunk: {}", encrypted_hash);
262
+        Ok(encrypted_hash)
263
+    }
264
+    
265
+    /// Retrieve an encrypted chunk by hash
266
+    /// 
267
+    /// Zero-knowledge: Returns encrypted data without any decryption
268
+    pub async fn retrieve_encrypted_chunk(&self, encrypted_hash: &str) -> Result<Option<EncryptedData>> {
269
+        debug!("Retrieving encrypted chunk: {}", encrypted_hash);
270
+        
271
+        // Get metadata first
272
+        let metadata = match self.get_encrypted_chunk_metadata(encrypted_hash).await? {
273
+            Some(meta) => meta,
274
+            None => {
275
+                debug!("Encrypted chunk not found: {}", encrypted_hash);
276
+                return Ok(None);
277
+            }
278
+        };
279
+        
280
+        // Retrieve encrypted ciphertext
281
+        let chunk_key = format!("chunk:{}", encrypted_hash);
282
+        let ciphertext = match self.db.get(&chunk_key)
283
+            .context("Failed to read encrypted chunk from database")? {
284
+            Some(data) => data,
285
+            None => {
286
+                warn!("Encrypted chunk data missing for hash: {}", encrypted_hash);
287
+                return Ok(None);
288
+            }
289
+        };
290
+        
291
+        // Verify integrity
292
+        let computed_checksum = self.calculate_encrypted_checksum(&ciphertext, encrypted_hash);
293
+        if computed_checksum != metadata.checksum {
294
+            return Err(anyhow::anyhow!(
295
+                "Encrypted chunk integrity verification failed for {}", encrypted_hash
296
+            ));
297
+        }
298
+        
299
+        // Reconstruct EncryptedData
300
+        let encrypted_data = EncryptedData {
301
+            segment_index: 0, // Will be set by caller
302
+            ciphertext,
303
+            nonce: metadata.nonce,
304
+            aad: metadata.aad,
305
+            key_path: metadata.key_path,
306
+        };
307
+        
308
+        {
309
+            let mut stats = self.stats.write().await;
310
+            stats.cache_hits += 1;
311
+        }
312
+        
313
+        Ok(Some(encrypted_data))
314
+    }
315
+    
316
+    /// Store encrypted file metadata
317
+    /// 
318
+    /// Zero-knowledge: All sensitive metadata is encrypted
319
+    pub async fn store_encrypted_file_metadata(&self, file_id: &str, metadata: &EncryptedFileMetadata) -> Result<()> {
320
+        debug!("Storing encrypted file metadata: {}", file_id);
321
+        
322
+        let metadata_key = format!("file:{}", file_id);
323
+        let metadata_bytes = bincode::serialize(metadata)
324
+            .context("Failed to serialize encrypted file metadata")?;
325
+        
326
+        self.db.put(&metadata_key, &metadata_bytes)
327
+            .context("Failed to store encrypted file metadata")?;
328
+        
329
+        // Update cache
330
+        {
331
+            let mut cache = self.file_metadata_cache.write().await;
332
+            cache.insert(file_id.to_string(), metadata.clone());
333
+        }
334
+        
335
+        {
336
+            let mut stats = self.stats.write().await;
337
+            stats.total_encrypted_files += 1;
338
+        }
339
+        
340
+        Ok(())
341
+    }
342
+    
343
+    /// Retrieve encrypted file metadata
344
+    /// 
345
+    /// Zero-knowledge: Returns encrypted metadata without decryption
346
+    pub async fn get_encrypted_file_metadata(&self, file_id: &str) -> Result<Option<EncryptedFileMetadata>> {
347
+        // Check cache first
348
+        {
349
+            let cache = self.file_metadata_cache.read().await;
350
+            if let Some(metadata) = cache.get(file_id) {
351
+                return Ok(Some(metadata.clone()));
352
+            }
353
+        }
354
+        
355
+        let metadata_key = format!("file:{}", file_id);
356
+        let metadata_bytes = match self.db.get(&metadata_key)
357
+            .context("Failed to read encrypted file metadata")? {
358
+            Some(data) => data,
359
+            None => return Ok(None),
360
+        };
361
+        
362
+        let metadata: EncryptedFileMetadata = bincode::deserialize(&metadata_bytes)
363
+            .context("Failed to deserialize encrypted file metadata")?;
364
+        
365
+        // Update cache
366
+        {
367
+            let mut cache = self.file_metadata_cache.write().await;
368
+            cache.insert(file_id.to_string(), metadata.clone());
369
+        }
370
+        
371
+        Ok(Some(metadata))
372
+    }
373
+    
374
+    /// Store a file capability for secure access control
375
+    /// 
376
+    /// Zero-knowledge: Capability contains encrypted permissions and keys
377
+    pub async fn store_capability(&self, capability: &FileCapability) -> Result<()> {
378
+        debug!("Storing file capability: {}", capability.capability_id);
379
+        
380
+        let cap_key = format!("cap:{}", capability.capability_id);
381
+        let cap_bytes = bincode::serialize(capability)
382
+            .context("Failed to serialize capability")?;
383
+        
384
+        self.db.put(&cap_key, &cap_bytes)
385
+            .context("Failed to store capability")?;
386
+        
387
+        // Update cache and stats
388
+        {
389
+            let mut cache = self.capability_cache.write().await;
390
+            cache.insert(capability.capability_id.clone(), capability.clone());
391
+        }
392
+        
393
+        {
394
+            let mut stats = self.stats.write().await;
395
+            stats.active_capabilities += 1;
396
+        }
397
+        
398
+        Ok(())
399
+    }
400
+    
401
+    /// Retrieve a file capability
402
+    pub async fn get_capability(&self, capability_id: &str) -> Result<Option<FileCapability>> {
403
+        // Check cache first
404
+        {
405
+            let cache = self.capability_cache.read().await;
406
+            if let Some(capability) = cache.get(capability_id) {
407
+                return Ok(Some(capability.clone()));
408
+            }
409
+        }
410
+        
411
+        let cap_key = format!("cap:{}", capability_id);
412
+        let cap_bytes = match self.db.get(&cap_key)
413
+            .context("Failed to read capability")? {
414
+            Some(data) => data,
415
+            None => return Ok(None),
416
+        };
417
+        
418
+        let capability: FileCapability = bincode::deserialize(&cap_bytes)
419
+            .context("Failed to deserialize capability")?;
420
+        
421
+        // Check expiration
422
+        if let Some(expires_at) = capability.expires_at {
423
+            let now = std::time::SystemTime::now()
424
+                .duration_since(std::time::UNIX_EPOCH)?
425
+                .as_secs();
426
+            if now > expires_at {
427
+                debug!("Capability expired: {}", capability_id);
428
+                return Ok(None);
429
+            }
430
+        }
431
+        
432
+        // Update cache
433
+        {
434
+            let mut cache = self.capability_cache.write().await;
435
+            cache.insert(capability_id.to_string(), capability.clone());
436
+        }
437
+        
438
+        Ok(Some(capability))
439
+    }
440
+    
441
+    /// Get storage statistics
442
+    pub async fn get_encrypted_stats(&self) -> EncryptedStorageStats {
443
+        let stats = self.stats.read().await;
444
+        (*stats).clone()
445
+    }
446
+    
447
+    /// Helper methods
448
+    async fn get_encrypted_chunk_metadata(&self, encrypted_hash: &str) -> Result<Option<EncryptedChunkMetadata>> {
449
+        // Check cache first
450
+        {
451
+            let cache = self.metadata_cache.read().await;
452
+            if let Some(metadata) = cache.get(encrypted_hash) {
453
+                return Ok(Some(metadata.clone()));
454
+            }
455
+        }
456
+        
457
+        let metadata_key = format!("meta:{}", encrypted_hash);
458
+        let metadata_bytes = match self.db.get(&metadata_key)? {
459
+            Some(data) => data,
460
+            None => return Ok(None),
461
+        };
462
+        
463
+        let metadata: EncryptedChunkMetadata = bincode::deserialize(&metadata_bytes)
464
+            .context("Failed to deserialize encrypted chunk metadata")?;
465
+        
466
+        Ok(Some(metadata))
467
+    }
468
+    
469
+    async fn update_encrypted_chunk_metadata(&self, encrypted_hash: &str, metadata: &EncryptedChunkMetadata) -> Result<()> {
470
+        let metadata_key = format!("meta:{}", encrypted_hash);
471
+        let metadata_bytes = bincode::serialize(metadata)?;
472
+        
473
+        self.db.put(&metadata_key, &metadata_bytes)?;
474
+        
475
+        // Update cache
476
+        {
477
+            let mut cache = self.metadata_cache.write().await;
478
+            cache.insert(encrypted_hash.to_string(), metadata.clone());
479
+        }
480
+        
481
+        Ok(())
482
+    }
483
+    
484
+    fn calculate_encrypted_checksum(&self, ciphertext: &[u8], hash: &str) -> String {
485
+        let mut hasher = Sha256::new();
486
+        hasher.update(ciphertext);
487
+        hasher.update(hash.as_bytes());
488
+        hasher.update(b"zephyrfs-encrypted-chunk-v1");
489
+        hex::encode(hasher.finalize())
490
+    }
491
+    
492
+    fn load_stats_from_db(&self) -> Result<()> {
493
+        // Implementation would scan database to calculate stats
494
+        // For now, we'll initialize with defaults
495
+        Ok(())
496
+    }
497
+}
498
+
499
+#[cfg(test)]
500
+mod tests {
501
+    use super::*;
502
+    use tempfile::tempdir;
503
+    
504
+    #[tokio::test]
505
+    async fn test_encrypted_chunk_store_creation() {
506
+        let temp_dir = tempdir().unwrap();
507
+        let store = EncryptedChunkStore::new(temp_dir.path()).await.unwrap();
508
+        let stats = store.get_encrypted_stats().await;
509
+        
510
+        assert_eq!(stats.total_encrypted_chunks, 0);
511
+        assert_eq!(stats.total_encrypted_files, 0);
512
+        assert_eq!(stats.active_capabilities, 0);
513
+    }
514
+    
515
+    #[tokio::test]
516
+    async fn test_encrypted_chunk_deduplication() {
517
+        let temp_dir = tempdir().unwrap();
518
+        let store = EncryptedChunkStore::new(temp_dir.path()).await.unwrap();
519
+        
520
+        let encrypted_data = EncryptedData {
521
+            segment_index: 0,
522
+            ciphertext: vec![1, 2, 3, 4, 5],
523
+            nonce: [0u8; 12],
524
+            aad: vec![],
525
+            key_path: vec![0, 1],
526
+        };
527
+        
528
+        // Store same encrypted chunk twice
529
+        let hash1 = store.store_encrypted_chunk("chunk1", &encrypted_data).await.unwrap();
530
+        let hash2 = store.store_encrypted_chunk("chunk2", &encrypted_data).await.unwrap();
531
+        
532
+        // Should be deduplicated (same hash)
533
+        assert_eq!(hash1, hash2);
534
+        
535
+        // Should have reference count of 2
536
+        let metadata = store.get_encrypted_chunk_metadata(&hash1).await.unwrap().unwrap();
537
+        assert_eq!(metadata.ref_count, 2);
538
+    }
539
+}
src/storage/enhanced_storage_manager.rsadded
@@ -0,0 +1,498 @@
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
+}