Skip to main content

aura_effects/
encrypted_storage.rs

1//! Layer 3: Unified Encrypted Storage Handler
2//!
3//! Provides transparent encryption for all storage operations by composing:
4//! - `StorageEffects` for underlying data persistence
5//! - `CryptoEffects` for ChaCha20-Poly1305 encryption
6//! - `SecureStorageEffects` for master key storage in platform secure enclave
7//!
8//! **Architecture**: This is the SINGLE encryption layer for all persistent data.
9//! All application data flows through this handler and is encrypted at rest.
10//!
11//! **Layer Constraint**: Stateless handler that composes three effect traits.
12//! No multi-party coordination. No mock implementations (those belong in aura-testkit).
13
14use async_trait::async_trait;
15use aura_core::effects::{
16    CryptoEffects, SecureStorageCapability, SecureStorageEffects, SecureStorageLocation,
17    StorageCoreEffects, StorageError, StorageExtendedEffects, StorageStats,
18};
19use std::collections::HashMap;
20use std::sync::Arc;
21use tokio::sync::{Mutex, RwLock};
22use zeroize::Zeroizing;
23
24/// Nonce size for ChaCha20-Poly1305 (96 bits = 12 bytes)
25const NONCE_SIZE: usize = 12;
26
27/// Version byte for encrypted blob format
28const BLOB_VERSION: u8 = 0x01;
29
30/// Master key location in secure storage
31const MASTER_KEY_NAMESPACE: &str = "aura-encryption";
32const MASTER_KEY_ID: &str = "master-key";
33
34type MasterKeyMaterial = Arc<Zeroizing<[u8; 32]>>;
35
36/// Configuration for encrypted storage behavior
37#[derive(Debug, Clone)]
38pub struct EncryptedStorageConfig {
39    /// Enable encryption (and master-key management) for all operations.
40    ///
41    /// This exists primarily for testing and bring-up. Production should keep this `true`.
42    pub enabled: bool,
43    /// Use opaque (hashed) file names instead of semantic names
44    pub opaque_names: bool,
45    /// Custom namespace for master key in secure storage
46    pub key_namespace: Option<String>,
47    /// Custom key identifier within namespace
48    pub key_id: Option<String>,
49}
50
51impl Default for EncryptedStorageConfig {
52    fn default() -> Self {
53        Self {
54            enabled: true,
55            opaque_names: false,
56            key_namespace: None,
57            key_id: None,
58        }
59    }
60}
61
62impl EncryptedStorageConfig {
63    /// Create config with default settings
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    /// Enable opaque file names derived from the master key and semantic name.
69    pub fn with_opaque_names(mut self) -> Self {
70        self.opaque_names = true;
71        self
72    }
73
74    /// Enable or disable encryption.
75    pub fn with_encryption_enabled(mut self, enabled: bool) -> Self {
76        self.enabled = enabled;
77        self
78    }
79
80    /// Set custom key namespace
81    pub fn with_key_namespace(mut self, namespace: impl Into<String>) -> Self {
82        self.key_namespace = Some(namespace.into());
83        self
84    }
85
86    /// Set custom key identifier
87    pub fn with_key_id(mut self, id: impl Into<String>) -> Self {
88        self.key_id = Some(id.into());
89        self
90    }
91}
92
93/// Unified encrypted storage that wraps any StorageEffects implementation.
94///
95/// All data passing through this layer is encrypted using a master key
96/// stored in the platform's secure enclave via SecureStorageEffects.
97///
98/// # Type Parameters
99///
100/// - `S`: Inner storage implementation (where encrypted blobs live)
101/// - `C`: Crypto implementation (for ChaCha20-Poly1305 operations)
102/// - `Sec`: Secure storage implementation (where master key lives)
103///
104/// # Example
105///
106/// ```rust,ignore
107/// let encrypted = EncryptedStorage::new(
108///     filesystem_handler,
109///     crypto_handler,
110///     secure_storage_handler,
111///     EncryptedStorageConfig::default(),
112/// ).await?;
113///
114/// // All operations are now transparently encrypted
115/// encrypted.store("accounts", data).await?;
116/// let data = encrypted.retrieve("accounts").await?;
117/// ```
118pub struct EncryptedStorage<S, C, Sec>
119where
120    S: StorageCoreEffects + StorageExtendedEffects,
121    C: CryptoEffects,
122    Sec: SecureStorageEffects,
123{
124    /// Underlying storage for encrypted blobs
125    inner: S,
126    /// Cryptographic operations
127    crypto: Arc<C>,
128    /// Secure storage for master key
129    secure: Arc<Sec>,
130    /// Cached master key (lazily loaded/created on first use).
131    master_key: RwLock<Option<MasterKeyMaterial>>,
132    /// Single-flight guard for master-key initialization.
133    master_key_init: Mutex<()>,
134    /// Configuration options
135    config: EncryptedStorageConfig,
136}
137
138impl<S, C, Sec> EncryptedStorage<S, C, Sec>
139where
140    S: StorageCoreEffects + StorageExtendedEffects,
141    C: CryptoEffects,
142    Sec: SecureStorageEffects,
143{
144    /// Create a new encrypted storage handler.
145    ///
146    /// Master key initialization is **lazy**: the key is loaded/created on the first
147    /// `store/retrieve/...` call. This keeps runtime assembly synchronous and avoids
148    /// turning effect-system constructors into async APIs.
149    pub fn new(inner: S, crypto: Arc<C>, secure: Arc<Sec>, config: EncryptedStorageConfig) -> Self {
150        Self {
151            inner,
152            crypto,
153            secure,
154            master_key: RwLock::new(None),
155            master_key_init: Mutex::new(()),
156            config,
157        }
158    }
159
160    /// Get the secure storage location for the master key
161    fn master_key_location(config: &EncryptedStorageConfig) -> SecureStorageLocation {
162        SecureStorageLocation::new(
163            config
164                .key_namespace
165                .as_deref()
166                .unwrap_or(MASTER_KEY_NAMESPACE),
167            config.key_id.as_deref().unwrap_or(MASTER_KEY_ID),
168        )
169    }
170
171    async fn get_or_init_master_key(&self) -> Result<MasterKeyMaterial, StorageError> {
172        if let Some(key) = self.master_key.read().await.clone() {
173            return Ok(key);
174        }
175
176        // Single-flight to avoid concurrent "create key" races.
177        let _guard = self.master_key_init.lock().await;
178        if let Some(key) = self.master_key.read().await.clone() {
179            return Ok(key);
180        }
181
182        let location = Self::master_key_location(&self.config);
183        let read_caps = [SecureStorageCapability::Read];
184        let write_caps = [SecureStorageCapability::Write];
185
186        let mut key_bytes = if self.secure.secure_exists(&location).await.map_err(|e| {
187            StorageError::ConfigurationError {
188                reason: format!("Failed to check secure storage: {e}"),
189            }
190        })? {
191            self.secure
192                .secure_retrieve(&location, &read_caps)
193                .await
194                .map_err(|e| StorageError::ConfigurationError {
195                    reason: format!("Failed to retrieve master key: {e}"),
196                })?
197        } else {
198            let key_bytes = self.crypto.random_bytes(32).await;
199            if key_bytes.len() != 32 {
200                return Err(StorageError::ConfigurationError {
201                    reason: "Failed to generate 32-byte key".to_string(),
202                });
203            }
204
205            self.secure
206                .secure_store(&location, &key_bytes, &write_caps)
207                .await
208                .map_err(|e| StorageError::ConfigurationError {
209                    reason: format!("Failed to store master key: {e}"),
210                })?;
211
212            key_bytes
213        };
214
215        if key_bytes.len() != 32 {
216            // If secure storage contains an invalid key length, treat it as corrupt and
217            // re-generate a fresh master key. This preserves correctness because existing
218            // encrypted data would be unreadable with a malformed key anyway.
219            let delete_caps = [SecureStorageCapability::Delete];
220            let _ = self.secure.secure_delete(&location, &delete_caps).await;
221            let regenerated = self.crypto.random_bytes(32).await;
222            if regenerated.len() != 32 {
223                return Err(StorageError::ConfigurationError {
224                    reason: "Failed to generate 32-byte key".to_string(),
225                });
226            }
227            self.secure
228                .secure_store(&location, &regenerated, &write_caps)
229                .await
230                .map_err(|e| StorageError::ConfigurationError {
231                    reason: format!("Failed to store master key: {e}"),
232                })?;
233            key_bytes = regenerated;
234        }
235
236        let mut key = [0u8; 32];
237        key.copy_from_slice(&key_bytes);
238        let key = Arc::new(Zeroizing::new(key));
239        *self.master_key.write().await = Some(key.clone());
240        Ok(key)
241    }
242
243    /// Derive an opaque storage key from the semantic key.
244    ///
245    /// Uses the crypto KDF to derive a deterministic but unpredictable key name
246    /// from the master key and semantic key.
247    async fn derive_opaque_key(&self, semantic_key: &str) -> Result<String, StorageError> {
248        let master_key = self.get_or_init_master_key().await?;
249        let derived = self
250            .crypto
251            .kdf_derive(
252                &**master_key,
253                semantic_key.as_bytes(),
254                b"aura-opaque-key-v1",
255                16,
256            )
257            .await
258            .map_err(|e| StorageError::EncryptionFailed {
259                reason: format!("Opaque key derivation failed: {e}"),
260            })?;
261
262        // Encode as hex for filesystem-safe name
263        Ok(hex::encode(&derived))
264    }
265
266    /// Get the storage key to use (opaque or semantic)
267    async fn storage_key(&self, key: &str) -> Result<String, StorageError> {
268        if !self.config.enabled {
269            return Ok(key.to_string());
270        }
271        if self.config.opaque_names {
272            self.derive_opaque_key(key).await
273        } else {
274            Ok(key.to_string())
275        }
276    }
277
278    /// Derive a per-key encryption key using the crypto KDF.
279    ///
280    /// This binds the encryption to the storage key, providing key separation
281    /// and preventing cross-key ciphertext attacks without needing AAD.
282    async fn derive_encryption_key(&self, storage_key: &str) -> Result<[u8; 32], StorageError> {
283        let master_key = self.get_or_init_master_key().await?;
284        let derived = self
285            .crypto
286            .kdf_derive(
287                &**master_key,
288                storage_key.as_bytes(),
289                b"aura-storage-encryption-v1",
290                32,
291            )
292            .await
293            .map_err(|e| StorageError::EncryptionFailed {
294                reason: format!("Key derivation failed: {e}"),
295            })?;
296
297        let mut key = [0u8; 32];
298        key.copy_from_slice(&derived);
299        Ok(key)
300    }
301
302    /// Encrypt data with a key derived from master key + storage key.
303    ///
304    /// Format: version (1 byte) || nonce (12 bytes) || ciphertext
305    async fn encrypt(&self, key: &str, data: &[u8]) -> Result<Vec<u8>, StorageError> {
306        // Derive per-key encryption key
307        let encryption_key = self.derive_encryption_key(key).await?;
308
309        // Generate unique nonce
310        let nonce_bytes = self.crypto.random_bytes(NONCE_SIZE).await;
311        if nonce_bytes.len() != NONCE_SIZE {
312            return Err(StorageError::EncryptionFailed {
313                reason: "Failed to generate nonce".to_string(),
314            });
315        }
316
317        let mut nonce = [0u8; NONCE_SIZE];
318        nonce.copy_from_slice(&nonce_bytes);
319
320        // Encrypt with ChaCha20-Poly1305
321        let ciphertext = self
322            .crypto
323            .chacha20_encrypt(data, &encryption_key, &nonce)
324            .await
325            .map_err(|e| StorageError::EncryptionFailed {
326                reason: e.to_string(),
327            })?;
328
329        // Build blob: version || nonce || ciphertext
330        let mut blob = Vec::with_capacity(1 + NONCE_SIZE + ciphertext.len());
331        blob.push(BLOB_VERSION);
332        blob.extend_from_slice(&nonce);
333        blob.extend_from_slice(&ciphertext);
334
335        Ok(blob)
336    }
337
338    /// Decrypt data with a key derived from master key + storage key.
339    ///
340    /// Expects format: version (1 byte) || nonce (12 bytes) || ciphertext
341    async fn decrypt(&self, key: &str, blob: &[u8]) -> Result<Vec<u8>, StorageError> {
342        // Check minimum length
343        if blob.len() < 1 + NONCE_SIZE {
344            return Err(StorageError::DecryptionFailed {
345                reason: "Blob too short".to_string(),
346            });
347        }
348
349        // Check version
350        let version = blob[0];
351        if version != BLOB_VERSION {
352            return Err(StorageError::DecryptionFailed {
353                reason: format!("Unknown blob version: {version}"),
354            });
355        }
356
357        // Derive per-key encryption key
358        let encryption_key = self.derive_encryption_key(key).await?;
359
360        // Extract nonce and ciphertext
361        let nonce_bytes = &blob[1..1 + NONCE_SIZE];
362        let ciphertext = &blob[1 + NONCE_SIZE..];
363
364        let mut nonce = [0u8; NONCE_SIZE];
365        nonce.copy_from_slice(nonce_bytes);
366
367        // Decrypt with ChaCha20-Poly1305
368        self.crypto
369            .chacha20_decrypt(ciphertext, &encryption_key, &nonce)
370            .await
371            .map_err(|e| StorageError::DecryptionFailed {
372                reason: e.to_string(),
373            })
374    }
375
376    /// Check if a blob is encrypted (has our version header).
377    ///
378    /// Used for detecting unencrypted legacy data.
379    pub fn is_encrypted(blob: &[u8]) -> bool {
380        !blob.is_empty() && blob[0] == BLOB_VERSION
381    }
382
383    /// Get reference to inner storage (for debugging/testing)
384    pub fn inner(&self) -> &S {
385        &self.inner
386    }
387
388    /// Get reference to secure storage (for key management)
389    pub fn secure(&self) -> &Arc<Sec> {
390        &self.secure
391    }
392}
393
394#[async_trait]
395impl<S, C, Sec> StorageCoreEffects for EncryptedStorage<S, C, Sec>
396where
397    S: StorageCoreEffects + StorageExtendedEffects + Send + Sync,
398    C: CryptoEffects + Send + Sync,
399    Sec: SecureStorageEffects + Send + Sync,
400{
401    async fn store(&self, key: &str, value: Vec<u8>) -> Result<(), StorageError> {
402        if !self.config.enabled {
403            return self.inner.store(key, value).await;
404        }
405        let storage_key = self.storage_key(key).await?;
406        let encrypted = self.encrypt(key, &value).await?;
407        self.inner.store(&storage_key, encrypted).await
408    }
409
410    async fn retrieve(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
411        if !self.config.enabled {
412            return self.inner.retrieve(key).await;
413        }
414        let storage_key = self.storage_key(key).await?;
415        match self.inner.retrieve(&storage_key).await? {
416            Some(blob) => {
417                if Self::is_encrypted(&blob) {
418                    let decrypted = self.decrypt(key, &blob).await?;
419                    return Ok(Some(decrypted));
420                }
421
422                // Plaintext blobs are not allowed when encryption is enabled
423                Err(StorageError::DecryptionFailed {
424                    reason: "Plaintext blob detected while encryption is enabled".to_string(),
425                })
426            }
427            None => Ok(None),
428        }
429    }
430
431    async fn remove(&self, key: &str) -> Result<bool, StorageError> {
432        if !self.config.enabled {
433            return self.inner.remove(key).await;
434        }
435        let storage_key = self.storage_key(key).await?;
436        self.inner.remove(&storage_key).await
437    }
438
439    async fn list_keys(&self, prefix: Option<&str>) -> Result<Vec<String>, StorageError> {
440        if !self.config.enabled {
441            return self.inner.list_keys(prefix).await;
442        }
443        // If using opaque names, we can't filter by prefix effectively
444        // Return all keys and let caller filter (or return empty if opaque)
445        if self.config.opaque_names {
446            // With opaque names, we can't meaningfully list by prefix
447            // The caller would need to maintain their own index
448            return self.inner.list_keys(None).await;
449        }
450        self.inner.list_keys(prefix).await
451    }
452}
453
454#[async_trait]
455impl<S, C, Sec> StorageExtendedEffects for EncryptedStorage<S, C, Sec>
456where
457    S: StorageCoreEffects + StorageExtendedEffects + Send + Sync,
458    C: CryptoEffects + Send + Sync,
459    Sec: SecureStorageEffects + Send + Sync,
460{
461    async fn exists(&self, key: &str) -> Result<bool, StorageError> {
462        if !self.config.enabled {
463            return self.inner.exists(key).await;
464        }
465        let storage_key = self.storage_key(key).await?;
466        self.inner.exists(&storage_key).await
467    }
468
469    async fn store_batch(&self, pairs: HashMap<String, Vec<u8>>) -> Result<(), StorageError> {
470        if !self.config.enabled {
471            return self.inner.store_batch(pairs).await;
472        }
473        // Encrypt each value
474        let mut encrypted_pairs = HashMap::with_capacity(pairs.len());
475        for (key, value) in pairs {
476            let storage_key = self.storage_key(&key).await?;
477            let encrypted = self.encrypt(&key, &value).await?;
478            encrypted_pairs.insert(storage_key, encrypted);
479        }
480        self.inner.store_batch(encrypted_pairs).await
481    }
482
483    async fn retrieve_batch(
484        &self,
485        keys: &[String],
486    ) -> Result<HashMap<String, Vec<u8>>, StorageError> {
487        if !self.config.enabled {
488            return self.inner.retrieve_batch(keys).await;
489        }
490        // Map semantic keys to storage keys
491        let mut storage_keys = Vec::with_capacity(keys.len());
492        let mut key_map = HashMap::with_capacity(keys.len());
493        for key in keys {
494            let storage_key = self.storage_key(key).await?;
495            key_map.insert(storage_key.clone(), key.clone());
496            storage_keys.push(storage_key);
497        }
498
499        // Retrieve encrypted blobs
500        let encrypted = self.inner.retrieve_batch(&storage_keys).await?;
501
502        // Decrypt each value
503        let mut decrypted = HashMap::with_capacity(encrypted.len());
504        for (storage_key, blob) in encrypted {
505            if let Some(semantic_key) = key_map.get(&storage_key) {
506                let value = self.decrypt(semantic_key, &blob).await?;
507                decrypted.insert(semantic_key.clone(), value);
508            }
509        }
510
511        Ok(decrypted)
512    }
513
514    async fn clear_all(&self) -> Result<(), StorageError> {
515        if !self.config.enabled {
516            return self.inner.clear_all().await;
517        }
518        self.inner.clear_all().await
519    }
520
521    async fn stats(&self) -> Result<StorageStats, StorageError> {
522        if !self.config.enabled {
523            return self.inner.stats().await;
524        }
525        let mut stats = self.inner.stats().await?;
526        // Update backend type to indicate encryption
527        stats.backend_type = format!("encrypted({})", stats.backend_type);
528        Ok(stats)
529    }
530}
531
532// Debug impl that doesn't expose the master key
533impl<S, C, Sec> std::fmt::Debug for EncryptedStorage<S, C, Sec>
534where
535    S: StorageCoreEffects + StorageExtendedEffects + std::fmt::Debug,
536    C: CryptoEffects,
537    Sec: SecureStorageEffects,
538{
539    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540        f.debug_struct("EncryptedStorage")
541            .field("inner", &self.inner)
542            .field("config", &self.config)
543            .field("master_key", &"[REDACTED]")
544            .finish()
545    }
546}
547
548#[cfg(test)]
549mod tests {
550    use super::*;
551    use aura_core::effects::storage::StorageStats;
552    use aura_core::effects::{
553        CryptoCoreEffects, CryptoExtendedEffects, StorageCoreEffects, StorageExtendedEffects,
554    };
555    use aura_core::time::PhysicalTime;
556    use tokio::sync::RwLock;
557
558    // Simple mock implementations for testing
559
560    #[derive(Default)]
561    struct MockStorage {
562        data: RwLock<HashMap<String, Vec<u8>>>,
563    }
564
565    impl MockStorage {
566        fn new() -> Self {
567            Self::default()
568        }
569    }
570
571    #[async_trait]
572    impl StorageCoreEffects for MockStorage {
573        async fn store(&self, key: &str, value: Vec<u8>) -> Result<(), StorageError> {
574            self.data.write().await.insert(key.to_string(), value);
575            Ok(())
576        }
577
578        async fn retrieve(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
579            Ok(self.data.read().await.get(key).cloned())
580        }
581
582        async fn remove(&self, key: &str) -> Result<bool, StorageError> {
583            Ok(self.data.write().await.remove(key).is_some())
584        }
585
586        async fn list_keys(&self, prefix: Option<&str>) -> Result<Vec<String>, StorageError> {
587            let data = self.data.read().await;
588            let keys: Vec<String> = match prefix {
589                Some(p) => data.keys().filter(|k| k.starts_with(p)).cloned().collect(),
590                None => data.keys().cloned().collect(),
591            };
592            Ok(keys)
593        }
594    }
595
596    #[async_trait]
597    impl StorageExtendedEffects for MockStorage {
598        async fn exists(&self, key: &str) -> Result<bool, StorageError> {
599            Ok(self.data.read().await.contains_key(key))
600        }
601
602        async fn store_batch(&self, pairs: HashMap<String, Vec<u8>>) -> Result<(), StorageError> {
603            self.data.write().await.extend(pairs);
604            Ok(())
605        }
606
607        async fn retrieve_batch(
608            &self,
609            keys: &[String],
610        ) -> Result<HashMap<String, Vec<u8>>, StorageError> {
611            let data = self.data.read().await;
612            Ok(keys
613                .iter()
614                .filter_map(|k| data.get(k).map(|v| (k.clone(), v.clone())))
615                .collect())
616        }
617
618        async fn clear_all(&self) -> Result<(), StorageError> {
619            self.data.write().await.clear();
620            Ok(())
621        }
622
623        async fn stats(&self) -> Result<StorageStats, StorageError> {
624            let data = self.data.read().await;
625            Ok(StorageStats {
626                key_count: data.len() as u64,
627                total_size: data.values().map(|v| v.len() as u64).sum(),
628                available_space: None,
629                backend_type: "mock".to_string(),
630            })
631        }
632    }
633
634    struct MockCrypto;
635
636    #[async_trait]
637    impl aura_core::effects::random::RandomCoreEffects for MockCrypto {
638        async fn random_bytes(&self, len: usize) -> Vec<u8> {
639            vec![42u8; len] // Deterministic for testing
640        }
641
642        async fn random_bytes_32(&self) -> [u8; 32] {
643            [42u8; 32]
644        }
645
646        async fn random_u64(&self) -> u64 {
647            42
648        }
649    }
650
651    #[async_trait]
652    impl CryptoCoreEffects for MockCrypto {
653        async fn kdf_derive(
654            &self,
655            ikm: &[u8],
656            salt: &[u8],
657            info: &[u8],
658            len: u32,
659        ) -> Result<Vec<u8>, aura_core::AuraError> {
660            let mut result = vec![0u8; len as usize];
661            for (i, byte) in ikm.iter().chain(salt.iter()).chain(info.iter()).enumerate() {
662                let idx = i % (len as usize);
663                result[idx] ^= byte;
664            }
665            Ok(result)
666        }
667
668        async fn derive_key(
669            &self,
670            master_key: &[u8],
671            context: &aura_core::effects::crypto::KeyDerivationContext,
672        ) -> Result<Vec<u8>, aura_core::AuraError> {
673            self.kdf_derive(master_key, context.context.as_bytes(), b"derive", 32)
674                .await
675        }
676
677        async fn ed25519_generate_keypair(
678            &self,
679        ) -> Result<(Vec<u8>, Vec<u8>), aura_core::AuraError> {
680            Ok((vec![1u8; 32], vec![2u8; 32]))
681        }
682
683        async fn ed25519_sign(
684            &self,
685            message: &[u8],
686            _private_key: &[u8],
687        ) -> Result<Vec<u8>, aura_core::AuraError> {
688            Ok(message.to_vec())
689        }
690
691        async fn ed25519_verify(
692            &self,
693            _message: &[u8],
694            _signature: &[u8],
695            _public_key: &[u8],
696        ) -> Result<bool, aura_core::AuraError> {
697            Ok(true)
698        }
699
700        fn is_simulated(&self) -> bool {
701            true
702        }
703
704        fn crypto_capabilities(&self) -> Vec<String> {
705            vec!["mock".to_string()]
706        }
707
708        fn constant_time_eq(&self, a: &[u8], b: &[u8]) -> bool {
709            a == b
710        }
711
712        fn secure_zero(&self, data: &mut [u8]) {
713            for byte in data.iter_mut() {
714                *byte = 0;
715            }
716        }
717    }
718
719    #[async_trait]
720    impl CryptoExtendedEffects for MockCrypto {
721        async fn generate_signing_keys(
722            &self,
723            _threshold: u16,
724            _max_signers: u16,
725        ) -> Result<aura_core::effects::crypto::SigningKeyGenResult, aura_core::AuraError> {
726            Ok(aura_core::effects::crypto::SigningKeyGenResult {
727                key_packages: vec![vec![0u8; 32]],
728                public_key_package: vec![0u8; 32],
729                mode: aura_core::crypto::single_signer::SigningMode::SingleSigner,
730            })
731        }
732
733        async fn sign_with_key(
734            &self,
735            message: &[u8],
736            _key_package: &[u8],
737            _mode: aura_core::crypto::single_signer::SigningMode,
738        ) -> Result<Vec<u8>, aura_core::AuraError> {
739            Ok(message.to_vec())
740        }
741
742        async fn verify_signature(
743            &self,
744            _message: &[u8],
745            _signature: &[u8],
746            _public_key_package: &[u8],
747            _mode: aura_core::crypto::single_signer::SigningMode,
748        ) -> Result<bool, aura_core::AuraError> {
749            Ok(true)
750        }
751
752        async fn frost_generate_keys(
753            &self,
754            _threshold: u16,
755            max_signers: u16,
756        ) -> Result<aura_core::effects::crypto::FrostKeyGenResult, aura_core::AuraError> {
757            Ok(aura_core::effects::crypto::FrostKeyGenResult {
758                key_packages: vec![vec![0u8; 32]; max_signers as usize],
759                public_key_package: vec![0u8; 32],
760            })
761        }
762
763        async fn frost_generate_nonces(
764            &self,
765            _key_package: &[u8],
766        ) -> Result<Vec<u8>, aura_core::AuraError> {
767            Ok(vec![0u8; 64])
768        }
769
770        async fn frost_create_signing_package(
771            &self,
772            message: &[u8],
773            _nonces: &[Vec<u8>],
774            participants: &[u16],
775            public_key_package: &[u8],
776        ) -> Result<aura_core::effects::crypto::FrostSigningPackage, aura_core::AuraError> {
777            Ok(aura_core::effects::crypto::FrostSigningPackage {
778                message: message.to_vec(),
779                package: vec![0u8; 64],
780                participants: participants.to_vec(),
781                public_key_package: public_key_package.to_vec(),
782            })
783        }
784
785        async fn frost_sign_share(
786            &self,
787            _signing_package: &aura_core::effects::crypto::FrostSigningPackage,
788            _key_share: &[u8],
789            _nonces: &[u8],
790        ) -> Result<Vec<u8>, aura_core::AuraError> {
791            Ok(vec![0u8; 64])
792        }
793
794        async fn frost_aggregate_signatures(
795            &self,
796            _signing_package: &aura_core::effects::crypto::FrostSigningPackage,
797            _signature_shares: &[Vec<u8>],
798        ) -> Result<Vec<u8>, aura_core::AuraError> {
799            Ok(vec![0u8; 64])
800        }
801
802        async fn frost_verify(
803            &self,
804            _message: &[u8],
805            _signature: &[u8],
806            _group_public_key: &[u8],
807        ) -> Result<bool, aura_core::AuraError> {
808            Ok(true)
809        }
810
811        async fn ed25519_public_key(
812            &self,
813            _private_key: &[u8],
814        ) -> Result<Vec<u8>, aura_core::AuraError> {
815            Ok(vec![2u8; 32])
816        }
817
818        async fn chacha20_encrypt(
819            &self,
820            plaintext: &[u8],
821            key: &[u8; 32],
822            nonce: &[u8; 12],
823        ) -> Result<Vec<u8>, aura_core::AuraError> {
824            // Simple XOR cipher for testing
825            let mut result = plaintext.to_vec();
826            for (i, byte) in result.iter_mut().enumerate() {
827                *byte ^= key[i % 32] ^ nonce[i % 12];
828            }
829            Ok(result)
830        }
831
832        async fn chacha20_decrypt(
833            &self,
834            ciphertext: &[u8],
835            key: &[u8; 32],
836            nonce: &[u8; 12],
837        ) -> Result<Vec<u8>, aura_core::AuraError> {
838            // XOR is symmetric
839            self.chacha20_encrypt(ciphertext, key, nonce).await
840        }
841
842        async fn aes_gcm_encrypt(
843            &self,
844            plaintext: &[u8],
845            key: &[u8; 32],
846            nonce: &[u8; 12],
847        ) -> Result<Vec<u8>, aura_core::AuraError> {
848            self.chacha20_encrypt(plaintext, key, nonce).await
849        }
850
851        async fn aes_gcm_decrypt(
852            &self,
853            ciphertext: &[u8],
854            key: &[u8; 32],
855            nonce: &[u8; 12],
856        ) -> Result<Vec<u8>, aura_core::AuraError> {
857            self.chacha20_decrypt(ciphertext, key, nonce).await
858        }
859
860        async fn frost_rotate_keys(
861            &self,
862            _old_shares: &[Vec<u8>],
863            _old_threshold: u16,
864            new_threshold: u16,
865            new_max_signers: u16,
866        ) -> Result<aura_core::effects::crypto::FrostKeyGenResult, aura_core::AuraError> {
867            self.frost_generate_keys(new_threshold, new_max_signers)
868                .await
869        }
870    }
871
872    struct MockSecureStorage {
873        data: RwLock<HashMap<String, Vec<u8>>>,
874    }
875
876    impl MockSecureStorage {
877        fn new() -> Self {
878            Self {
879                data: RwLock::new(HashMap::new()),
880            }
881        }
882    }
883
884    #[async_trait]
885    impl SecureStorageEffects for MockSecureStorage {
886        async fn secure_store(
887            &self,
888            location: &SecureStorageLocation,
889            data: &[u8],
890            _caps: &[SecureStorageCapability],
891        ) -> Result<(), aura_core::AuraError> {
892            self.data
893                .write()
894                .await
895                .insert(location.full_path(), data.to_vec());
896            Ok(())
897        }
898
899        async fn secure_retrieve(
900            &self,
901            location: &SecureStorageLocation,
902            _caps: &[SecureStorageCapability],
903        ) -> Result<Vec<u8>, aura_core::AuraError> {
904            self.data
905                .read()
906                .await
907                .get(&location.full_path())
908                .cloned()
909                .ok_or_else(|| aura_core::AuraError::storage("not found"))
910        }
911
912        async fn secure_delete(
913            &self,
914            location: &SecureStorageLocation,
915            _caps: &[SecureStorageCapability],
916        ) -> Result<(), aura_core::AuraError> {
917            self.data.write().await.remove(&location.full_path());
918            Ok(())
919        }
920
921        async fn secure_exists(
922            &self,
923            location: &SecureStorageLocation,
924        ) -> Result<bool, aura_core::AuraError> {
925            Ok(self.data.read().await.contains_key(&location.full_path()))
926        }
927
928        async fn secure_list_keys(
929            &self,
930            namespace: &str,
931            _caps: &[SecureStorageCapability],
932        ) -> Result<Vec<String>, aura_core::AuraError> {
933            let prefix = format!("{namespace}/");
934            Ok(self
935                .data
936                .read()
937                .await
938                .keys()
939                .filter(|k| k.starts_with(&prefix))
940                .cloned()
941                .collect())
942        }
943
944        async fn secure_generate_key(
945            &self,
946            location: &SecureStorageLocation,
947            _key_type: &str,
948            caps: &[SecureStorageCapability],
949        ) -> Result<Option<Vec<u8>>, aura_core::AuraError> {
950            let key = vec![0u8; 32];
951            self.secure_store(location, &key, caps).await?;
952            Ok(Some(key))
953        }
954
955        async fn secure_create_time_bound_token(
956            &self,
957            _location: &SecureStorageLocation,
958            _caps: &[SecureStorageCapability],
959            _expires_at: &PhysicalTime,
960        ) -> Result<Vec<u8>, aura_core::AuraError> {
961            Ok(vec![])
962        }
963
964        async fn secure_access_with_token(
965            &self,
966            _token: &[u8],
967            _location: &SecureStorageLocation,
968        ) -> Result<Vec<u8>, aura_core::AuraError> {
969            Ok(vec![])
970        }
971
972        async fn get_device_attestation(&self) -> Result<Vec<u8>, aura_core::AuraError> {
973            Ok(b"mock-attestation".to_vec())
974        }
975
976        async fn is_secure_storage_available(&self) -> bool {
977            true
978        }
979
980        fn get_secure_storage_capabilities(&self) -> Vec<String> {
981            vec!["mock".to_string()]
982        }
983    }
984
985    #[tokio::test]
986    async fn test_encrypted_storage_round_trip() {
987        let storage = MockStorage::new();
988        let crypto = Arc::new(MockCrypto);
989        let secure = Arc::new(MockSecureStorage::new());
990
991        let encrypted =
992            EncryptedStorage::new(storage, crypto, secure, EncryptedStorageConfig::default());
993
994        // Store data
995        let key = "test-key";
996        let value = b"hello world".to_vec();
997        encrypted.store(key, value.clone()).await.unwrap();
998
999        // Retrieve and verify
1000        let retrieved = encrypted.retrieve(key).await.unwrap().unwrap();
1001        assert_eq!(retrieved, value);
1002    }
1003
1004    #[tokio::test]
1005    async fn test_encrypted_storage_master_key_generated() {
1006        let storage = MockStorage::new();
1007        let crypto = Arc::new(MockCrypto);
1008        let secure = Arc::new(MockSecureStorage::new());
1009
1010        // Create encrypted storage and trigger key init via a write.
1011        let encrypted = EncryptedStorage::new(
1012            storage,
1013            crypto,
1014            secure.clone(),
1015            EncryptedStorageConfig::default(),
1016        );
1017        encrypted.store("probe", b"probe".to_vec()).await.unwrap();
1018
1019        // Verify master key was stored in secure storage
1020        let location = SecureStorageLocation::new(MASTER_KEY_NAMESPACE, MASTER_KEY_ID);
1021        assert!(secure.secure_exists(&location).await.unwrap());
1022    }
1023
1024    #[tokio::test]
1025    async fn test_encrypted_storage_master_key_reused() {
1026        let secure = Arc::new(MockSecureStorage::new());
1027
1028        // Pre-store a master key
1029        let location = SecureStorageLocation::new(MASTER_KEY_NAMESPACE, MASTER_KEY_ID);
1030        let key = vec![1u8; 32];
1031        secure
1032            .secure_store(&location, &key, &[SecureStorageCapability::Write])
1033            .await
1034            .unwrap();
1035
1036        // Create encrypted storage (should reuse existing key)
1037        let storage = MockStorage::new();
1038        let crypto = Arc::new(MockCrypto);
1039
1040        let encrypted = EncryptedStorage::new(
1041            storage,
1042            crypto,
1043            secure.clone(),
1044            EncryptedStorageConfig::default(),
1045        );
1046
1047        // Store and retrieve to verify encryption works
1048        encrypted.store("test", b"data".to_vec()).await.unwrap();
1049        let retrieved = encrypted.retrieve("test").await.unwrap().unwrap();
1050        assert_eq!(retrieved, b"data");
1051    }
1052
1053    #[tokio::test]
1054    async fn test_encrypted_storage_blob_format() {
1055        let storage = MockStorage::new();
1056        let crypto = Arc::new(MockCrypto);
1057        let secure = Arc::new(MockSecureStorage::new());
1058
1059        let encrypted =
1060            EncryptedStorage::new(storage, crypto, secure, EncryptedStorageConfig::default());
1061
1062        // Store data
1063        encrypted.store("test", b"data".to_vec()).await.unwrap();
1064
1065        // Check raw blob format
1066        let raw = encrypted.inner().retrieve("test").await.unwrap().unwrap();
1067        assert_eq!(raw[0], BLOB_VERSION); // Version byte
1068        assert!(raw.len() > NONCE_SIZE); // At least version + nonce
1069    }
1070
1071    #[tokio::test]
1072    async fn test_is_encrypted_detection() {
1073        // Valid encrypted blob
1074        let mut blob = vec![BLOB_VERSION];
1075        blob.extend_from_slice(&[0u8; NONCE_SIZE]);
1076        blob.extend_from_slice(b"ciphertext");
1077        assert!(
1078            EncryptedStorage::<MockStorage, MockCrypto, MockSecureStorage>::is_encrypted(&blob)
1079        );
1080
1081        // Plaintext (wrong version)
1082        let plaintext = b"just plain text";
1083        assert!(!EncryptedStorage::<
1084            MockStorage,
1085            MockCrypto,
1086            MockSecureStorage,
1087        >::is_encrypted(plaintext));
1088
1089        // Empty
1090        assert!(!EncryptedStorage::<
1091            MockStorage,
1092            MockCrypto,
1093            MockSecureStorage,
1094        >::is_encrypted(&[]));
1095    }
1096
1097    #[tokio::test]
1098    async fn test_stats_shows_encrypted() {
1099        let storage = MockStorage::new();
1100        let crypto = Arc::new(MockCrypto);
1101        let secure = Arc::new(MockSecureStorage::new());
1102
1103        let encrypted =
1104            EncryptedStorage::new(storage, crypto, secure, EncryptedStorageConfig::default());
1105
1106        let stats = encrypted.stats().await.unwrap();
1107        assert!(stats.backend_type.starts_with("encrypted("));
1108    }
1109
1110    #[tokio::test]
1111    async fn test_plaintext_read_rejected() {
1112        let storage = MockStorage::new();
1113        let crypto = Arc::new(MockCrypto);
1114        let secure = Arc::new(MockSecureStorage::new());
1115
1116        let encrypted =
1117            EncryptedStorage::new(storage, crypto, secure, EncryptedStorageConfig::default());
1118
1119        // Write plaintext directly to inner storage (simulating legacy data)
1120        encrypted
1121            .inner()
1122            .store("legacy", b"legacy-data".to_vec())
1123            .await
1124            .unwrap();
1125
1126        // Reading plaintext should fail when encryption is enabled
1127        let result = encrypted.retrieve("legacy").await;
1128        assert!(
1129            result.is_err(),
1130            "Plaintext blobs should be rejected when encryption is enabled"
1131        );
1132    }
1133
1134    /// Different storage keys must produce different raw ciphertext blobs,
1135    /// even when the plaintext is identical. This validates per-key encryption
1136    /// key derivation — if broken, cross-key ciphertext analysis becomes
1137    /// possible.
1138    #[tokio::test]
1139    async fn test_different_keys_produce_different_ciphertext() {
1140        let storage = MockStorage::new();
1141        let crypto = Arc::new(MockCrypto);
1142        let secure = Arc::new(MockSecureStorage::new());
1143
1144        let encrypted =
1145            EncryptedStorage::new(storage, crypto, secure, EncryptedStorageConfig::default());
1146
1147        let value = b"identical-plaintext".to_vec();
1148        encrypted.store("key-a", value.clone()).await.unwrap();
1149        encrypted.store("key-b", value.clone()).await.unwrap();
1150
1151        let raw_a = encrypted.inner().retrieve("key-a").await.unwrap().unwrap();
1152        let raw_b = encrypted.inner().retrieve("key-b").await.unwrap().unwrap();
1153
1154        // Ciphertext must differ because per-key encryption keys differ
1155        assert_ne!(
1156            raw_a, raw_b,
1157            "Same plaintext under different storage keys must produce different ciphertext"
1158        );
1159    }
1160
1161    /// With encryption disabled, data must pass through to the inner storage
1162    /// unchanged. If broken, either: (a) data is silently unencrypted in
1163    /// production, or (b) test/bring-up environments fail because data is
1164    /// unexpectedly encrypted.
1165    #[tokio::test]
1166    async fn test_disabled_encryption_passes_through_plaintext() {
1167        let storage = MockStorage::new();
1168        let crypto = Arc::new(MockCrypto);
1169        let secure = Arc::new(MockSecureStorage::new());
1170
1171        let config = EncryptedStorageConfig::default().with_encryption_enabled(false);
1172        let encrypted = EncryptedStorage::new(storage, crypto, secure, config);
1173
1174        let value = b"raw-data".to_vec();
1175        encrypted.store("passthrough", value.clone()).await.unwrap();
1176
1177        // Raw storage should contain the exact plaintext
1178        let raw = encrypted
1179            .inner()
1180            .retrieve("passthrough")
1181            .await
1182            .unwrap()
1183            .unwrap();
1184        assert_eq!(
1185            raw, value,
1186            "Disabled encryption must store plaintext unchanged"
1187        );
1188
1189        // Retrieve should also return plaintext
1190        let retrieved = encrypted.retrieve("passthrough").await.unwrap().unwrap();
1191        assert_eq!(retrieved, value);
1192    }
1193}