Skip to main content

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}