use nu_plugin_secret::{SecretBinary, SecretBool, SecretFloat, SecretInt, SecretString};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[cfg(test)]
mod side_channel_tests {
use super::*;
#[test]
fn test_string_comparison_timing_attacks() {
let reference_secret = "correct_password_12345";
let secret = SecretString::new(reference_secret.to_string());
let long_string = "a".repeat(100);
let test_cases = vec![
("", "empty string"),
("x", "single char"),
("wrong", "short wrong"),
("correct_password_12345", "exact match"),
("correct_password_12346", "off by one"),
("correct_password_99999", "same length different"),
("correct_password_12345_extra", "longer correct prefix"),
("totally_different_password", "completely different"),
(long_string.as_str(), "very long string"),
];
let mut timings: HashMap<String, Vec<Duration>> = HashMap::new();
for (test_string, description) in &test_cases {
let test_secret = SecretString::new(test_string.to_string());
let mut case_timings = Vec::new();
for _ in 0..1000 {
let start = Instant::now();
let _result = secret == test_secret;
let duration = start.elapsed();
case_timings.push(duration);
}
timings.insert(description.to_string(), case_timings);
}
let mut avg_timings: HashMap<String, u64> = HashMap::new();
for (desc, times) in &timings {
let avg_nanos: u64 =
times.iter().map(|d| d.as_nanos() as u64).sum::<u64>() / times.len() as u64;
avg_timings.insert(desc.clone(), avg_nanos);
}
let min_time = avg_timings.values().min().unwrap();
let max_time = avg_timings.values().max().unwrap();
let time_ratio = *max_time as f64 / *min_time as f64;
println!("Timing analysis results:");
for (desc, avg_ns) in &avg_timings {
println!(" {}: {}ns", desc, avg_ns);
}
println!("Max/Min ratio: {:.2}", time_ratio);
if time_ratio > 3.0 {
println!(
"Warning: Significant timing differences detected (ratio: {:.2})",
time_ratio
);
println!("This may indicate vulnerability to timing attacks");
}
}
#[test]
fn test_integer_comparison_timing_attacks() {
let reference_value = 1234567890i64;
let secret = SecretInt::new(reference_value);
let test_values = vec![
0i64,
1,
reference_value - 1,
reference_value,
reference_value + 1,
i64::MAX,
i64::MIN,
];
let mut timings = Vec::new();
for &test_value in &test_values {
let test_secret = SecretInt::new(test_value);
let mut case_timings = Vec::new();
for _ in 0..1000 {
let start = Instant::now();
let _result = secret == test_secret;
let duration = start.elapsed();
case_timings.push(duration);
}
let avg_nanos = case_timings
.iter()
.map(|d| d.as_nanos() as u64)
.sum::<u64>()
/ case_timings.len() as u64;
timings.push((test_value, avg_nanos));
}
let avg_times: Vec<u64> = timings.iter().map(|(_, time)| *time).collect();
let min_time = avg_times.iter().min().unwrap();
let max_time = avg_times.iter().max().unwrap();
let time_ratio = *max_time as f64 / *min_time as f64;
println!("Integer comparison timing analysis:");
for (value, time_ns) in &timings {
println!(" {}: {}ns", value, time_ns);
}
println!("Max/Min ratio: {:.2}", time_ratio);
if time_ratio > 2.0 {
println!(
"Warning: Integer comparison timing variance detected (ratio: {:.2})",
time_ratio
);
}
}
#[test]
#[ignore] fn test_binary_data_sidechannel_resistance() {
let reference_data = vec![0xde, 0xad, 0xbe, 0xef, 0x12, 0x34, 0x56, 0x78];
let secret = SecretBinary::new(reference_data.clone());
let test_patterns = vec![
vec![0x00; 8], vec![0xff; 8], reference_data.clone(), vec![0xde, 0xad, 0xbe, 0xef, 0x12, 0x34, 0x56, 0x79], vec![0xaa; 8], vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77], ];
let mut timings = Vec::new();
for pattern in &test_patterns {
let test_secret = SecretBinary::new(pattern.clone());
let mut case_timings = Vec::new();
for _ in 0..500 {
let start = Instant::now();
let _result = secret == test_secret;
let duration = start.elapsed();
case_timings.push(duration);
}
let avg_nanos = case_timings
.iter()
.map(|d| d.as_nanos() as u64)
.sum::<u64>()
/ case_timings.len() as u64;
timings.push((format!("{:02x?}", &pattern[..4]), avg_nanos));
}
let times: Vec<u64> = timings.iter().map(|(_, time)| *time).collect();
let min_time = times.iter().min().unwrap();
let max_time = times.iter().max().unwrap();
let time_ratio = *max_time as f64 / *min_time as f64;
println!("Binary comparison timing analysis:");
for (pattern, time_ns) in &timings {
println!(" {}: {}ns", pattern, time_ns);
}
assert!(
time_ratio < 4.0,
"Binary comparison timing too variable (ratio: {:.2}) - potential side-channel vulnerability",
time_ratio
);
}
#[test]
fn test_cache_timing_resistance() {
let secrets = [
SecretString::new("cache_test_pattern_1".to_string()),
SecretString::new("cache_test_pattern_2".to_string()),
SecretString::new("different_pattern_abc".to_string()),
SecretString::new("x".repeat(100)),
SecretString::new("short".to_string()),
];
let mut access_timings = Vec::new();
for (i, secret) in secrets.iter().enumerate() {
let mut times = Vec::new();
for _ in 0..200 {
let _dummy = SecretString::new("cache_flush".to_string());
let start = Instant::now();
let _revealed = secret.reveal(); let duration = start.elapsed();
times.push(duration.as_nanos() as u64);
}
let avg_time = times.iter().sum::<u64>() / times.len() as u64;
access_timings.push((i, avg_time));
}
let times: Vec<u64> = access_timings.iter().map(|(_, time)| *time).collect();
let min_time = times.iter().min().unwrap();
let max_time = times.iter().max().unwrap();
let time_ratio = *max_time as f64 / *min_time as f64;
println!("Cache timing analysis:");
for (idx, time_ns) in &access_timings {
println!(" Secret {}: {}ns", idx, time_ns);
}
println!("Max/Min ratio: {:.2}", time_ratio);
if time_ratio > 3.0 {
println!(
"Warning: Cache timing variance detected (ratio: {:.2})",
time_ratio
);
}
}
#[test]
fn test_branch_prediction_resistance() {
let _secret_bool = SecretBool::new(true);
let mut true_timings = Vec::new();
let mut false_timings = Vec::new();
for condition in [true, false] {
let test_secret = SecretBool::new(condition);
for _ in 0..500 {
let start = Instant::now();
let _result = if test_secret.reveal() {
let mut sum = 0u64;
for i in 0..10 {
sum = sum.wrapping_add(i);
}
sum
} else {
let mut prod = 1u64;
for i in 1..10 {
prod = prod.wrapping_mul(i);
}
prod
};
let duration = start.elapsed();
if condition {
true_timings.push(duration.as_nanos() as u64);
} else {
false_timings.push(duration.as_nanos() as u64);
}
}
}
let avg_true = true_timings.iter().sum::<u64>() / true_timings.len() as u64;
let avg_false = false_timings.iter().sum::<u64>() / false_timings.len() as u64;
let time_ratio = avg_true.max(avg_false) as f64 / avg_true.min(avg_false) as f64;
println!("Branch prediction analysis:");
println!(" True branch: {}ns", avg_true);
println!(" False branch: {}ns", avg_false);
println!(" Ratio: {:.2}", time_ratio);
if time_ratio > 5.0 {
println!(
"Warning: Large branch timing difference (ratio: {:.2})",
time_ratio
);
}
}
#[test]
fn test_power_analysis_simulation() {
let secrets = [
SecretInt::new(0), SecretInt::new(i64::MAX), SecretInt::new(0xAAAAAAAA), SecretInt::new(0x12345678), ];
let mut complexity_scores = Vec::new();
for (i, secret) in secrets.iter().enumerate() {
let mut operation_times = Vec::new();
for _ in 0..100 {
let start = Instant::now();
let revealed = secret.reveal();
let _bit_count = revealed.count_ones(); let _leading_zeros = revealed.leading_zeros(); let _trailing_zeros = revealed.trailing_zeros();
let duration = start.elapsed();
operation_times.push(duration.as_nanos() as u64);
}
let avg_time = operation_times.iter().sum::<u64>() / operation_times.len() as u64;
complexity_scores.push((i, avg_time));
}
println!("Power analysis simulation:");
for (idx, time_ns) in &complexity_scores {
println!(" Pattern {}: {}ns", idx, time_ns);
}
let times: Vec<u64> = complexity_scores.iter().map(|(_, time)| *time).collect();
let min_time = times.iter().min().unwrap();
let max_time = times.iter().max().unwrap();
let variance_ratio = *max_time as f64 / *min_time as f64;
println!("Timing variance ratio: {:.2}", variance_ratio);
if variance_ratio > 2.0 {
println!(
"Warning: Potential power analysis vulnerability (ratio: {:.2})",
variance_ratio
);
}
}
#[test]
fn test_electromagnetic_emanation_simulation() {
let test_data = [
"aaaaaaaa".to_string(), "abababab".to_string(), "abcdefgh".to_string(), "zyx987!@".to_string(), ];
let mut access_patterns = Vec::new();
for (i, data) in test_data.iter().enumerate() {
let secret = SecretString::new(data.clone());
let mut access_times = Vec::new();
for _ in 0..100 {
let start = Instant::now();
let chars: Vec<char> = secret.reveal().chars().collect();
let _checksum: u32 = chars
.iter()
.enumerate()
.map(|(idx, &c)| (c as u32).wrapping_mul(idx as u32 + 1))
.sum();
let duration = start.elapsed();
access_times.push(duration.as_nanos() as u64);
}
let avg_time = access_times.iter().sum::<u64>() / access_times.len() as u64;
access_patterns.push((i, avg_time));
}
println!("EM emanation simulation:");
for (idx, time_ns) in &access_patterns {
println!(" Pattern {}: {}ns", idx, time_ns);
}
let times: Vec<u64> = access_patterns.iter().map(|(_, time)| *time).collect();
let min_time = times.iter().min().unwrap();
let max_time = times.iter().max().unwrap();
let consistency_ratio = *max_time as f64 / *min_time as f64;
println!("EM consistency ratio: {:.2}", consistency_ratio);
if consistency_ratio > 2.5 {
println!(
"Warning: Potential EM emanation vulnerability (ratio: {:.2})",
consistency_ratio
);
}
}
}
#[cfg(test)]
mod statistical_analysis {
use super::*;
#[test]
fn test_timing_statistical_significance() {
let secret = SecretString::new("test_secret_for_stats".to_string());
let mut sample_times = Vec::new();
for _ in 0..10000 {
let start = Instant::now();
let _revealed = secret.reveal();
let duration = start.elapsed();
sample_times.push(duration.as_nanos() as u64);
}
let mean = sample_times.iter().sum::<u64>() / sample_times.len() as u64;
let variance = sample_times
.iter()
.map(|&x| {
let diff = x as i64 - mean as i64;
(diff * diff) as u64
})
.sum::<u64>()
/ sample_times.len() as u64;
let std_dev = (variance as f64).sqrt();
sample_times.sort();
let median = sample_times[sample_times.len() / 2];
let p95 = sample_times[(sample_times.len() * 95) / 100];
let p99 = sample_times[(sample_times.len() * 99) / 100];
println!("Statistical analysis of secret access timing:");
println!(" Sample size: {}", sample_times.len());
println!(" Mean: {}ns", mean);
println!(" Median: {}ns", median);
println!(" Std Dev: {:.2}ns", std_dev);
println!(" 95th percentile: {}ns", p95);
println!(" 99th percentile: {}ns", p99);
println!(" Min: {}ns", sample_times[0]);
println!(" Max: {}ns", sample_times[sample_times.len() - 1]);
let coefficient_of_variation = std_dev / mean as f64;
println!(
" Coefficient of variation: {:.4}",
coefficient_of_variation
);
if coefficient_of_variation > 0.5 {
println!(
"Warning: High timing variability detected (CV: {:.4})",
coefficient_of_variation
);
}
}
#[test]
fn test_timing_distribution_normality() {
let secret = SecretFloat::new(std::f64::consts::PI);
let mut sample_times = Vec::new();
for _ in 0..1000 {
let start = Instant::now();
let _value = secret.reveal();
let duration = start.elapsed();
sample_times.push(duration.as_nanos() as f64);
}
let mean = sample_times.iter().sum::<f64>() / sample_times.len() as f64;
let variance = sample_times
.iter()
.map(|&x| (x - mean).powi(2))
.sum::<f64>()
/ sample_times.len() as f64;
let std_dev = variance.sqrt();
let skewness = sample_times
.iter()
.map(|&x| ((x - mean) / std_dev).powi(3))
.sum::<f64>()
/ sample_times.len() as f64;
let kurtosis = sample_times
.iter()
.map(|&x| ((x - mean) / std_dev).powi(4))
.sum::<f64>()
/ sample_times.len() as f64;
println!("Timing distribution analysis:");
println!(" Mean: {:.2}ns", mean);
println!(" Std Dev: {:.2}ns", std_dev);
println!(" Skewness: {:.4}", skewness);
println!(" Kurtosis: {:.4}", kurtosis);
if skewness.abs() > 2.0 {
println!(
"Warning: High skewness detected ({:.4}) - timing distribution is asymmetric",
skewness
);
}
if (kurtosis - 3.0).abs() > 2.0 {
println!("Warning: Abnormal kurtosis ({:.4}) - timing distribution has unusual tail behavior", kurtosis);
}
}
}
#[cfg(all(test, not(miri)))]
mod security_performance_tests {
use super::*;
#[test]
fn test_constant_time_operation_performance() {
let test_sizes = vec![10, 100, 1000, 10000];
for size in test_sizes {
let data1 = "x".repeat(size);
let data2 = "y".repeat(size);
let secret1 = SecretString::new(data1);
let secret2 = SecretString::new(data2);
let start = Instant::now();
let _result = secret1 == secret2;
let duration = start.elapsed();
println!(
"Constant-time comparison for {} chars: {}ns",
size,
duration.as_nanos()
);
assert!(
duration.as_millis() < 100,
"Constant-time operation too slow for {} characters: {}ms",
size,
duration.as_millis()
);
}
}
#[test]
fn test_adversarial_performance() {
let adversarial_inputs = [
"".to_string(), "\0".repeat(1000), std::char::MAX.to_string().repeat(100), "a".repeat(10000), ];
for (i, input) in adversarial_inputs.iter().enumerate() {
let secret = SecretString::new(input.clone());
let start = Instant::now();
let _revealed = secret.reveal();
let duration = start.elapsed();
println!(
"Adversarial input {} performance: {}ns",
i,
duration.as_nanos()
);
assert!(
duration.as_millis() < 100,
"Adversarial input {} caused performance degradation: {}ms",
i,
duration.as_millis()
);
}
}
}