#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Key, Nonce,
};
#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
use argon2::Argon2;
#[cfg(feature = "session-encryption")]
use crate::error::{Error, Result};
#[cfg(feature = "session-encryption")]
use crate::session::SessionData;
#[cfg(feature = "session-encryption")]
use rand::RngCore;
#[cfg(feature = "session-encryption")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "session-encryption")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedSessionData {
pub encrypted_data: Vec<u8>,
pub nonce: Vec<u8>,
pub salt: Option<Vec<u8>>,
pub metadata: EncryptionMetadata,
}
#[cfg(feature = "session-encryption")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptionMetadata {
pub algorithm: String,
pub kdf: Option<String>,
pub version: u32,
}
#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
pub struct SessionEncryptor {
cipher: Aes256Gcm,
}
#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
impl SessionEncryptor {
pub fn new(key: [u8; 32]) -> Result<Self> {
#[allow(deprecated)]
let key = Key::<Aes256Gcm>::from_slice(&key);
let cipher = Aes256Gcm::new(key);
Ok(Self { cipher })
}
pub fn from_password(password: &str, salt: Option<&[u8]>) -> Result<(Self, Vec<u8>)> {
let salt = match salt {
Some(s) => s.to_vec(),
None => {
let mut salt = vec![0u8; 16];
rand::thread_rng().fill_bytes(&mut salt);
salt
}
};
let argon2 = Argon2::default();
let mut key = [0u8; 32];
argon2
.hash_password_into(password.as_bytes(), &salt, &mut key)
.map_err(|e| Error::crypto(format!("Failed to derive key from password: {}", e)))?;
let encryptor = Self::new(key)?;
Ok((encryptor, salt))
}
pub fn encrypt_session(&self, session_data: &SessionData) -> Result<SessionData> {
let serialized = serde_json::to_vec(session_data).map_err(|e| {
Error::crypto(format!("Failed to serialize session for encryption: {}", e))
})?;
let nonce_bytes = rand::random::<[u8; 12]>();
#[allow(deprecated)]
let nonce = Nonce::from_slice(&nonce_bytes);
#[allow(clippy::needless_borrow)]
let encrypted_data = self
.cipher
.encrypt(&nonce, serialized.as_ref())
.map_err(|e| Error::crypto(format!("Failed to encrypt session: {}", e)))?;
let encrypted_container = EncryptedSessionData {
encrypted_data,
nonce: nonce.to_vec(),
salt: None,
metadata: EncryptionMetadata {
algorithm: "AES-256-GCM".to_string(),
kdf: None,
version: 1,
},
};
let mut encrypted_session = session_data.clone();
encrypted_session.platform_data.insert(
"encrypted_payload".to_string(),
serde_json::to_value(&encrypted_container).map_err(|e| {
Error::crypto(format!("Failed to serialize encrypted container: {}", e))
})?,
);
encrypted_session.session.access_token = "***ENCRYPTED***".to_string();
encrypted_session.session.refresh_token = "***ENCRYPTED***".to_string();
encrypted_session.session.user.email = None;
encrypted_session.session.user.phone = None;
Ok(encrypted_session)
}
pub fn decrypt_session(&self, encrypted_session: &SessionData) -> Result<SessionData> {
let encrypted_container_value = encrypted_session
.platform_data
.get("encrypted_payload")
.ok_or_else(|| Error::crypto("No encrypted payload found in session"))?;
let encrypted_container: EncryptedSessionData =
serde_json::from_value(encrypted_container_value.clone()).map_err(|e| {
Error::crypto(format!("Failed to deserialize encrypted container: {}", e))
})?;
if encrypted_container.metadata.algorithm != "AES-256-GCM" {
return Err(Error::crypto(format!(
"Unsupported encryption algorithm: {}",
encrypted_container.metadata.algorithm
)));
}
if encrypted_container.metadata.version != 1 {
return Err(Error::crypto(format!(
"Unsupported encryption version: {}",
encrypted_container.metadata.version
)));
}
#[allow(deprecated)]
let nonce = Nonce::from_slice(&encrypted_container.nonce);
#[allow(clippy::needless_borrow)]
let decrypted_data = self
.cipher
.decrypt(&nonce, encrypted_container.encrypted_data.as_ref())
.map_err(|e| Error::crypto(format!("Failed to decrypt session: {}", e)))?;
let original_session: SessionData =
serde_json::from_slice(&decrypted_data).map_err(|e| {
Error::crypto(format!("Failed to deserialize decrypted session: {}", e))
})?;
Ok(original_session)
}
pub fn generate_key() -> [u8; 32] {
rand::random()
}
}
#[cfg(all(feature = "session-encryption", target_arch = "wasm32"))]
#[derive(Debug)]
pub struct SessionEncryptor {
key: [u8; 32],
}
#[cfg(all(feature = "session-encryption", target_arch = "wasm32"))]
impl SessionEncryptor {
pub fn new(key: [u8; 32]) -> Result<Self> {
Ok(Self { key })
}
pub fn encrypt_session(&self, session_data: &SessionData) -> Result<SessionData> {
let serialized = serde_json::to_vec(session_data).map_err(|e| {
Error::crypto(format!("Failed to serialize session for encryption: {}", e))
})?;
let mut encrypted_data = Vec::with_capacity(serialized.len());
for (i, byte) in serialized.iter().enumerate() {
encrypted_data.push(byte ^ self.key[i % 32]);
}
let encrypted_container = EncryptedSessionData {
encrypted_data,
nonce: vec![0; 12], salt: None,
metadata: EncryptionMetadata {
algorithm: "XOR-DEMO".to_string(),
kdf: None,
version: 1,
},
};
let mut encrypted_session = session_data.clone();
encrypted_session.platform_data.insert(
"encrypted_payload".to_string(),
serde_json::to_value(&encrypted_container).map_err(|e| {
Error::crypto(format!("Failed to serialize encrypted container: {}", e))
})?,
);
encrypted_session.session.access_token = "***ENCRYPTED***".to_string();
encrypted_session.session.refresh_token = "***ENCRYPTED***".to_string();
encrypted_session.session.user.email = None;
encrypted_session.session.user.phone = None;
Ok(encrypted_session)
}
pub fn decrypt_session(&self, encrypted_session: &SessionData) -> Result<SessionData> {
let encrypted_container_value = encrypted_session
.platform_data
.get("encrypted_payload")
.ok_or_else(|| Error::crypto("No encrypted payload found in session"))?;
let encrypted_container: EncryptedSessionData =
serde_json::from_value(encrypted_container_value.clone()).map_err(|e| {
Error::crypto(format!("Failed to deserialize encrypted container: {}", e))
})?;
if encrypted_container.metadata.algorithm != "XOR-DEMO" {
return Err(Error::crypto(format!(
"Unsupported encryption algorithm: {}",
encrypted_container.metadata.algorithm
)));
}
let mut decrypted_data = Vec::with_capacity(encrypted_container.encrypted_data.len());
for (i, byte) in encrypted_container.encrypted_data.iter().enumerate() {
decrypted_data.push(byte ^ self.key[i % 32]);
}
let original_session: SessionData =
serde_json::from_slice(&decrypted_data).map_err(|e| {
Error::crypto(format!("Failed to deserialize decrypted session: {}", e))
})?;
Ok(original_session)
}
pub fn generate_key() -> [u8; 32] {
[0; 32] }
}
#[cfg(feature = "session-encryption")]
pub struct KeyManager;
#[cfg(feature = "session-encryption")]
impl KeyManager {
pub fn generate_encryption_key() -> [u8; 32] {
SessionEncryptor::generate_key()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn derive_key_from_password(
password: &str,
salt: Option<&[u8]>,
) -> Result<([u8; 32], Vec<u8>)> {
use rand::RngCore;
let salt = salt.map(|s| s.to_vec()).unwrap_or_else(|| {
let mut salt = vec![0u8; 16];
rand::thread_rng().fill_bytes(&mut salt);
salt
});
let mut key = [0u8; 32];
let combined = format!("{}{}", password, hex::encode(&salt));
let hash = combined.bytes().cycle().take(32).collect::<Vec<_>>();
key.copy_from_slice(&hash);
Ok((key, salt))
}
#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
pub fn store_key_securely(service: &str, username: &str, key: &[u8; 32]) -> Result<()> {
let entry = keyring::Entry::new(service, username)
.map_err(|e| Error::crypto(format!("Failed to create keyring entry: {}", e)))?;
let key_hex = hex::encode(key);
entry
.set_password(&key_hex)
.map_err(|e| Error::crypto(format!("Failed to store key in keyring: {}", e)))?;
Ok(())
}
#[cfg(all(feature = "session-encryption", not(target_arch = "wasm32")))]
pub fn retrieve_key_securely(service: &str, username: &str) -> Result<[u8; 32]> {
let entry = keyring::Entry::new(service, username)
.map_err(|e| Error::crypto(format!("Failed to create keyring entry: {}", e)))?;
let key_hex = entry
.get_password()
.map_err(|e| Error::crypto(format!("Failed to retrieve key from keyring: {}", e)))?;
let key_bytes = hex::decode(&key_hex)
.map_err(|e| Error::crypto(format!("Failed to decode key from hex: {}", e)))?;
if key_bytes.len() != 32 {
return Err(Error::crypto("Invalid key length"));
}
let mut key = [0u8; 32];
key.copy_from_slice(&key_bytes);
Ok(key)
}
}