sosecrets_rs/
secret.rs

1//! # `sosecrets-rs`
2//! `sosecret-rs` is a Rust crate providing a Secret type for managing secret values with exposure control.
3//! It aims to enhance security by allowing controlled exposure of sensitive information.
4//!
5//! # Features
6//! Exposure Control: Secret values can only be exposed a limited number of times, preventing unintentional information leaks. This is guaranteed at compile time.
7//! Zeroization: If configured with the zeroize feature, secrets are zeroized upon reaching their maximum exposure count.
8//! Cloneable Secrets: With the cloneable-secret feature, Secret values can be cloned if the underlying type implements the CloneableSecret trait.
9//! Debugging Secrets: The debug-secret feature enables the debugging of Secret values if the underlying type implements the DebugSecret trait.
10
11use core::{
12    marker::PhantomData,
13    mem::{forget, ManuallyDrop},
14    ops::{Add, Deref, Drop},
15};
16
17use crate::traits::ExposeSecret;
18pub use typenum;
19use typenum::{IsLessOrEqual, Sum, True, Unsigned, U0, U1};
20
21#[cfg(feature = "zeroize")]
22use zeroize::Zeroize;
23
24#[cfg(feature = "cloneable-secret")]
25use crate::traits::CloneableSecret;
26
27#[cfg(feature = "debug-secret")]
28use crate::traits::DebugSecret;
29
30type AddU1<A> = <A as core::ops::Add<U1>>::Output;
31
32/// The `Secret` struct represents a secure container for managing sensitive values with built-in exposure control.
33///
34/// It provides a mechanism to limit the number of times a secret can be exposed at compile time.
35/// Exposure of secret is strictly limited to a lexical scope.
36/// The behavior of the `Secret` type is customizable through various features, such as zeroization, cloning support, and debugging capabilities.
37///
38/// ## Type Parameters
39/// - `T`: The underlying type of the secret.
40/// - `MEC`: Maximum Exposure Count, a type-level unsigned integer, with `typenum::Unsigned` bound, indicating the maximum allowed exposures for the secret.
41/// - `EC`: Exposure Count, a type-level unsigned integer, with `typenum::Unsigned` bound, representing the current exposure count of the secret.
42/// It is limited by the Maximum Exposure Count, if `EC` is greater than `MEC`, the program cannot be compiled.
43///
44/// ## Features
45/// - `zeroize` (optional): If enabled, the secret will be automatically zeroized (cleared) after reaching its maximum exposure count.
46/// - `cloneable-secret` (optional): If enabled, the underlying type `T` must implement the `sosecrets_rs::traits::CloneableSecret` trait, allowing the secret to be cloned.
47/// - `debug-secret` (optional): If enabled, the underlying type `T` must implement the `sosecrets_rs::traits::DebugSecret` trait, enabling debugging of the secret.
48#[repr(transparent)]
49pub struct Secret<
50    #[cfg(feature = "zeroize")] T: Zeroize,
51    #[cfg(not(feature = "zeroize"))] T,
52    MEC: Unsigned,
53    EC: Add<U1> + IsLessOrEqual<MEC, Output = True> + Unsigned = U0,
54>(ManuallyDrop<T>, PhantomData<(MEC, EC)>);
55
56/// Type representing an exposed secret value. It holds an annotated (`'brand`) [invariant](https://doc.rust-lang.org/nomicon/subtyping.html#variance) lifetime.
57pub struct ExposedSecret<'brand, T>(T, PhantomData<fn(&'brand ()) -> &'brand ()>);
58
59impl<#[cfg(feature = "zeroize")] T: Zeroize, #[cfg(not(feature = "zeroize"))] T, MEC: Unsigned>
60    Secret<T, MEC, U0>
61where
62    U0: IsLessOrEqual<MEC, Output = True>,
63{
64    /// Creates a new `Secret` instance with the specified value.
65    ///
66    /// # Parameters
67    /// - `value`: The initial value to be stored in the secret.
68    ///
69    /// # Returns
70    /// A new `Secret` instance initialized with the provided value.
71    ///
72    /// # Examples
73    /// ```rust
74    /// use sosecrets_rs::prelude::*;
75    /// use typenum::U5;
76    ///
77    /// // Create a new secret with a maximum exposure count of 5
78    /// let secret = Secret::<_, U5>::new("my_secret_value".to_string());
79    /// ```
80    #[inline(always)]
81    pub const fn new(value: T) -> Self {
82        Self(ManuallyDrop::new(value), PhantomData)
83    }
84
85    /// Creates a new `Secret` instance by generating the value with a closure.
86    ///
87    /// # Parameters
88    /// - `closure`: A closure that generates the initial value to be stored in the secret.
89    ///
90    /// # Returns
91    /// A new `Secret` instance initialized with the value produced by the closure.
92    ///
93    /// # Examples
94    /// ```rust
95    /// use sosecrets_rs::prelude::*;
96    /// use typenum::U3;
97    ///
98    /// // Create a new secret with a maximum exposure count of 3 using a closure
99    /// let secret = Secret::<_, U3>::new_with(|| "generated_secret_value".to_string());
100    /// ```
101    #[inline(always)]
102    pub fn new_with<ClosureType>(closure: ClosureType) -> Self
103    where
104        ClosureType: FnOnce() -> T,
105    {
106        Self(ManuallyDrop::new(closure()), PhantomData)
107    }
108}
109
110impl<
111        'max,
112        #[cfg(feature = "zeroize")] T: Zeroize,
113        #[cfg(not(feature = "zeroize"))] T,
114        MEC: Unsigned,
115        EC: Add<U1> + Unsigned + IsLessOrEqual<MEC, Output = True>,
116    > ExposeSecret<'max, &'max T, MEC, EC> for Secret<T, MEC, EC>
117{
118    type Exposed<'brand> = ExposedSecret<'brand, &'brand T>
119    where
120        'max: 'brand;
121
122    type Next = Secret<T, MEC, Sum<EC, U1>>
123    where
124        EC: Add<U1> + Unsigned + IsLessOrEqual<MEC, Output = True>,
125        Sum<EC, U1>: Unsigned + IsLessOrEqual<MEC, Output = True> + Add<U1>;
126
127    /// Exposes the secret value to a closure, consuming the `Secret`.
128    /// At compile time, if the type parameter `EC` 'is greater than' `MEC`, calling this method will be a compile error.
129    ///
130    /// Example:
131    /// ```rust
132    /// use sosecrets_rs::{prelude::{Secret, typenum::U2}, traits::ExposeSecret};
133    ///
134    /// struct UseSecret {
135    ///     inner: i32,
136    /// }
137    ///
138    /// impl UseSecret {
139    ///
140    ///     fn new(v: i32) -> Self {
141    ///         Self {
142    ///             inner: v,
143    ///         }
144    ///     }
145    /// }
146    ///
147    /// let new_secret: Secret<_, U2> = Secret::new(69);
148    ///
149    /// let (new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
150    ///     let returned_value = UseSecret::new(*exposed_secret);
151    ///     returned_value
152    /// });
153    /// assert_eq!(69, returned_value.inner);
154    ///
155    /// let (_new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
156    ///     let returned_value = UseSecret::new(*exposed_secret);
157    ///     returned_value
158    /// });
159    /// assert_eq!(69, returned_value.inner);
160    /// ```
161    ///
162    /// Example (this will **not** compile):
163    /// ```rust,compile_fail
164    /// use sosecrets_rs::{prelude::{Secret, typenum::U2}, traits::ExposeSecret};
165    ///
166    /// struct UseSecret {
167    ///     inner: i32,
168    /// }
169    ///
170    /// impl UseSecret {
171    ///
172    ///     fn new(v: i32) -> Self {
173    ///         Self {
174    ///             inner: v,
175    ///         }
176    ///     }
177    /// }
178    ///
179    /// let (new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
180    ///     let returned_value = UseSecret::new(*exposed_secret);
181    ///     returned_value
182    /// });
183    /// assert_eq!(69, returned_value.inner);
184    ///
185    /// let (_new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
186    ///     let returned_value = UseSecret::new(*exposed_secret);
187    ///     returned_value
188    /// });
189    /// assert_eq!(69, returned_value.inner);
190    ///
191    /// let (_new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
192    ///     let returned_value = UseSecret::new(*exposed_secret);
193    ///     returned_value
194    /// });
195    /// ```
196    ///
197    #[inline(always)]
198    fn expose_secret<ReturnType, ClosureType>(
199        mut self,
200        scope: ClosureType,
201    ) -> (Secret<T, MEC, AddU1<EC>>, ReturnType)
202    where
203        AddU1<EC>: Add<U1> + Unsigned + IsLessOrEqual<MEC, Output = True>,
204        for<'brand> ClosureType: FnOnce(ExposedSecret<'brand, &'brand T>) -> ReturnType,
205    {
206        let returned_value = scope(ExposedSecret(&self.0, PhantomData));
207        // SAFETY: Since compile error prevents constructing a `Secret` with `EC` > `MEC`,
208        // and it is not possible to call `expose_secret(...)`
209        // when `Secret` is maximally exposed to access **private** `self.0` field,
210        // therefore, this is safe.
211        let inner = ManuallyDrop::new(unsafe { ManuallyDrop::take(&mut self.0) });
212        forget(self);
213        (Secret(inner, PhantomData), returned_value)
214    }
215}
216
217impl<T> Deref for ExposedSecret<'_, &'_ T> {
218    type Target = T;
219
220    #[inline(always)]
221    fn deref(&self) -> &T {
222        self.0
223    }
224}
225
226impl<#[cfg(feature = "zeroize")] T: Zeroize, #[cfg(not(feature = "zeroize"))] T, MEC, EC> Drop
227    for Secret<T, MEC, EC>
228where
229    MEC: Unsigned,
230    EC: Add<U1> + Unsigned + IsLessOrEqual<MEC, Output = True>,
231{
232    #[inline(always)]
233    fn drop(&mut self) {
234        // SAFETY: Since compile error prevents constructing a `Secret` with `EC` > `MEC`,
235        // and it is not possible to call `expose_secret(...)`
236        // when `Secret` is maximally exposed to access **private** `self.0` field,
237        // therefore, this is safe.
238        let mut _inner = unsafe { ManuallyDrop::take(&mut self.0) };
239        #[cfg(feature = "zeroize")]
240        _inner.zeroize();
241    }
242}
243
244#[cfg(feature = "cloneable-secret")]
245impl<T, MEC, EC> Clone for Secret<T, MEC, EC>
246where
247    T: CloneableSecret,
248    MEC: Unsigned,
249    EC: Unsigned + Add<U1> + IsLessOrEqual<MEC, Output = True>,
250{
251    #[inline(always)]
252    fn clone(&self) -> Self {
253        Self(self.0.clone(), PhantomData)
254    }
255}
256
257#[cfg(feature = "debug-secret")]
258impl<T, MEC, EC> core::fmt::Debug for Secret<T, MEC, EC>
259where
260    T: DebugSecret,
261    MEC: Unsigned,
262    EC: Unsigned + Add<U1> + IsLessOrEqual<MEC, Output = True>,
263{
264    #[inline(always)]
265    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
266        f.write_str("Secret<")?;
267        T::debug_secret(f)?;
268        f.write_str(">")
269    }
270}