1use 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
24const NONCE_SIZE: usize = 12;
26
27const BLOB_VERSION: u8 = 0x01;
29
30const MASTER_KEY_NAMESPACE: &str = "aura-encryption";
32const MASTER_KEY_ID: &str = "master-key";
33
34type MasterKeyMaterial = Arc<Zeroizing<[u8; 32]>>;
35
36#[derive(Debug, Clone)]
38pub struct EncryptedStorageConfig {
39 pub enabled: bool,
43 pub opaque_names: bool,
45 pub key_namespace: Option<String>,
47 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 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn with_opaque_names(mut self) -> Self {
70 self.opaque_names = true;
71 self
72 }
73
74 pub fn with_encryption_enabled(mut self, enabled: bool) -> Self {
76 self.enabled = enabled;
77 self
78 }
79
80 pub fn with_key_namespace(mut self, namespace: impl Into<String>) -> Self {
82 self.key_namespace = Some(namespace.into());
83 self
84 }
85
86 pub fn with_key_id(mut self, id: impl Into<String>) -> Self {
88 self.key_id = Some(id.into());
89 self
90 }
91}
92
93pub struct EncryptedStorage<S, C, Sec>
119where
120 S: StorageCoreEffects + StorageExtendedEffects,
121 C: CryptoEffects,
122 Sec: SecureStorageEffects,
123{
124 inner: S,
126 crypto: Arc<C>,
128 secure: Arc<Sec>,
130 master_key: RwLock<Option<MasterKeyMaterial>>,
132 master_key_init: Mutex<()>,
134 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 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 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 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 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, ®enerated, &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 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 Ok(hex::encode(&derived))
264 }
265
266 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 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 async fn encrypt(&self, key: &str, data: &[u8]) -> Result<Vec<u8>, StorageError> {
306 let encryption_key = self.derive_encryption_key(key).await?;
308
309 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 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 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 async fn decrypt(&self, key: &str, blob: &[u8]) -> Result<Vec<u8>, StorageError> {
342 if blob.len() < 1 + NONCE_SIZE {
344 return Err(StorageError::DecryptionFailed {
345 reason: "Blob too short".to_string(),
346 });
347 }
348
349 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 let encryption_key = self.derive_encryption_key(key).await?;
359
360 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 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 pub fn is_encrypted(blob: &[u8]) -> bool {
380 !blob.is_empty() && blob[0] == BLOB_VERSION
381 }
382
383 pub fn inner(&self) -> &S {
385 &self.inner
386 }
387
388 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 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 self.config.opaque_names {
446 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 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 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 let encrypted = self.inner.retrieve_batch(&storage_keys).await?;
501
502 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 stats.backend_type = format!("encrypted({})", stats.backend_type);
528 Ok(stats)
529 }
530}
531
532impl<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 #[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] }
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 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 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 let key = "test-key";
996 let value = b"hello world".to_vec();
997 encrypted.store(key, value.clone()).await.unwrap();
998
999 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 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 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 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 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 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 encrypted.store("test", b"data".to_vec()).await.unwrap();
1064
1065 let raw = encrypted.inner().retrieve("test").await.unwrap().unwrap();
1067 assert_eq!(raw[0], BLOB_VERSION); assert!(raw.len() > NONCE_SIZE); }
1070
1071 #[tokio::test]
1072 async fn test_is_encrypted_detection() {
1073 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 let plaintext = b"just plain text";
1083 assert!(!EncryptedStorage::<
1084 MockStorage,
1085 MockCrypto,
1086 MockSecureStorage,
1087 >::is_encrypted(plaintext));
1088
1089 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 encrypted
1121 .inner()
1122 .store("legacy", b"legacy-data".to_vec())
1123 .await
1124 .unwrap();
1125
1126 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 #[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 assert_ne!(
1156 raw_a, raw_b,
1157 "Same plaintext under different storage keys must produce different ciphertext"
1158 );
1159 }
1160
1161 #[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 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 let retrieved = encrypted.retrieve("passthrough").await.unwrap().unwrap();
1191 assert_eq!(retrieved, value);
1192 }
1193}