pub mod constant_time;
pub mod memory;
pub mod nonce;
pub mod side_channel;
pub mod stack_buffer;
pub mod timing;
pub mod validation;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SecurityConfig {
pub constant_time: bool,
pub side_channel_protection: bool,
pub secure_memory: bool,
pub strict_validation: bool,
pub timing_protection: bool,
pub fault_injection_protection: bool,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
constant_time: true,
side_channel_protection: true,
secure_memory: true,
strict_validation: true,
timing_protection: true,
fault_injection_protection: true,
}
}
}
impl SecurityConfig {
pub fn strict() -> Self {
Self {
constant_time: true,
side_channel_protection: true,
secure_memory: true,
strict_validation: true,
timing_protection: true,
fault_injection_protection: true,
}
}
pub fn permissive() -> Self {
Self {
constant_time: false,
side_channel_protection: false,
secure_memory: false,
strict_validation: false,
timing_protection: false,
fault_injection_protection: false,
}
}
pub fn balanced() -> Self {
Self {
constant_time: true,
side_channel_protection: true,
secure_memory: true,
strict_validation: true,
timing_protection: false,
fault_injection_protection: false,
}
}
}
#[cfg(feature = "std")]
use std::sync::{
LazyLock,
RwLock,
};
#[cfg(feature = "std")]
static GLOBAL_SECURITY_CONFIG: LazyLock<RwLock<SecurityConfig>> =
LazyLock::new(|| RwLock::new(SecurityConfig::default()));
#[cfg(not(feature = "std"))]
static GLOBAL_SECURITY_CONFIG: spin::LazyLock<spin::Mutex<SecurityConfig>> =
spin::LazyLock::new(|| spin::Mutex::new(SecurityConfig::default()));
pub fn get_security_config() -> SecurityConfig {
#[cfg(feature = "std")]
{
*GLOBAL_SECURITY_CONFIG
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(not(feature = "std"))]
{
*GLOBAL_SECURITY_CONFIG.lock()
}
}
pub fn set_security_config(config: SecurityConfig) {
#[cfg(feature = "std")]
{
*GLOBAL_SECURITY_CONFIG
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner) = config;
}
#[cfg(not(feature = "std"))]
{
*GLOBAL_SECURITY_CONFIG.lock() = config;
}
}
#[cfg(all(test, feature = "std"))]
pub(crate) static TEST_CONFIG_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[cfg(all(test, feature = "std"))]
pub(crate) fn lock_for_test() -> std::sync::MutexGuard<'static, ()> {
TEST_CONFIG_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub struct SecurityContext {
config: SecurityConfig,
operation_id: u64,
start_time: u64,
}
impl SecurityContext {
pub fn new() -> Self {
Self {
config: get_security_config(),
operation_id: Self::generate_operation_id(),
start_time: Self::get_timestamp(),
}
}
pub fn with_config(config: SecurityConfig) -> Self {
Self {
config,
operation_id: Self::generate_operation_id(),
start_time: Self::get_timestamp(),
}
}
pub fn operation_id(&self) -> u64 {
self.operation_id
}
pub fn elapsed_time(&self) -> u64 {
Self::get_timestamp() - self.start_time
}
pub fn constant_time_enabled(&self) -> bool {
self.config.constant_time
}
pub fn side_channel_protection_enabled(&self) -> bool {
self.config.side_channel_protection
}
pub fn secure_memory_enabled(&self) -> bool {
self.config.secure_memory
}
pub fn strict_validation_enabled(&self) -> bool {
self.config.strict_validation
}
pub fn timing_protection_enabled(&self) -> bool {
self.config.timing_protection
}
pub fn fault_injection_protection_enabled(&self) -> bool {
self.config.fault_injection_protection
}
fn generate_operation_id() -> u64 {
use core::sync::atomic::Ordering;
use portable_atomic::AtomicU64;
static COUNTER: AtomicU64 = AtomicU64::new(0);
let counter: &AtomicU64 = &COUNTER;
counter.fetch_add(1, Ordering::Relaxed) + 1
}
fn get_timestamp() -> u64 {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
{
use std::time::{
SystemTime,
UNIX_EPOCH,
};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
{
use core::sync::atomic::Ordering;
use portable_atomic::AtomicU64;
static COUNTER: AtomicU64 = AtomicU64::new(0);
COUNTER.fetch_add(1, Ordering::SeqCst)
}
}
}
impl Default for SecurityContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_config_defaults() {
let config = SecurityConfig::default();
assert!(config.constant_time);
assert!(config.side_channel_protection);
assert!(config.secure_memory);
assert!(config.strict_validation);
assert!(config.timing_protection);
assert!(config.fault_injection_protection);
}
#[test]
fn test_security_config_strict() {
let config = SecurityConfig::strict();
assert!(config.constant_time);
assert!(config.side_channel_protection);
assert!(config.secure_memory);
assert!(config.strict_validation);
assert!(config.timing_protection);
assert!(config.fault_injection_protection);
}
#[test]
fn test_security_config_permissive() {
let config = SecurityConfig::permissive();
assert!(!config.constant_time);
assert!(!config.side_channel_protection);
assert!(!config.secure_memory);
assert!(!config.strict_validation);
assert!(!config.timing_protection);
assert!(!config.fault_injection_protection);
}
#[test]
fn test_security_config_balanced() {
let config = SecurityConfig::balanced();
assert!(config.constant_time);
assert!(config.side_channel_protection);
assert!(config.secure_memory);
assert!(config.strict_validation);
assert!(!config.timing_protection);
assert!(!config.fault_injection_protection);
}
#[test]
fn test_security_context_creation() {
#[cfg(feature = "std")]
let _guard = lock_for_test();
let ctx = SecurityContext::new();
assert!(ctx.operation_id() > 0);
let _elapsed = ctx.elapsed_time();
assert!(ctx.constant_time_enabled());
}
#[test]
fn test_security_context_with_config() {
let config = SecurityConfig::permissive();
let ctx = SecurityContext::with_config(config);
assert!(!ctx.constant_time_enabled());
assert!(!ctx.side_channel_protection_enabled());
assert!(!ctx.secure_memory_enabled());
assert!(!ctx.strict_validation_enabled());
assert!(!ctx.timing_protection_enabled());
assert!(!ctx.fault_injection_protection_enabled());
}
#[test]
fn test_global_security_config() {
#[cfg(feature = "std")]
let _guard = lock_for_test();
let original_config = get_security_config();
let new_config = SecurityConfig::permissive();
set_security_config(new_config);
let retrieved_config = get_security_config();
assert_eq!(retrieved_config, new_config);
set_security_config(original_config);
}
#[cfg(feature = "std")]
fn poison_global_config_lock() {
let poisoner = std::thread::spawn(|| {
let _write_guard = GLOBAL_SECURITY_CONFIG
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
panic!("deliberately poisoning GLOBAL_SECURITY_CONFIG");
});
assert!(
poisoner.join().is_err(),
"the poisoning thread did not panic, so the lock is not poisoned and this test \
would prove nothing"
);
}
#[cfg(feature = "std")]
#[test]
fn set_applies_even_after_poison() {
let _guard = lock_for_test();
poison_global_config_lock();
set_security_config(SecurityConfig::permissive());
assert_eq!(
get_security_config(),
SecurityConfig::permissive(),
"set_security_config silently did not apply after the lock was poisoned"
);
set_security_config(SecurityConfig::default());
}
#[cfg(feature = "std")]
#[test]
fn get_returns_last_set_even_after_poison() {
let _guard = lock_for_test();
set_security_config(SecurityConfig::permissive());
assert_eq!(
get_security_config(),
SecurityConfig::permissive(),
"set_security_config did not apply before any poisoning happened"
);
poison_global_config_lock();
assert_eq!(
get_security_config(),
SecurityConfig::permissive(),
"get_security_config invented a value instead of returning the stored one"
);
set_security_config(SecurityConfig::default());
assert_eq!(get_security_config(), SecurityConfig::default());
}
}