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}