use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use generic_array::GenericArray;
use rand::RngCore;
use ring::hmac;
use std::env;
use crate::core::error::RiError;
use crate::core::error::RiResult;
#[cfg(feature = "pyo3")]
pub(crate) fn ri_error_to_py_err(e: RiError) -> pyo3::prelude::PyErr {
use pyo3::exceptions::*;
match e {
RiError::InvalidInput(_) | RiError::InvalidState(_) | RiError::SecurityViolation(_)
| RiError::TomlError(_) | RiError::YamlError(_) | RiError::FrameError(_) => {
PyValueError::new_err(e.to_string())
}
RiError::DeviceNotFound { .. } | RiError::AllocationNotFound { .. }
| RiError::ModuleNotFound { .. } | RiError::MissingDependency { .. } => {
PyKeyError::new_err(e.to_string())
}
RiError::CircularDependency { .. } => {
PyValueError::new_err(e.to_string())
}
RiError::Io(_) | RiError::Config(_) | RiError::Serde(_) | RiError::Hook(_)
| RiError::Prometheus(_) | RiError::ServiceMesh(_) | RiError::DeviceAllocationFailed { .. }
| RiError::ModuleInitFailed { .. } | RiError::ModuleStartFailed { .. } | RiError::ModuleShutdownFailed { .. }
| RiError::Other(_) | RiError::ExternalError(_) | RiError::PoolError(_) | RiError::DeviceError(_)
| RiError::RedisError(_) | RiError::HttpClientError(_) | RiError::Queue(_)
| RiError::Database(_) => {
PyRuntimeError::new_err(e.to_string())
}
}
}
const ENCRYPTION_KEY_ENV: &str = "Ri_ENCRYPTION_KEY";
const HMAC_KEY_ENV: &str = "Ri_HMAC_KEY";
const DEFAULT_KEY_LENGTH: usize = 32;
const NONCE_LENGTH: usize = 12;
static ENCRYPTION_KEY_WARNED: std::sync::Once = std::sync::Once::new();
static HMAC_KEY_WARNED: std::sync::Once = std::sync::Once::new();
fn load_or_generate_key(env_var: &str, length: usize, key_name: &str, warned: &std::sync::Once) -> Vec<u8> {
if let Ok(s) = env::var(env_var) {
if let Ok(key) = hex::decode(&s) {
if key.len() >= 16 {
return key;
}
tracing::warn!(
"{} from {} is too short ({} bytes), minimum 16 bytes required",
key_name, env_var, key.len()
);
}
}
warned.call_once(|| {
tracing::warn!(
"SECURITY WARNING: {} not set or invalid. Using ephemeral random key. \
Encrypted data will be lost on restart! Set {} environment variable.",
key_name, env_var
);
});
let mut key = vec![0u8; length];
rand::thread_rng().fill_bytes(&mut key);
key
}
fn load_encryption_key() -> Vec<u8> {
load_or_generate_key(ENCRYPTION_KEY_ENV, DEFAULT_KEY_LENGTH, "Encryption key", &ENCRYPTION_KEY_WARNED)
}
fn load_hmac_key() -> Vec<u8> {
load_or_generate_key(HMAC_KEY_ENV, DEFAULT_KEY_LENGTH, "HMAC key", &HMAC_KEY_WARNED)
}
#[allow(dead_code)]
pub fn check_encryption_keys() -> RiResult<()> {
let encryption_key_set = env::var(ENCRYPTION_KEY_ENV)
.ok()
.and_then(|s| hex::decode(&s).ok())
.map(|k| k.len() >= 16)
.unwrap_or(false);
let hmac_key_set = env::var(HMAC_KEY_ENV)
.ok()
.and_then(|s| hex::decode(&s).ok())
.map(|k| k.len() >= 16)
.unwrap_or(false);
if !encryption_key_set || !hmac_key_set {
let mut missing = Vec::new();
if !encryption_key_set {
missing.push(ENCRYPTION_KEY_ENV);
}
if !hmac_key_set {
missing.push(HMAC_KEY_ENV);
}
return Err(RiError::SecurityViolation(format!(
"Encryption keys not configured: {}. \
Generate keys using RiSecurityManager::generate_encryption_key() and \
RiSecurityManager::generate_hmac_key(), then set them as environment variables. \
WARNING: Without proper keys, encrypted data will be lost on restart!",
missing.join(", ")
)));
}
Ok(())
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiSecurityManager;
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiSecurityManager {
#[new]
fn py_new() -> Self {
Self
}
#[staticmethod]
fn encrypt_py(plaintext: &str) -> pyo3::prelude::PyResult<String> {
Self::encrypt(plaintext).map_err(ri_error_to_py_err)
}
#[staticmethod]
fn decrypt_py(encrypted: &str) -> pyo3::prelude::PyResult<String> {
Self::decrypt(encrypted).map_err(ri_error_to_py_err)
}
#[staticmethod]
fn hmac_sign_py(data: &str) -> String {
Self::hmac_sign(data)
}
#[staticmethod]
fn hmac_verify_py(data: &str, signature: &str) -> bool {
Self::hmac_verify(data, signature)
}
#[staticmethod]
fn generate_encryption_key_py() -> String {
Self::generate_encryption_key()
}
#[staticmethod]
fn generate_hmac_key_py() -> String {
Self::generate_hmac_key()
}
}
impl RiSecurityManager {
pub fn encrypt(plaintext: &str) -> RiResult<String> {
let key = load_encryption_key();
let nonce = {
let mut n = [0u8; NONCE_LENGTH];
rand::thread_rng().fill_bytes(&mut n);
n
};
let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
let ciphertext = cipher
.encrypt(Nonce::from_slice(&nonce), plaintext.as_bytes())
.map_err(|e| RiError::SecurityViolation(format!("encryption failed: {}", e)))?;
let mut result = Vec::with_capacity(nonce.len() + ciphertext.len());
result.extend_from_slice(&nonce);
result.extend_from_slice(&ciphertext);
Ok(STANDARD.encode(result))
}
pub fn decrypt(encrypted: &str) -> RiResult<String> {
let key = load_encryption_key();
let data = STANDARD.decode(encrypted)
.map_err(|e| RiError::SecurityViolation(format!("Base64 decode failed: {}", e)))?;
if data.len() < NONCE_LENGTH {
return Err(RiError::SecurityViolation(
format!("Encrypted data too short: expected at least {} bytes, got {}",
NONCE_LENGTH, data.len())
));
}
let (nonce, ciphertext) = data.split_at(NONCE_LENGTH);
let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
let plaintext = cipher
.decrypt(Nonce::from_slice(nonce), ciphertext)
.map_err(|e| RiError::SecurityViolation(format!("Decryption failed: {}", e)))?;
String::from_utf8(plaintext)
.map_err(|e| RiError::SecurityViolation(format!("UTF-8 decode failed: {}", e)))
}
pub fn hmac_sign(data: &str) -> String {
let key = load_hmac_key();
let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &key);
let signature = hmac::sign(&signing_key, data.as_bytes());
hex::encode(signature)
}
pub fn hmac_verify(data: &str, signature: &str) -> bool {
let expected = match hex::decode(signature) {
Ok(sig) => sig,
Err(_) => {
log::warn!("[Ri.Security] Invalid hex signature format");
return false;
}
};
let key = load_hmac_key();
let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &key);
hmac::verify(&signing_key, data.as_bytes(), &expected).is_ok()
}
pub fn generate_encryption_key() -> String {
let mut key = vec![0u8; DEFAULT_KEY_LENGTH];
rand::thread_rng().fill_bytes(&mut key);
hex::encode(key)
}
pub fn generate_hmac_key() -> String {
let mut key = vec![0u8; DEFAULT_KEY_LENGTH];
rand::thread_rng().fill_bytes(&mut key);
hex::encode(key)
}
}