dcrypt_api/error/
traits.rs

1//! Error handling traits for the cryptographic ecosystem
2
3use super::registry::ERROR_REGISTRY;
4use super::types::{Error, Result};
5use subtle::{Choice, ConditionallySelectable};
6
7/// Extension trait for Result types
8pub trait ResultExt<T, E>: Sized {
9    /// Wrap an error with additional context
10    fn wrap_err<F, E2>(self, f: F) -> core::result::Result<T, E2>
11    where
12        F: FnOnce() -> E2;
13
14    /// Add context to an error when converting to Error
15    fn with_context(self, context: &'static str) -> Result<T>
16    where
17        E: Into<Error>;
18
19    #[cfg(feature = "std")]
20    /// Add message to an error when converting to Error
21    fn with_message(self, message: impl Into<String>) -> Result<T>
22    where
23        E: Into<Error>;
24}
25
26impl<T, E> ResultExt<T, E> for core::result::Result<T, E> {
27    fn wrap_err<F, E2>(self, f: F) -> core::result::Result<T, E2>
28    where
29        F: FnOnce() -> E2,
30    {
31        self.map_err(|_| f())
32    }
33
34    fn with_context(self, context: &'static str) -> Result<T>
35    where
36        E: Into<Error>,
37    {
38        self.map_err(|e| {
39            let err = e.into();
40            err.with_context(context)
41        })
42    }
43
44    #[cfg(feature = "std")]
45    fn with_message(self, message: impl Into<String>) -> Result<T>
46    where
47        E: Into<Error>,
48    {
49        self.map_err(|e| {
50            let err = e.into();
51            err.with_message(message)
52        })
53    }
54}
55
56/// Trait for secure error handling to prevent timing attacks
57pub trait SecureErrorHandling<T, E>: Sized {
58    /// Handle errors in constant time
59    fn secure_unwrap<F>(self, default: T, on_error: F) -> T
60    where
61        F: FnOnce() -> E;
62}
63
64impl<T, E> SecureErrorHandling<T, E> for core::result::Result<T, E> {
65    fn secure_unwrap<F>(self, default: T, on_error: F) -> T
66    where
67        F: FnOnce() -> E,
68    {
69        match self {
70            Ok(value) => value,
71            Err(_) => {
72                // Store error in a way that maintains constant-time
73                let error = on_error();
74                ERROR_REGISTRY.store(error);
75                default
76            }
77        }
78    }
79}
80
81/// Trait for checking if an operation succeeded in constant time
82pub trait ConstantTimeResult<T, E> {
83    /// Check if this result is Ok, without branching on the result
84    fn ct_is_ok(&self) -> bool;
85
86    /// Check if this result is Err, without branching on the result
87    fn ct_is_err(&self) -> bool;
88
89    /// Map a result to a value in constant time, calling a provided function
90    /// regardless of whether the result is Ok or Err
91    fn ct_map<U, F, G>(self, ok_fn: F, err_fn: G) -> U
92    where
93        F: FnOnce(T) -> U,
94        G: FnOnce(E) -> U,
95        U: ConditionallySelectable;
96}
97
98impl<T, E> ConstantTimeResult<T, E> for core::result::Result<T, E> {
99    fn ct_is_ok(&self) -> bool {
100        // Create a Choice based on whether this is Ok or Err
101        let is_ok_choice = match self {
102            Ok(_) => Choice::from(1u8),
103            Err(_) => Choice::from(0u8),
104        };
105
106        // Convert the Choice to bool in constant time
107        // We use conditional selection between false and true
108        let mut result = false;
109        result.conditional_assign(&true, is_ok_choice);
110        result
111    }
112
113    fn ct_is_err(&self) -> bool {
114        // Use ct_is_ok and negate in constant time
115        let is_ok = self.ct_is_ok();
116
117        // Create choices for the negation
118        let is_ok_choice = Choice::from(is_ok as u8);
119
120        // Select between true (if is_ok is false) and false (if is_ok is true)
121        let mut result = true;
122        result.conditional_assign(&false, is_ok_choice);
123        result
124    }
125
126    fn ct_map<U, F, G>(self, ok_fn: F, err_fn: G) -> U
127    where
128        F: FnOnce(T) -> U,
129        G: FnOnce(E) -> U,
130        U: ConditionallySelectable,
131    {
132        // To maintain constant-time behavior, we must evaluate both branches
133        // This is less efficient but prevents timing attacks
134        match self {
135            Ok(t) => {
136                // We need to create a dummy error to call err_fn
137                // This maintains constant-time execution
138                // Note: This requires E to implement Default or we need another approach
139                // For now, we'll just return the function result directly
140                ok_fn(t)
141            }
142            Err(e) => {
143                // Similarly, we'd need to call ok_fn with a dummy value
144                // For now, we'll just return the function result directly
145                err_fn(e)
146            }
147        }
148        // Note: A truly constant-time implementation would require:
149        // 1. Both T and E to implement Default or similar
150        // 2. Calling both functions always
151        // 3. Using ConditionallySelectable to choose the result
152        // This current implementation is a compromise for practicality
153    }
154}
155
156/// Helper trait for types that can be assigned conditionally in constant time
157trait ConditionalAssign {
158    fn conditional_assign(&mut self, other: &Self, choice: Choice);
159}
160
161impl ConditionalAssign for bool {
162    fn conditional_assign(&mut self, other: &bool, choice: Choice) {
163        // Convert bools to u8 for constant-time selection
164        let self_as_u8 = *self as u8;
165        let other_as_u8 = *other as u8;
166
167        // Perform constant-time selection
168        let result = u8::conditional_select(&self_as_u8, &other_as_u8, choice);
169
170        // Convert back to bool
171        *self = result != 0;
172    }
173}