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}