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::{
18    Choice,
19    ConditionallySelectable,
20    ConstantTimeEq,
21};
22
23use crate::error::Result;
24
25/// Timing attack prevention validator
26///
27/// This validator provides utilities to prevent timing attacks by ensuring
28/// constant-time operations where necessary.
29#[cfg(feature = "alloc")]
30#[derive(Clone)]
31pub struct TimingValidator {
32    // Configuration for timing attack prevention
33    enable_timing_validation: bool,
34}
35
36#[cfg(feature = "alloc")]
37impl TimingValidator {
38    /// Create a new timing validator
39    ///
40    /// # Returns
41    ///
42    /// A new instance of TimingValidator with timing attack prevention enabled.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if the validator fails to initialize.
47    pub fn new() -> Result<Self> {
48        Ok(Self {
49            enable_timing_validation: true,
50        })
51    }
52
53    /// Perform constant-time comparison of two byte slices
54    ///
55    /// This function performs a constant-time comparison to prevent timing attacks.
56    /// It returns true if the slices are equal, false otherwise.
57    ///
58    /// # Arguments
59    ///
60    /// * `a` - First byte slice
61    /// * `b` - Second byte slice
62    ///
63    /// # Returns
64    ///
65    /// Returns `true` if the slices are equal, `false` otherwise.
66    /// The comparison is performed in constant time to prevent timing attacks.
67    pub fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
68        if a.len() != b.len() {
69            return false;
70        }
71        a.ct_eq(b).into()
72    }
73
74    /// Constant-time selection between two values
75    ///
76    /// Returns `a` if `choice` is true, `b` if `choice` is false.
77    /// Selection is branchless: a full-width mask is derived from `choice` via
78    /// `subtle::ConditionallySelectable` (no secret-dependent branch). Callers must derive
79    /// `choice` itself in constant time.
80    ///
81    /// # Arguments
82    ///
83    /// * `choice` - Boolean choice
84    /// * `a` - First value
85    /// * `b` - Second value
86    ///
87    /// # Returns
88    ///
89    /// Returns the selected value in constant time.
90    pub fn constant_time_select<T: ConditionallySelectable>(&self, choice: bool, a: T, b: T) -> T {
91        T::conditional_select(&b, &a, Choice::from(u8::from(choice)))
92    }
93
94    /// Constant-time conditional assignment
95    ///
96    /// Assigns `src` to `dst` if `choice` is true, otherwise leaves `dst` unchanged.
97    /// Branchless: dispatches to `subtle::ConditionallySelectable::conditional_assign`, deriving a
98    /// full-width mask from `choice` instead of branching. Callers must derive `choice` itself in
99    /// constant time.
100    ///
101    /// # Arguments
102    ///
103    /// * `choice` - Boolean choice
104    /// * `dst` - Destination to potentially assign to
105    /// * `src` - Source value to assign
106    pub fn constant_time_assign<T: ConditionallySelectable>(
107        &self,
108        choice: bool,
109        dst: &mut T,
110        src: T,
111    ) {
112        dst.conditional_assign(&src, Choice::from(u8::from(choice)));
113    }
114
115    /// Constant-time conditional copy
116    ///
117    /// Copies `src` to `dst` if `choice` is true, otherwise leaves `dst` unchanged.
118    /// Branchless: each byte is assigned via `subtle::ConditionallySelectable::conditional_assign`,
119    /// deriving a full-width mask from `choice` instead of branching. Callers must derive `choice`
120    /// itself in constant time.
121    ///
122    /// # Arguments
123    ///
124    /// * `choice` - Boolean choice
125    /// * `dst` - Destination slice
126    /// * `src` - Source slice
127    ///
128    /// # Panics
129    ///
130    /// Panics if the slices have different lengths.
131    pub fn constant_time_copy(&self, choice: bool, dst: &mut [u8], src: &[u8]) {
132        assert_eq!(dst.len(), src.len(), "Slices must have the same length");
133
134        let choice = Choice::from(u8::from(choice));
135        for (d, s) in dst.iter_mut().zip(src.iter()) {
136            d.conditional_assign(s, choice);
137        }
138    }
139
140    /// Validate that an operation is timing-safe
141    ///
142    /// This function can be used to validate that operations are performed
143    /// in constant time to prevent timing attacks.
144    ///
145    /// # Arguments
146    ///
147    /// * `operation` - Name of the operation being validated
148    ///
149    /// # Returns
150    ///
151    /// Returns `Ok(())` if timing validation is enabled and the operation
152    /// is considered safe, or an error if timing validation fails.
153    pub fn validate_timing_safety(&self, operation: &str) -> Result<()> {
154        if !self.enable_timing_validation {
155            return Ok(());
156        }
157
158        // In a real implementation, this would perform actual timing analysis
159        // For now, we'll just validate that the operation name is not empty
160        if operation.is_empty() {
161            return Err(crate::error::Error::InvalidState {
162                operation: "timing_validation".to_string(),
163                reason: "Operation name cannot be empty".to_string(),
164            });
165        }
166
167        Ok(())
168    }
169
170    /// Enable or disable timing validation
171    ///
172    /// # Arguments
173    ///
174    /// * `enabled` - Whether to enable timing validation
175    pub fn set_timing_validation(&mut self, enabled: bool) {
176        self.enable_timing_validation = enabled;
177    }
178
179    /// Check if timing validation is enabled
180    ///
181    /// # Returns
182    ///
183    /// Returns `true` if timing validation is enabled, `false` otherwise.
184    pub fn is_timing_validation_enabled(&self) -> bool {
185        self.enable_timing_validation
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_timing_validator_creation() {
195        let validator = TimingValidator::new();
196        assert!(
197            validator.is_ok(),
198            "TimingValidator should be created successfully"
199        );
200    }
201
202    #[test]
203    fn test_constant_time_compare() {
204        let validator = TimingValidator::new().unwrap();
205
206        // Test equal slices
207        let a = vec![1, 2, 3, 4];
208        let b = vec![1, 2, 3, 4];
209        assert!(
210            validator.constant_time_compare(&a, &b),
211            "Should return true for equal slices"
212        );
213
214        // Test different slices
215        let c = vec![1, 2, 3, 5];
216        assert!(
217            !validator.constant_time_compare(&a, &c),
218            "Should return false for different slices"
219        );
220
221        // Test different length slices
222        let d = vec![1, 2, 3];
223        assert!(
224            !validator.constant_time_compare(&a, &d),
225            "Should return false for different length slices"
226        );
227    }
228
229    #[test]
230    fn test_constant_time_select() {
231        let validator = TimingValidator::new().unwrap();
232
233        // Test selection with true choice
234        let result = validator.constant_time_select(true, 42, 24);
235        assert_eq!(result, 42, "Should select first value when choice is true");
236
237        // Test selection with false choice
238        let result = validator.constant_time_select(false, 42, 24);
239        assert_eq!(
240            result, 24,
241            "Should select second value when choice is false"
242        );
243    }
244
245    #[test]
246    fn test_constant_time_assign() {
247        let validator = TimingValidator::new().unwrap();
248
249        let mut value = 10;
250
251        // Test assignment with true choice
252        validator.constant_time_assign(true, &mut value, 20);
253        assert_eq!(value, 20, "Should assign new value when choice is true");
254
255        // Test assignment with false choice
256        validator.constant_time_assign(false, &mut value, 30);
257        assert_eq!(value, 20, "Should not change value when choice is false");
258    }
259
260    #[test]
261    fn test_constant_time_copy() {
262        let validator = TimingValidator::new().unwrap();
263
264        let mut dst = vec![1, 2, 3, 4];
265        let src = vec![5, 6, 7, 8];
266
267        // Test copy with true choice
268        validator.constant_time_copy(true, &mut dst, &src);
269        assert_eq!(dst, src, "Should copy source when choice is true");
270
271        // Test copy with false choice
272        let original = dst.clone();
273        validator.constant_time_copy(false, &mut dst, &[9, 10, 11, 12]);
274        assert_eq!(
275            dst, original,
276            "Should not change destination when choice is false"
277        );
278    }
279
280    #[test]
281    fn test_validate_timing_safety() {
282        let validator = TimingValidator::new().unwrap();
283
284        // Test valid operation
285        let result = validator.validate_timing_safety("test_operation");
286        assert!(result.is_ok(), "Should accept valid operation name");
287
288        // Test empty operation name
289        let result = validator.validate_timing_safety("");
290        assert!(result.is_err(), "Should reject empty operation name");
291    }
292
293    /// Marks its output when selected through the branchless `subtle` path — the if/else
294    /// implementation returns the operand unmarked, so this test fails on any branch-based select.
295    #[derive(Clone, Copy, Debug, PartialEq)]
296    struct SelectCanary(u32);
297    const CANARY_MARK: u32 = 0x8000_0000;
298    impl ConditionallySelectable for SelectCanary {
299        fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
300            SelectCanary(u32::conditional_select(&a.0, &b.0, choice) | CANARY_MARK)
301        }
302    }
303
304    #[test]
305    fn select_and_assign_dispatch_through_subtle_not_a_branch() {
306        let v = TimingValidator::new().unwrap();
307        let r = v.constant_time_select(true, SelectCanary(1), SelectCanary(2));
308        assert_eq!(
309            r,
310            SelectCanary(1 | CANARY_MARK),
311            "constant_time_select must route through ConditionallySelectable (mask), not if/else"
312        );
313        let r = v.constant_time_select(false, SelectCanary(1), SelectCanary(2));
314        assert_eq!(r, SelectCanary(2 | CANARY_MARK));
315
316        let mut d = SelectCanary(7);
317        v.constant_time_assign(true, &mut d, SelectCanary(9));
318        assert_eq!(
319            d,
320            SelectCanary(9 | CANARY_MARK),
321            "constant_time_assign must route through conditional_assign"
322        );
323    }
324
325    #[test]
326    fn test_timing_validation_control() {
327        let mut validator = TimingValidator::new().unwrap();
328
329        // Test initial state
330        assert!(
331            validator.is_timing_validation_enabled(),
332            "Timing validation should be enabled by default"
333        );
334
335        // Test disabling
336        validator.set_timing_validation(false);
337        assert!(
338            !validator.is_timing_validation_enabled(),
339            "Timing validation should be disabled"
340        );
341
342        // Test enabling
343        validator.set_timing_validation(true);
344        assert!(
345            validator.is_timing_validation_enabled(),
346            "Timing validation should be enabled"
347        );
348    }
349}