use secure_identity::{
authenticator::{AuthenticationRequest, Authenticator, TokenKind},
token::{TokenValidator, TokenValidatorConfig},
};
use std::time::Instant;
const SAMPLE_COUNT: usize = 200;
const SECRET: &[u8] = b"test-secret-key-for-timing-test-only-32b";
fn make_validator() -> TokenValidator {
TokenValidator::new(TokenValidatorConfig {
issuer: "test-issuer".to_string(),
audience: "test-audience".to_string(),
secret: SECRET.to_vec(),
})
}
fn make_request(token: &str) -> AuthenticationRequest {
AuthenticationRequest {
token: token.to_string(),
token_kind: TokenKind::BearerJwt,
}
}
fn welchs_t(a: &[f64], b: &[f64]) -> f64 {
let mean_a = a.iter().sum::<f64>() / a.len() as f64;
let mean_b = b.iter().sum::<f64>() / b.len() as f64;
let var_a = a.iter().map(|x| (x - mean_a).powi(2)).sum::<f64>() / (a.len() - 1) as f64;
let var_b = b.iter().map(|x| (x - mean_b).powi(2)).sum::<f64>() / (b.len() - 1) as f64;
let se = (var_a / a.len() as f64 + var_b / b.len() as f64).sqrt();
if se == 0.0 {
return 0.0;
}
((mean_a - mean_b) / se).abs()
}
#[test]
#[ignore = "timing test — run locally on a stable machine: cargo test -p secure_identity -- timing_ --ignored"]
fn timing_token_validation_no_significant_difference() {
let rt = tokio::runtime::Runtime::new().unwrap();
let validator = make_validator();
let short_invalid = "not.a.jwt";
let long_invalid = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\
eyJzdWIiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDEiLCJleHAiOjk5OTk5OTk5OTksImlhdCI6MTcwMDAwMDAwMCwiaXNzIjoidGVzdC1pc3N1ZXIiLCJhdWQiOiJ0ZXN0LWF1ZGllbmNlIn0.\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
for _ in 0..20 {
let req = make_request(short_invalid);
let _ = rt.block_on(validator.authenticate(&req));
let req = make_request(long_invalid);
let _ = rt.block_on(validator.authenticate(&req));
}
let mut short_times = Vec::with_capacity(SAMPLE_COUNT);
let mut long_times = Vec::with_capacity(SAMPLE_COUNT);
for _ in 0..SAMPLE_COUNT {
let req = make_request(short_invalid);
let start = Instant::now();
let _ = rt.block_on(validator.authenticate(&req));
short_times.push(start.elapsed().as_nanos() as f64);
let req = make_request(long_invalid);
let start = Instant::now();
let _ = rt.block_on(validator.authenticate(&req));
long_times.push(start.elapsed().as_nanos() as f64);
}
let t = welchs_t(&short_times, &long_times);
assert!(
t < 4.5,
"Suspicious timing difference (Welch's t={t:.2}) between short and long invalid tokens. \
This may indicate a timing side-channel. Check for early-exit logic in token validation."
);
}