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}