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}