supabase/session/
encryption.rs

1//! Session encryption utilities
2//!
3//! This module provides secure session encryption and decryption using AES-GCM
4
5#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
6use aes_gcm::{
7    aead::{Aead, KeyInit},
8    Aes256Gcm, Key, Nonce,
9};
10
11#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
12use argon2::Argon2;
13
14#[cfg(feature = "session-encryption")]
15use crate::error::{Error, Result};
16#[cfg(feature = "session-encryption")]
17use crate::session::SessionData;
18#[cfg(feature = "session-encryption")]
19use rand::RngCore;
20#[cfg(feature = "session-encryption")]
21use serde::{Deserialize, Serialize};
22
23/// Encrypted session container
24#[cfg(feature = "session-encryption")]
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct EncryptedSessionData {
27    /// Encrypted session data
28    pub encrypted_data: Vec<u8>,
29    /// Nonce used for encryption
30    pub nonce: Vec<u8>,
31    /// Salt used for key derivation (if applicable)
32    pub salt: Option<Vec<u8>>,
33    /// Encryption metadata
34    pub metadata: EncryptionMetadata,
35}
36
37/// Encryption metadata
38#[cfg(feature = "session-encryption")]
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct EncryptionMetadata {
41    /// Encryption algorithm used
42    pub algorithm: String,
43    /// Key derivation function used
44    pub kdf: Option<String>,
45    /// Version for forward compatibility
46    pub version: u32,
47}
48
49/// Session encryptor for secure storage
50#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
51pub struct SessionEncryptor {
52    cipher: Aes256Gcm,
53}
54
55#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
56impl SessionEncryptor {
57    /// Create a new session encryptor with the given key
58    pub fn new(key: [u8; 32]) -> Result<Self> {
59        let key = Key::<Aes256Gcm>::from_slice(&key);
60        let cipher = Aes256Gcm::new(key);
61        Ok(Self { cipher })
62    }
63
64    /// Create a new session encryptor with password-derived key
65    pub fn from_password(password: &str, salt: Option<&[u8]>) -> Result<(Self, Vec<u8>)> {
66        let salt = match salt {
67            Some(s) => s.to_vec(),
68            None => {
69                let mut salt = vec![0u8; 16];
70                rand::thread_rng().fill_bytes(&mut salt);
71                salt
72            }
73        };
74
75        let argon2 = Argon2::default();
76        let mut key = [0u8; 32];
77
78        argon2
79            .hash_password_into(password.as_bytes(), &salt, &mut key)
80            .map_err(|e| Error::crypto(format!("Failed to derive key from password: {}", e)))?;
81
82        let encryptor = Self::new(key)?;
83        Ok((encryptor, salt))
84    }
85
86    /// Encrypt a session
87    pub fn encrypt_session(&self, session_data: &SessionData) -> Result<SessionData> {
88        // Serialize the original session
89        let serialized = serde_json::to_vec(session_data).map_err(|e| {
90            Error::crypto(format!("Failed to serialize session for encryption: {}", e))
91        })?;
92
93        // Generate random nonce
94        let nonce_bytes = rand::random::<[u8; 12]>();
95        let nonce = Nonce::from_slice(&nonce_bytes);
96
97        // Encrypt the data
98        let encrypted_data = self
99            .cipher
100            .encrypt(nonce, serialized.as_ref())
101            .map_err(|e| Error::crypto(format!("Failed to encrypt session: {}", e)))?;
102
103        // Create encrypted container
104        let encrypted_container = EncryptedSessionData {
105            encrypted_data,
106            nonce: nonce.to_vec(),
107            salt: None,
108            metadata: EncryptionMetadata {
109                algorithm: "AES-256-GCM".to_string(),
110                kdf: None,
111                version: 1,
112            },
113        };
114
115        // Create a new SessionData with encrypted content
116        let mut encrypted_session = session_data.clone();
117        encrypted_session.platform_data.insert(
118            "encrypted_payload".to_string(),
119            serde_json::to_value(&encrypted_container).map_err(|e| {
120                Error::crypto(format!("Failed to serialize encrypted container: {}", e))
121            })?,
122        );
123
124        // Clear sensitive data from the session
125        encrypted_session.session.access_token = "***ENCRYPTED***".to_string();
126        encrypted_session.session.refresh_token = "***ENCRYPTED***".to_string();
127        encrypted_session.session.user.email = None;
128        encrypted_session.session.user.phone = None;
129
130        Ok(encrypted_session)
131    }
132
133    /// Decrypt a session
134    pub fn decrypt_session(&self, encrypted_session: &SessionData) -> Result<SessionData> {
135        // Extract encrypted container from platform data
136        let encrypted_container_value = encrypted_session
137            .platform_data
138            .get("encrypted_payload")
139            .ok_or_else(|| Error::crypto("No encrypted payload found in session"))?;
140
141        let encrypted_container: EncryptedSessionData =
142            serde_json::from_value(encrypted_container_value.clone()).map_err(|e| {
143                Error::crypto(format!("Failed to deserialize encrypted container: {}", e))
144            })?;
145
146        // Verify encryption metadata
147        if encrypted_container.metadata.algorithm != "AES-256-GCM" {
148            return Err(Error::crypto(format!(
149                "Unsupported encryption algorithm: {}",
150                encrypted_container.metadata.algorithm
151            )));
152        }
153
154        if encrypted_container.metadata.version != 1 {
155            return Err(Error::crypto(format!(
156                "Unsupported encryption version: {}",
157                encrypted_container.metadata.version
158            )));
159        }
160
161        // Decrypt the data
162        let nonce = Nonce::from_slice(&encrypted_container.nonce);
163        let decrypted_data = self
164            .cipher
165            .decrypt(nonce, encrypted_container.encrypted_data.as_ref())
166            .map_err(|e| Error::crypto(format!("Failed to decrypt session: {}", e)))?;
167
168        // Deserialize the original session
169        let original_session: SessionData =
170            serde_json::from_slice(&decrypted_data).map_err(|e| {
171                Error::crypto(format!("Failed to deserialize decrypted session: {}", e))
172            })?;
173
174        Ok(original_session)
175    }
176
177    /// Generate a secure random encryption key
178    pub fn generate_key() -> [u8; 32] {
179        rand::random()
180    }
181}
182
183/// WASM-compatible session encryptor (simplified implementation)
184#[cfg(all(feature = "session-encryption", target_arch = "wasm32"))]
185#[derive(Debug)]
186pub struct SessionEncryptor {
187    key: [u8; 32],
188}
189
190#[cfg(all(feature = "session-encryption", target_arch = "wasm32"))]
191impl SessionEncryptor {
192    /// Create a new session encryptor with the given key
193    pub fn new(key: [u8; 32]) -> Result<Self> {
194        Ok(Self { key })
195    }
196
197    /// Encrypt a session (WASM implementation using Web Crypto API)
198    pub fn encrypt_session(&self, session_data: &SessionData) -> Result<SessionData> {
199        // For WASM, we'll implement a simplified version
200        // In a real implementation, you would use the Web Crypto API
201
202        // Serialize the original session
203        let serialized = serde_json::to_vec(session_data).map_err(|e| {
204            Error::crypto(format!("Failed to serialize session for encryption: {}", e))
205        })?;
206
207        // Simple XOR encryption (NOT secure - for demo only)
208        let mut encrypted_data = Vec::with_capacity(serialized.len());
209        for (i, byte) in serialized.iter().enumerate() {
210            encrypted_data.push(byte ^ self.key[i % 32]);
211        }
212
213        // Create encrypted container
214        let encrypted_container = EncryptedSessionData {
215            encrypted_data,
216            nonce: vec![0; 12], // Dummy nonce for WASM demo
217            salt: None,
218            metadata: EncryptionMetadata {
219                algorithm: "XOR-DEMO".to_string(),
220                kdf: None,
221                version: 1,
222            },
223        };
224
225        // Create a new SessionData with encrypted content
226        let mut encrypted_session = session_data.clone();
227        encrypted_session.platform_data.insert(
228            "encrypted_payload".to_string(),
229            serde_json::to_value(&encrypted_container).map_err(|e| {
230                Error::crypto(format!("Failed to serialize encrypted container: {}", e))
231            })?,
232        );
233
234        // Clear sensitive data from the session
235        encrypted_session.session.access_token = "***ENCRYPTED***".to_string();
236        encrypted_session.session.refresh_token = "***ENCRYPTED***".to_string();
237        encrypted_session.session.user.email = None;
238        encrypted_session.session.user.phone = None;
239
240        Ok(encrypted_session)
241    }
242
243    /// Decrypt a session (WASM implementation)
244    pub fn decrypt_session(&self, encrypted_session: &SessionData) -> Result<SessionData> {
245        // Extract encrypted container from platform data
246        let encrypted_container_value = encrypted_session
247            .platform_data
248            .get("encrypted_payload")
249            .ok_or_else(|| Error::crypto("No encrypted payload found in session"))?;
250
251        let encrypted_container: EncryptedSessionData =
252            serde_json::from_value(encrypted_container_value.clone()).map_err(|e| {
253                Error::crypto(format!("Failed to deserialize encrypted container: {}", e))
254            })?;
255
256        // Verify encryption metadata
257        if encrypted_container.metadata.algorithm != "XOR-DEMO" {
258            return Err(Error::crypto(format!(
259                "Unsupported encryption algorithm: {}",
260                encrypted_container.metadata.algorithm
261            )));
262        }
263
264        // Decrypt the data (reverse XOR)
265        let mut decrypted_data = Vec::with_capacity(encrypted_container.encrypted_data.len());
266        for (i, byte) in encrypted_container.encrypted_data.iter().enumerate() {
267            decrypted_data.push(byte ^ self.key[i % 32]);
268        }
269
270        // Deserialize the original session
271        let original_session: SessionData =
272            serde_json::from_slice(&decrypted_data).map_err(|e| {
273                Error::crypto(format!("Failed to deserialize decrypted session: {}", e))
274            })?;
275
276        Ok(original_session)
277    }
278
279    /// Generate a secure random encryption key (WASM version)
280    pub fn generate_key() -> [u8; 32] {
281        // Use Web Crypto API in real implementation
282        // For demo, use a simple approach
283        [0; 32] // This is NOT secure - use proper random generation
284    }
285}
286
287/// Key management utilities
288#[cfg(feature = "session-encryption")]
289pub struct KeyManager;
290
291#[cfg(feature = "session-encryption")]
292impl KeyManager {
293    /// Generate a new encryption key
294    pub fn generate_encryption_key() -> [u8; 32] {
295        SessionEncryptor::generate_key()
296    }
297
298    /// Derive key from password
299    #[cfg(not(target_arch = "wasm32"))]
300    pub fn derive_key_from_password(
301        password: &str,
302        salt: Option<&[u8]>,
303    ) -> Result<([u8; 32], Vec<u8>)> {
304        use rand::RngCore;
305
306        let salt = salt.map(|s| s.to_vec()).unwrap_or_else(|| {
307            let mut salt = vec![0u8; 16];
308            rand::thread_rng().fill_bytes(&mut salt);
309            salt
310        });
311
312        // Use a simple key derivation for demo purposes
313        // In production, use proper PBKDF2/Argon2
314        let mut key = [0u8; 32];
315        let combined = format!("{}{}", password, hex::encode(&salt));
316        let hash = combined.bytes().cycle().take(32).collect::<Vec<_>>();
317        key.copy_from_slice(&hash);
318
319        Ok((key, salt))
320    }
321
322    /// Store encryption key securely (using OS keyring)
323    #[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
324    pub fn store_key_securely(service: &str, username: &str, key: &[u8; 32]) -> Result<()> {
325        let entry = keyring::Entry::new(service, username)
326            .map_err(|e| Error::crypto(format!("Failed to create keyring entry: {}", e)))?;
327
328        let key_hex = hex::encode(key);
329        entry
330            .set_password(&key_hex)
331            .map_err(|e| Error::crypto(format!("Failed to store key in keyring: {}", e)))?;
332
333        Ok(())
334    }
335
336    /// Retrieve encryption key from secure storage
337    #[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
338    pub fn retrieve_key_securely(service: &str, username: &str) -> Result<[u8; 32]> {
339        let entry = keyring::Entry::new(service, username)
340            .map_err(|e| Error::crypto(format!("Failed to create keyring entry: {}", e)))?;
341
342        let key_hex = entry
343            .get_password()
344            .map_err(|e| Error::crypto(format!("Failed to retrieve key from keyring: {}", e)))?;
345
346        let key_bytes = hex::decode(&key_hex)
347            .map_err(|e| Error::crypto(format!("Failed to decode key from hex: {}", e)))?;
348
349        if key_bytes.len() != 32 {
350            return Err(Error::crypto("Invalid key length"));
351        }
352
353        let mut key = [0u8; 32];
354        key.copy_from_slice(&key_bytes);
355        Ok(key)
356    }
357}