auth_framework/security/timing_protection.rs
1//! Timing attack protection utilities
2//!
3//! This module provides utilities to prevent timing side-channel attacks,
4//! particularly important for authentication frameworks where attackers
5//! could exploit timing differences to extract sensitive information.
6
7use crate::errors::Result;
8use ring::rand::SecureRandom;
9use std::time::Duration;
10use subtle::ConstantTimeEq;
11
12/// Perform constant-time comparison of byte arrays
13///
14/// This function provides protection against timing attacks by ensuring
15/// the comparison takes the same amount of time regardless of where the
16/// first difference occurs.
17///
18/// # Arguments
19/// * `a` - First byte array to compare
20/// * `b` - Second byte array to compare
21///
22/// # Returns
23/// `true` if arrays are equal, `false` otherwise
24///
25/// # Security
26/// Uses the `subtle` crate's constant-time comparison to prevent
27/// timing side-channel attacks.
28pub fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
29 if a.len() != b.len() {
30 return false;
31 }
32 a.ct_eq(b).into()
33}
34
35/// Add a small random delay to mask timing patterns
36///
37/// This function adds a small, random delay to help mask timing patterns
38/// that could be exploited by attackers. Useful for authentication
39/// operations where you want to prevent timing analysis.
40///
41/// # Arguments
42/// * `base_delay_ms` - Base delay in milliseconds (default: 0)
43/// * `max_random_ms` - Maximum additional random delay in milliseconds (default: 10)
44///
45/// # Security
46/// The random delay helps prevent attackers from using timing analysis
47/// to determine success/failure patterns or extract sensitive information.
48pub async fn random_delay(base_delay_ms: u64, max_random_ms: u64) {
49 let base_delay = Duration::from_millis(base_delay_ms);
50 let rng = ring::rand::SystemRandom::new();
51 let mut buf = [0u8; 8];
52 rng.fill(&mut buf).expect("system RNG failure");
53 let random_ms = u64::from_le_bytes(buf) % max_random_ms;
54 let random_delay = Duration::from_millis(random_ms);
55 let total_delay = base_delay + random_delay;
56
57 tokio::time::sleep(total_delay).await;
58}
59
60/// Perform a constant-time string comparison
61///
62/// Compares two strings in constant time to prevent timing attacks.
63///
64/// # Arguments
65/// * `a` - First string to compare
66/// * `b` - Second string to compare
67///
68/// # Returns
69/// `true` if strings are equal, `false` otherwise
70pub fn constant_time_string_compare(a: &str, b: &str) -> bool {
71 constant_time_compare(a.as_bytes(), b.as_bytes())
72}
73
74/// Wrapper for sensitive authentication operations with timing protection
75///
76/// This function wraps sensitive operations with timing protection,
77/// ensuring that both success and failure cases take similar amounts of time.
78///
79/// # Arguments
80/// * `operation` - The async operation to perform
81/// * `min_duration_ms` - Minimum time the operation should take
82///
83/// # Returns
84/// The result of the operation
85///
86/// # Security
87/// Ensures that timing differences don't leak information about the
88/// success or failure of the operation.
89pub async fn timing_safe_operation<T, F, Fut>(operation: F, min_duration_ms: u64) -> Result<T>
90where
91 F: FnOnce() -> Fut,
92 Fut: std::future::Future<Output = Result<T>>,
93{
94 let start = std::time::Instant::now();
95 let result = operation().await;
96 let elapsed = start.elapsed();
97
98 let min_duration = Duration::from_millis(min_duration_ms);
99 if elapsed < min_duration {
100 let remaining = min_duration - elapsed;
101 tokio::time::sleep(remaining).await;
102 }
103
104 result
105}
106
107/// RSA operation wrapper with timing-attack protection
108///
109/// Wraps an RSA (or any asymmetric-key) operation with two layers of
110/// execution-time normalisation:
111///
112/// 1. **Pre-operation random jitter** – a random 1–5 ms delay is injected
113/// before the operation begins. This breaks any fixed-phase relationship
114/// an attacker might exploit to align power/EM traces or cache-timing
115/// samples across repeated calls.
116///
117/// 2. **Minimum execution time** – the total wall-clock time is padded to
118/// at least 10 ms. This ensures that fast-path completions (e.g. early
119/// rejection of a malformed ciphertext) do not leak information through
120/// shorter run times.
121///
122/// # RSA Blinding
123/// The underlying `ring` crate already applies Montgomery-form RSA blinding
124/// internally for all private-key operations (`RsaKeyPair::sign`,
125/// `RsaPrivateDecryptingKey::decrypt`, etc.). This wrapper is therefore
126/// complementary: it guards the *external* timing envelope rather than
127/// the per-bit operations inside the modular exponentiation.
128///
129/// For pure software RSA implementations that do NOT provide built-in
130/// blinding, callers should additionally randomise the exponent or base
131/// before entering this wrapper.
132///
133/// # Usage
134/// Prefer this wrapper around operations that touch long-term RSA private
135/// keys—signing tokens, unwrapping key-encryption keys, and similar tasks.
136pub async fn rsa_operation_protected<T, F, Fut>(operation: F) -> Result<T>
137where
138 F: FnOnce() -> Fut,
139 Fut: std::future::Future<Output = Result<T>>,
140{
141 // Add random delay before operation
142 random_delay(1, 5).await;
143
144 // Perform the operation with minimum timing
145 timing_safe_operation(operation, 10).await
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_constant_time_compare() {
154 let a = b"hello";
155 let b = b"hello";
156 let c = b"world";
157
158 assert!(constant_time_compare(a, b));
159 assert!(!constant_time_compare(a, c));
160 assert!(!constant_time_compare(a, b"hi"));
161 }
162
163 #[test]
164 fn test_constant_time_string_compare() {
165 assert!(constant_time_string_compare("hello", "hello"));
166 assert!(!constant_time_string_compare("hello", "world"));
167 assert!(!constant_time_string_compare("hello", "hi"));
168 }
169
170 #[tokio::test]
171 async fn test_random_delay() {
172 let start = std::time::Instant::now();
173 random_delay(0, 5).await;
174 let elapsed = start.elapsed();
175
176 // Should take at least some time but less than 50ms
177 assert!(elapsed >= Duration::from_millis(0));
178 assert!(elapsed < Duration::from_millis(50));
179 }
180
181 #[tokio::test]
182 async fn test_timing_safe_operation() {
183 let start = std::time::Instant::now();
184
185 let result = timing_safe_operation(
186 || async { Ok::<_, crate::errors::AuthError>("success") },
187 50,
188 )
189 .await;
190
191 let elapsed = start.elapsed();
192
193 assert!(result.is_ok());
194 assert!(elapsed >= Duration::from_millis(50));
195 }
196}