#[cfg(feature = "alloc")]
use alloc::string::ToString;
use subtle::{
Choice,
ConditionallySelectable,
ConstantTimeEq,
};
use crate::error::Result;
#[cfg(feature = "alloc")]
#[derive(Clone)]
pub struct TimingValidator {
enable_timing_validation: bool,
}
#[cfg(feature = "alloc")]
impl TimingValidator {
pub fn new() -> Result<Self> {
Ok(Self {
enable_timing_validation: true,
})
}
pub fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
a.ct_eq(b).into()
}
pub fn constant_time_select<T: ConditionallySelectable>(&self, choice: bool, a: T, b: T) -> T {
T::conditional_select(&b, &a, Choice::from(u8::from(choice)))
}
pub fn constant_time_assign<T: ConditionallySelectable>(
&self,
choice: bool,
dst: &mut T,
src: T,
) {
dst.conditional_assign(&src, Choice::from(u8::from(choice)));
}
pub fn constant_time_copy(&self, choice: bool, dst: &mut [u8], src: &[u8]) {
assert_eq!(dst.len(), src.len(), "Slices must have the same length");
let choice = Choice::from(u8::from(choice));
for (d, s) in dst.iter_mut().zip(src.iter()) {
d.conditional_assign(s, choice);
}
}
pub fn validate_timing_safety(&self, operation: &str) -> Result<()> {
if !self.enable_timing_validation {
return Ok(());
}
if operation.is_empty() {
return Err(crate::error::Error::InvalidState {
operation: "timing_validation".to_string(),
reason: "Operation name cannot be empty".to_string(),
});
}
Ok(())
}
pub fn set_timing_validation(&mut self, enabled: bool) {
self.enable_timing_validation = enabled;
}
pub fn is_timing_validation_enabled(&self) -> bool {
self.enable_timing_validation
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timing_validator_creation() {
let validator = TimingValidator::new();
assert!(
validator.is_ok(),
"TimingValidator should be created successfully"
);
}
#[test]
fn test_constant_time_compare() {
let validator = TimingValidator::new().unwrap();
let a = vec![1, 2, 3, 4];
let b = vec![1, 2, 3, 4];
assert!(
validator.constant_time_compare(&a, &b),
"Should return true for equal slices"
);
let c = vec![1, 2, 3, 5];
assert!(
!validator.constant_time_compare(&a, &c),
"Should return false for different slices"
);
let d = vec![1, 2, 3];
assert!(
!validator.constant_time_compare(&a, &d),
"Should return false for different length slices"
);
}
#[test]
fn test_constant_time_select() {
let validator = TimingValidator::new().unwrap();
let result = validator.constant_time_select(true, 42, 24);
assert_eq!(result, 42, "Should select first value when choice is true");
let result = validator.constant_time_select(false, 42, 24);
assert_eq!(
result, 24,
"Should select second value when choice is false"
);
}
#[test]
fn test_constant_time_assign() {
let validator = TimingValidator::new().unwrap();
let mut value = 10;
validator.constant_time_assign(true, &mut value, 20);
assert_eq!(value, 20, "Should assign new value when choice is true");
validator.constant_time_assign(false, &mut value, 30);
assert_eq!(value, 20, "Should not change value when choice is false");
}
#[test]
fn test_constant_time_copy() {
let validator = TimingValidator::new().unwrap();
let mut dst = vec![1, 2, 3, 4];
let src = vec![5, 6, 7, 8];
validator.constant_time_copy(true, &mut dst, &src);
assert_eq!(dst, src, "Should copy source when choice is true");
let original = dst.clone();
validator.constant_time_copy(false, &mut dst, &[9, 10, 11, 12]);
assert_eq!(
dst, original,
"Should not change destination when choice is false"
);
}
#[test]
fn test_validate_timing_safety() {
let validator = TimingValidator::new().unwrap();
let result = validator.validate_timing_safety("test_operation");
assert!(result.is_ok(), "Should accept valid operation name");
let result = validator.validate_timing_safety("");
assert!(result.is_err(), "Should reject empty operation name");
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct SelectCanary(u32);
const CANARY_MARK: u32 = 0x8000_0000;
impl ConditionallySelectable for SelectCanary {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
SelectCanary(u32::conditional_select(&a.0, &b.0, choice) | CANARY_MARK)
}
}
#[test]
fn select_and_assign_dispatch_through_subtle_not_a_branch() {
let v = TimingValidator::new().unwrap();
let r = v.constant_time_select(true, SelectCanary(1), SelectCanary(2));
assert_eq!(
r,
SelectCanary(1 | CANARY_MARK),
"constant_time_select must route through ConditionallySelectable (mask), not if/else"
);
let r = v.constant_time_select(false, SelectCanary(1), SelectCanary(2));
assert_eq!(r, SelectCanary(2 | CANARY_MARK));
let mut d = SelectCanary(7);
v.constant_time_assign(true, &mut d, SelectCanary(9));
assert_eq!(
d,
SelectCanary(9 | CANARY_MARK),
"constant_time_assign must route through conditional_assign"
);
}
#[test]
fn test_timing_validation_control() {
let mut validator = TimingValidator::new().unwrap();
assert!(
validator.is_timing_validation_enabled(),
"Timing validation should be enabled by default"
);
validator.set_timing_validation(false);
assert!(
!validator.is_timing_validation_enabled(),
"Timing validation should be disabled"
);
validator.set_timing_validation(true);
assert!(
validator.is_timing_validation_enabled(),
"Timing validation should be enabled"
);
}
}