Skip to main content

aura_core/effects/
secure.rs

1//! Secure Storage Effects Trait Definitions
2//!
3//! This module defines trait interfaces for secure storage operations that require
4//! hardware-backed security features like secure enclaves, TPMs, or hardware security modules.
5//! These operations provide stronger security guarantees than regular storage.
6//!
7//! # Effect Classification
8//!
9//! - **Category**: Infrastructure Effect
10//! - **Implementation**: `aura-effects` (Layer 3)
11//! - **Usage**: Secure enclave/TPM/HSM integration for cryptographic material storage
12//!
13//! This is an infrastructure effect providing hardware security module interfaces
14//! with no Aura-specific semantics. Implementations should interface with platform
15//! secure storage APIs (Intel SGX, ARM TrustZone, Apple Secure Enclave, TPM) and
16//! provide software fallback for testing environments.
17//!
18//! ## Security Model
19//!
20//! Secure storage provides:
21//! - Hardware-backed encryption and integrity protection
22//! - Access control enforced at the hardware level
23//! - Protection against physical attacks and privilege escalation
24//! - Key derivation tied to device identity/attestation
25//!
26//! ## Use Cases
27//!
28//! - FROST nonce storage (prevent reuse attacks)
29//! - Cryptographic signing shares (threshold cryptography)
30//! - Device attestation certificates
31//! - Critical configuration data
32
33use crate::time::PhysicalTime;
34use crate::types::identifiers::{ChannelId, ContextId};
35use crate::{AuraError, Hash32};
36use async_trait::async_trait;
37use serde::{Deserialize, Serialize};
38
39/// Secure storage operation error
40pub type SecureStorageError = AuraError;
41
42/// Location within secure storage
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub struct SecureStorageLocation {
45    /// Namespace for organizing secure data
46    pub namespace: String,
47    /// Unique key within the namespace
48    pub key: String,
49    /// Optional sub-key for hierarchical organization
50    pub sub_key: Option<String>,
51}
52
53impl SecureStorageLocation {
54    /// Create a new secure storage location
55    pub fn new(namespace: impl Into<String>, key: impl Into<String>) -> Self {
56        Self {
57            namespace: namespace.into(),
58            key: key.into(),
59            sub_key: None,
60        }
61    }
62
63    /// Create with a sub-key for hierarchical organization
64    #[must_use]
65    pub fn with_sub_key(
66        namespace: impl Into<String>,
67        key: impl Into<String>,
68        sub_key: impl Into<String>,
69    ) -> Self {
70        Self {
71            namespace: namespace.into(),
72            key: key.into(),
73            sub_key: Some(sub_key.into()),
74        }
75    }
76
77    /// Get the full key path as a string
78    pub fn full_path(&self) -> String {
79        if let Some(sub_key) = &self.sub_key {
80            format!("{}/{}/{}", self.namespace, self.key, sub_key)
81        } else {
82            format!("{}/{}", self.namespace, self.key)
83        }
84    }
85
86    /// Create a location for storing a guardian's FROST share.
87    ///
88    /// Guardian shares are stored by:
89    /// - namespace: "guardian_shares"
90    /// - key: account authority being protected
91    /// - sub_key: guardian authority holding the share
92    ///
93    /// # Security Note
94    ///
95    /// Guardian shares MUST be encrypted with the guardian's public key before
96    /// storage. The share data stored here is the encrypted share bytes, not
97    /// the raw FROST SigningShare.
98    pub fn guardian_share(
99        account_authority: &crate::AuthorityId,
100        guardian_authority: &crate::AuthorityId,
101    ) -> Self {
102        Self::with_sub_key(
103            "guardian_shares",
104            account_authority.to_string(),
105            guardian_authority.to_string(),
106        )
107    }
108
109    /// Create a location for storing authority FROST keys.
110    ///
111    /// Authority keys are stored by:
112    /// - namespace: "authority_keys"
113    /// - key: authority ID
114    /// - sub_key: epoch number
115    ///
116    /// # Security Note
117    ///
118    /// This stores the SigningShare and PublicKeyPackage for the authority.
119    /// The SigningShare is the secret key material that must never leave
120    /// secure storage unencrypted.
121    pub fn authority_keys(authority: &crate::AuthorityId, epoch: u64) -> Self {
122        Self::with_sub_key("authority_keys", authority.to_string(), epoch.to_string())
123    }
124}
125
126/// Capabilities required for secure storage operations
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub enum SecureStorageCapability {
129    /// Read access to secure storage
130    Read,
131    /// Write access to secure storage
132    Write,
133    /// Delete access to secure storage
134    Delete,
135    /// Ability to list keys in secure storage
136    List,
137    /// Access to device attestation for key binding
138    DeviceAttestation,
139    /// Time-bound access control (uses unified time system)
140    TimeBound { expires_at: PhysicalTime },
141}
142
143impl SecureStorageCapability {
144    /// Create a time-bound capability with millisecond expiration (backward compatibility)
145    pub fn time_bound_ms(expires_at_ms: u64) -> Self {
146        Self::TimeBound {
147            expires_at: PhysicalTime {
148                ts_ms: expires_at_ms,
149                uncertainty: None,
150            },
151        }
152    }
153
154    /// Get expiration in milliseconds if this is a TimeBound capability
155    pub fn expires_at_ms(&self) -> Option<u64> {
156        match self {
157            Self::TimeBound { expires_at } => Some(expires_at.ts_ms),
158            _ => None,
159        }
160    }
161}
162
163/// Secure storage effects interface
164///
165/// This trait defines operations for storing cryptographic material and sensitive data
166/// in hardware-backed secure storage. Unlike regular storage, secure storage provides:
167/// - Hardware-level encryption and integrity protection
168/// - Access control enforced by secure hardware
169/// - Protection against physical and privilege escalation attacks
170/// - Optional time-bound access controls
171///
172/// # Implementation Notes
173///
174/// - Production: Interface with platform secure enclaves (Intel SGX, ARM TrustZone, Apple Secure Enclave, etc.)
175/// - Testing: In-memory mock with simulated security properties
176/// - Simulation: Deterministic mock for reproducible testing
177///
178/// # Stability: EXPERIMENTAL
179/// This API is under development and may change in future versions.
180#[async_trait]
181pub trait SecureStorageEffects: Send + Sync {
182    /// Store data securely with optional capabilities
183    ///
184    /// Stores sensitive data in hardware-backed secure storage. The data is encrypted
185    /// and bound to the device identity to prevent unauthorized access.
186    ///
187    /// # Parameters
188    /// - `location`: Where to store the data within secure storage
189    /// - `data`: Sensitive data to store (will be encrypted)
190    /// - `capabilities`: Required capabilities for accessing this data
191    ///
192    /// # Security Properties
193    /// - Data is encrypted using hardware-derived keys
194    /// - Access is controlled by the specified capabilities
195    /// - Storage is tamper-resistant and integrity-protected
196    async fn secure_store(
197        &self,
198        location: &SecureStorageLocation,
199        data: &[u8],
200        capabilities: &[SecureStorageCapability],
201    ) -> Result<(), SecureStorageError>;
202
203    /// Retrieve data from secure storage
204    ///
205    /// Retrieves and decrypts data from secure storage, verifying that the caller
206    /// has the required capabilities.
207    ///
208    /// # Parameters
209    /// - `location`: Where to retrieve the data from
210    /// - `required_capabilities`: Capabilities needed to access this data
211    ///
212    /// # Returns
213    /// The decrypted data if access is authorized and data exists
214    async fn secure_retrieve(
215        &self,
216        location: &SecureStorageLocation,
217        required_capabilities: &[SecureStorageCapability],
218    ) -> Result<Vec<u8>, SecureStorageError>;
219
220    /// Delete data from secure storage
221    ///
222    /// Securely deletes data from storage, ensuring it cannot be recovered.
223    ///
224    /// # Parameters
225    /// - `location`: Location of data to delete
226    /// - `required_capabilities`: Capabilities needed to delete this data
227    async fn secure_delete(
228        &self,
229        location: &SecureStorageLocation,
230        required_capabilities: &[SecureStorageCapability],
231    ) -> Result<(), SecureStorageError>;
232
233    /// Check if data exists at the given location
234    ///
235    /// Checks for existence without retrieving the actual data.
236    ///
237    /// # Parameters
238    /// - `location`: Location to check
239    async fn secure_exists(
240        &self,
241        location: &SecureStorageLocation,
242    ) -> Result<bool, SecureStorageError>;
243
244    /// List available keys in a namespace
245    ///
246    /// Lists keys available in the specified namespace, subject to access controls.
247    ///
248    /// # Parameters
249    /// - `namespace`: Namespace to list
250    /// - `required_capabilities`: Capabilities needed to list keys
251    async fn secure_list_keys(
252        &self,
253        namespace: &str,
254        required_capabilities: &[SecureStorageCapability],
255    ) -> Result<Vec<String>, SecureStorageError>;
256
257    /// Generate and store a new cryptographic key
258    ///
259    /// Generates a new cryptographic key within the secure storage, ensuring it
260    /// never leaves the secure environment in plaintext.
261    ///
262    /// # Parameters
263    /// - `location`: Where to store the generated key
264    /// - `key_type`: Type of key to generate (e.g., "ed25519", "frost-share")
265    /// - `capabilities`: Required capabilities for accessing this key
266    ///
267    /// # Returns
268    /// The public key material (if applicable), while private key stays secure
269    async fn secure_generate_key(
270        &self,
271        location: &SecureStorageLocation,
272        key_type: &str,
273        capabilities: &[SecureStorageCapability],
274    ) -> Result<Option<Vec<u8>>, SecureStorageError>;
275
276    /// Create a time-bound access token
277    ///
278    /// Creates an access token that allows operations within a specific time window.
279    /// Used for implementing time-bound nonce access and preventing replay attacks.
280    ///
281    /// # Parameters
282    /// - `location`: Location the token grants access to
283    /// - `capabilities`: Capabilities granted by this token
284    /// - `expires_at`: Timestamp when the token expires (uses unified time system)
285    ///
286    /// # Returns
287    /// An opaque token that can be used for time-bound access
288    async fn secure_create_time_bound_token(
289        &self,
290        location: &SecureStorageLocation,
291        capabilities: &[SecureStorageCapability],
292        expires_at: &PhysicalTime,
293    ) -> Result<Vec<u8>, SecureStorageError>;
294
295    /// Use a time-bound token to access data
296    ///
297    /// Retrieves data using a previously created time-bound token, automatically
298    /// checking expiration and capabilities.
299    ///
300    /// # Parameters
301    /// - `token`: Time-bound access token
302    /// - `location`: Location to access
303    ///
304    /// # Returns
305    /// The data if the token is valid and not expired
306    async fn secure_access_with_token(
307        &self,
308        token: &[u8],
309        location: &SecureStorageLocation,
310    ) -> Result<Vec<u8>, SecureStorageError>;
311
312    /// Get device attestation for secure operations
313    ///
314    /// Provides device attestation that can be used to verify the integrity
315    /// of the secure storage environment and bind operations to a specific device.
316    ///
317    /// # Returns
318    /// Device attestation certificate or proof
319    async fn get_device_attestation(&self) -> Result<Vec<u8>, SecureStorageError>;
320
321    /// Check if secure storage is available
322    ///
323    /// Verifies that the underlying secure storage hardware is available and functional.
324    async fn is_secure_storage_available(&self) -> bool;
325
326    /// Get secure storage capabilities
327    ///
328    /// Returns information about what the secure storage implementation supports.
329    fn get_secure_storage_capabilities(&self) -> Vec<String>;
330}
331
332/// Helper functions for common secure storage operations
333impl SecureStorageLocation {
334    /// Create a location for storing FROST nonces
335    pub fn frost_nonce(session_id: &str, participant_id: u16) -> Self {
336        Self::new("frost_nonces", format!("{session_id}_{participant_id}"))
337    }
338
339    /// Create a location for storing signing shares
340    pub fn signing_share(account_id: &str, epoch: u64, participant_id: u16) -> Self {
341        Self::with_sub_key(
342            "signing_shares",
343            account_id,
344            format!("{epoch}_{participant_id}"),
345        )
346    }
347
348    /// Create a location for device attestation certificates
349    pub fn device_attestation(device_id: &str) -> Self {
350        Self::new("device_attestation", device_id)
351    }
352
353    /// Create a location for storing Biscuit token authority data.
354    ///
355    /// Stores the serialized Biscuit token and root public key for an authority:
356    /// - namespace: "biscuit"
357    /// - key: authority ID
358    /// - sub_key: "token_authority"
359    pub fn biscuit_authority(authority_id: &crate::AuthorityId) -> Self {
360        Self::with_sub_key("biscuit", authority_id.to_string(), "token_authority")
361    }
362
363    /// Create a location for AMP channel bootstrap keys.
364    pub fn amp_bootstrap_key(
365        context: &ContextId,
366        channel: &ChannelId,
367        bootstrap_id: &Hash32,
368    ) -> Self {
369        Self::with_sub_key(
370            "amp_bootstrap_keys",
371            format!("{context}:{channel}"),
372            bootstrap_id.to_hex(),
373        )
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn test_secure_storage_location_creation() {
383        let location = SecureStorageLocation::new("test", "key1");
384        assert_eq!(location.namespace, "test");
385        assert_eq!(location.key, "key1");
386        assert_eq!(location.sub_key, None);
387        assert_eq!(location.full_path(), "test/key1");
388    }
389
390    #[test]
391    fn test_secure_storage_location_with_sub_key() {
392        let location = SecureStorageLocation::with_sub_key("test", "key1", "subkey1");
393        assert_eq!(location.namespace, "test");
394        assert_eq!(location.key, "key1");
395        assert_eq!(location.sub_key, Some("subkey1".to_string()));
396        assert_eq!(location.full_path(), "test/key1/subkey1");
397    }
398
399    #[test]
400    fn test_frost_nonce_location() {
401        let location = SecureStorageLocation::frost_nonce("session123", 1);
402        assert_eq!(location.namespace, "frost_nonces");
403        assert_eq!(location.key, "session123_1");
404        assert_eq!(location.full_path(), "frost_nonces/session123_1");
405    }
406
407    #[test]
408    fn test_signing_share_location() {
409        let location = SecureStorageLocation::signing_share("account456", 42, 2);
410        assert_eq!(location.namespace, "signing_shares");
411        assert_eq!(location.key, "account456");
412        assert_eq!(location.sub_key, Some("42_2".to_string()));
413        assert_eq!(location.full_path(), "signing_shares/account456/42_2");
414    }
415
416    #[test]
417    fn test_device_attestation_location() {
418        let location = SecureStorageLocation::device_attestation("device789");
419        assert_eq!(location.namespace, "device_attestation");
420        assert_eq!(location.key, "device789");
421        assert_eq!(location.full_path(), "device_attestation/device789");
422    }
423
424    #[test]
425    fn test_amp_bootstrap_location() {
426        let context = ContextId::new_from_entropy([1u8; 32]);
427        let channel = ChannelId::from_bytes([2u8; 32]);
428        let bootstrap_id = Hash32::new([3u8; 32]);
429        let location = SecureStorageLocation::amp_bootstrap_key(&context, &channel, &bootstrap_id);
430        assert_eq!(location.namespace, "amp_bootstrap_keys");
431        assert_eq!(location.key, format!("{context}:{channel}"));
432        assert_eq!(location.sub_key, Some(bootstrap_id.to_hex()));
433    }
434}