Skip to main content

lib_q_core/security/
timing.rs

1//! Timing attack prevention utilities
2//!
3//! This module provides utilities to prevent timing attacks in cryptographic operations.
4//!
5//! ## AEAD decrypt and verification
6//!
7//! For authenticated decryption, see the implementor contract on [`crate::traits::Aead`]:
8//! symmetric work should precede verification branches where required by the threat model,
9//! while the public [`Result`] API still exposes success versus failure at the
10//! boundary unless a higher layer mediates timing. For an opt-in semantic decrypt surface
11//! (`Ok` versus `AuthenticationFailed` without plaintext on failure), see
12//! [`crate::AeadDecryptSemantic`] and workspace ADR `docs/adr/003-aead-decrypt-layers.md`.
13
14#[cfg(feature = "alloc")]
15use alloc::string::ToString;
16
17use subtle::ConstantTimeEq;
18
19use crate::error::Result;
20
21/// Timing attack prevention validator
22///
23/// This validator provides utilities to prevent timing attacks by ensuring
24/// constant-time operations where necessary.
25#[cfg(feature = "alloc")]
26#[derive(Clone)]
27pub struct TimingValidator {
28    // Configuration for timing attack prevention
29    enable_timing_validation: bool,
30}
31
32#[cfg(feature = "alloc")]
33impl TimingValidator {
34    /// Create a new timing validator
35    ///
36    /// # Returns
37    ///
38    /// A new instance of TimingValidator with timing attack prevention enabled.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the validator fails to initialize.
43    pub fn new() -> Result<Self> {
44        Ok(Self {
45            enable_timing_validation: true,
46        })
47    }
48
49    /// Perform constant-time comparison of two byte slices
50    ///
51    /// This function performs a constant-time comparison to prevent timing attacks.
52    /// It returns true if the slices are equal, false otherwise.
53    ///
54    /// # Arguments
55    ///
56    /// * `a` - First byte slice
57    /// * `b` - Second byte slice
58    ///
59    /// # Returns
60    ///
61    /// Returns `true` if the slices are equal, `false` otherwise.
62    /// The comparison is performed in constant time to prevent timing attacks.
63    pub fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
64        if a.len() != b.len() {
65            return false;
66        }
67        a.ct_eq(b).into()
68    }
69
70    /// Constant-time selection between two values
71    ///
72    /// Returns `a` if `choice` is true, `b` if `choice` is false.
73    /// The selection is performed in constant time to prevent timing attacks.
74    ///
75    /// # Arguments
76    ///
77    /// * `choice` - Boolean choice
78    /// * `a` - First value
79    /// * `b` - Second value
80    ///
81    /// # Returns
82    ///
83    /// Returns the selected value in constant time.
84    pub fn constant_time_select<T: Copy>(&self, choice: bool, a: T, b: T) -> T {
85        if choice { a } else { b }
86    }
87
88    /// Constant-time conditional assignment
89    ///
90    /// Assigns `src` to `dst` if `choice` is true, otherwise leaves `dst` unchanged.
91    /// The assignment is performed in constant time.
92    ///
93    /// # Arguments
94    ///
95    /// * `choice` - Boolean choice
96    /// * `dst` - Destination to potentially assign to
97    /// * `src` - Source value to assign
98    pub fn constant_time_assign<T: Copy>(&self, choice: bool, dst: &mut T, src: T) {
99        *dst = self.constant_time_select(choice, src, *dst);
100    }
101
102    /// Constant-time conditional copy
103    ///
104    /// Copies `src` to `dst` if `choice` is true, otherwise leaves `dst` unchanged.
105    /// The copy is performed in constant time.
106    ///
107    /// # Arguments
108    ///
109    /// * `choice` - Boolean choice
110    /// * `dst` - Destination slice
111    /// * `src` - Source slice
112    ///
113    /// # Panics
114    ///
115    /// Panics if the slices have different lengths.
116    pub fn constant_time_copy(&self, choice: bool, dst: &mut [u8], src: &[u8]) {
117        assert_eq!(dst.len(), src.len(), "Slices must have the same length");
118
119        for (d, s) in dst.iter_mut().zip(src.iter()) {
120            *d = self.constant_time_select(choice, *s, *d);
121        }
122    }
123
124    /// Validate that an operation is timing-safe
125    ///
126    /// This function can be used to validate that operations are performed
127    /// in constant time to prevent timing attacks.
128    ///
129    /// # Arguments
130    ///
131    /// * `operation` - Name of the operation being validated
132    ///
133    /// # Returns
134    ///
135    /// Returns `Ok(())` if timing validation is enabled and the operation
136    /// is considered safe, or an error if timing validation fails.
137    pub fn validate_timing_safety(&self, operation: &str) -> Result<()> {
138        if !self.enable_timing_validation {
139            return Ok(());
140        }
141
142        // In a real implementation, this would perform actual timing analysis
143        // For now, we'll just validate that the operation name is not empty
144        if operation.is_empty() {
145            return Err(crate::error::Error::InvalidState {
146                operation: "timing_validation".to_string(),
147                reason: "Operation name cannot be empty".to_string(),
148            });
149        }
150
151        Ok(())
152    }
153
154    /// Enable or disable timing validation
155    ///
156    /// # Arguments
157    ///
158    /// * `enabled` - Whether to enable timing validation
159    pub fn set_timing_validation(&mut self, enabled: bool) {
160        self.enable_timing_validation = enabled;
161    }
162
163    /// Check if timing validation is enabled
164    ///
165    /// # Returns
166    ///
167    /// Returns `true` if timing validation is enabled, `false` otherwise.
168    pub fn is_timing_validation_enabled(&self) -> bool {
169        self.enable_timing_validation
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_timing_validator_creation() {
179        let validator = TimingValidator::new();
180        assert!(
181            validator.is_ok(),
182            "TimingValidator should be created successfully"
183        );
184    }
185
186    #[test]
187    fn test_constant_time_compare() {
188        let validator = TimingValidator::new().unwrap();
189
190        // Test equal slices
191        let a = vec![1, 2, 3, 4];
192        let b = vec![1, 2, 3, 4];
193        assert!(
194            validator.constant_time_compare(&a, &b),
195            "Should return true for equal slices"
196        );
197
198        // Test different slices
199        let c = vec![1, 2, 3, 5];
200        assert!(
201            !validator.constant_time_compare(&a, &c),
202            "Should return false for different slices"
203        );
204
205        // Test different length slices
206        let d = vec![1, 2, 3];
207        assert!(
208            !validator.constant_time_compare(&a, &d),
209            "Should return false for different length slices"
210        );
211    }
212
213    #[test]
214    fn test_constant_time_select() {
215        let validator = TimingValidator::new().unwrap();
216
217        // Test selection with true choice
218        let result = validator.constant_time_select(true, 42, 24);
219        assert_eq!(result, 42, "Should select first value when choice is true");
220
221        // Test selection with false choice
222        let result = validator.constant_time_select(false, 42, 24);
223        assert_eq!(
224            result, 24,
225            "Should select second value when choice is false"
226        );
227    }
228
229    #[test]
230    fn test_constant_time_assign() {
231        let validator = TimingValidator::new().unwrap();
232
233        let mut value = 10;
234
235        // Test assignment with true choice
236        validator.constant_time_assign(true, &mut value, 20);
237        assert_eq!(value, 20, "Should assign new value when choice is true");
238
239        // Test assignment with false choice
240        validator.constant_time_assign(false, &mut value, 30);
241        assert_eq!(value, 20, "Should not change value when choice is false");
242    }
243
244    #[test]
245    fn test_constant_time_copy() {
246        let validator = TimingValidator::new().unwrap();
247
248        let mut dst = vec![1, 2, 3, 4];
249        let src = vec![5, 6, 7, 8];
250
251        // Test copy with true choice
252        validator.constant_time_copy(true, &mut dst, &src);
253        assert_eq!(dst, src, "Should copy source when choice is true");
254
255        // Test copy with false choice
256        let original = dst.clone();
257        validator.constant_time_copy(false, &mut dst, &[9, 10, 11, 12]);
258        assert_eq!(
259            dst, original,
260            "Should not change destination when choice is false"
261        );
262    }
263
264    #[test]
265    fn test_validate_timing_safety() {
266        let validator = TimingValidator::new().unwrap();
267
268        // Test valid operation
269        let result = validator.validate_timing_safety("test_operation");
270        assert!(result.is_ok(), "Should accept valid operation name");
271
272        // Test empty operation name
273        let result = validator.validate_timing_safety("");
274        assert!(result.is_err(), "Should reject empty operation name");
275    }
276
277    #[test]
278    fn test_timing_validation_control() {
279        let mut validator = TimingValidator::new().unwrap();
280
281        // Test initial state
282        assert!(
283            validator.is_timing_validation_enabled(),
284            "Timing validation should be enabled by default"
285        );
286
287        // Test disabling
288        validator.set_timing_validation(false);
289        assert!(
290            !validator.is_timing_validation_enabled(),
291            "Timing validation should be disabled"
292        );
293
294        // Test enabling
295        validator.set_timing_validation(true);
296        assert!(
297            validator.is_timing_validation_enabled(),
298            "Timing validation should be enabled"
299        );
300    }
301}