sosecrets_rs/runtime/
secret.rs

1use core::{
2    cell::Cell,
3    convert::Infallible,
4    fmt::Debug,
5    marker::PhantomData,
6    ops::{Deref, Drop},
7};
8
9use crate::{
10    runtime::{error, traits},
11    traits::{ChooseMinimallyRepresentableUInt, __private},
12    types::NumericalZeroSizedType,
13};
14use typenum::{IsGreater, True, Unsigned, U0};
15#[cfg(feature = "zeroize")]
16use zeroize::Zeroize;
17
18#[cfg(feature = "cloneable-secret")]
19use crate::traits::CloneableSecret;
20
21#[cfg(feature = "debug-secret")]
22use crate::traits::DebugSecret;
23
24/// A runtime secret with optional zeroization for the type `T` and exposure count tracking. It is the runtime version of `Secret<T, MEC, EC>`.
25pub struct RTSecret<
26    #[cfg(feature = "zeroize")] T: Zeroize,
27    #[cfg(not(feature = "zeroize"))] T,
28    MEC: ChooseMinimallyRepresentableUInt,
29>(
30    /// `T` is the type of the value that is meant to be kept as a secret,
31    T,
32    /// The type of the exposure counter, can be either `u8`, `u16`, `u32` or `u64`.
33    Cell<<MEC as ChooseMinimallyRepresentableUInt>::Output>,
34);
35
36/// A wrapper type representing an exposed secret.
37///
38/// The `RTExposedSecret` struct is a wrapper type representing an exposed secret.
39/// It holds an annotated (`'brand`) [invariant](https://doc.rust-lang.org/nomicon/subtyping.html#variance) lifetime, indicating the lifetime of the wrapper type, which is strictly a subtype of the lifetime of the secret and cannot be coerced to be any other lifetime.
40pub struct RTExposedSecret<'brand, T>(T, PhantomData<fn(&'brand ()) -> &'brand ()>);
41
42/// A convenience alias for `RTSecret` with a secret of type `T` that does **not** conduct any exposure count checking, i.e. the secret can be exposed infinitely many times.
43/// It is meant to function almost identically to `secrecy::Secret`, except that the signature of `.expose_secret(...)` method is different.
44pub type SecrecySecret<T> = RTSecret<T, NumericalZeroSizedType>;
45
46impl<'secret, #[cfg(feature = "zeroize")] T: Zeroize, #[cfg(not(feature = "zeroize"))] T>
47    traits::RTExposeSecret<'secret, &'secret T> for SecrecySecret<T>
48{
49    type Error = Infallible;
50
51    type Exposed<'brand> = RTExposedSecret<'brand, &'brand T>
52    where
53        'secret: 'brand;
54
55    /// Exposes the secret **without** any runtime checking that the exposure count is not more than the maximally allowed exposure count represented by the type parameter `MEC`.
56    /// Note: It is impossible to return the 'exposed secret' as the return value of the closure.
57    ///
58    /// Example:
59    /// ```rust
60    /// use sosecrets_rs::{
61    ///     prelude::{typenum::U2, SecrecySecret, RTSecret},
62    ///     runtime::traits::RTExposeSecret,
63    /// };
64    /// #[cfg(feature = "zeroize")]
65    /// use zeroize::Zeroize;
66    ///
67    /// struct A {
68    ///     inner: i32,
69    /// }
70    ///
71    /// #[cfg(feature = "zeroize")]
72    /// impl Zeroize for A {
73    ///     fn zeroize(&mut self) {
74    ///         self.inner.zeroize()
75    ///     }
76    /// }
77    ///
78    /// let secret_one = SecrecySecret::<A>::new(A { inner: 69 });
79    /// let returned_value = secret_one.expose_secret(|exposed_secret| A { inner: (*exposed_secret).inner + 1});
80    /// assert_eq!(returned_value.inner, 70);
81    /// ```
82    ///
83    /// Example (this does **NOT** compile):
84    /// ```compile_fail
85    /// use sosecrets_rs::{
86    ///     prelude::{typenum::U2, SecrecySecret, RTSecret},
87    ///     runtime::traits::RTExposeSecret,
88    /// };
89    /// #[cfg(feature = "zeroize")]
90    /// use zeroize::Zeroize;
91    ///
92    /// struct A {
93    ///     inner: i32,
94    /// }
95    ///
96    /// #[cfg(feature = "zeroize")]
97    /// impl Zeroize for A {
98    ///     fn zeroize(&mut self) {
99    ///         self.inner.zeroize()
100    ///     }
101    /// }
102    ///
103    /// let secret_one = SecrecySecret::<A>::new(A { inner: 69 });
104    /// let _ = secret_one.expose_secret(|exposed_secret| exposed_secret);
105    /// let _ = secret_one.expose_secret(|exposed_secret| *exposed_secret); // Only if T is not `Copy`
106    /// ```
107    ///
108    /// # Parameters
109    /// - `self`.
110    /// - `scope`: A closure that takes the exposed secret and returns a value of the `ReturnType`.
111    /// # Returns
112    /// A value of type `ReturnType` which is the type of the returned value from the closure named `scope`.
113    #[inline(always)]
114    fn expose_secret<ReturnType, ClosureType>(&self, scope: ClosureType) -> ReturnType
115    where
116        for<'brand> ClosureType: FnOnce(RTExposedSecret<'brand, &'brand T>) -> ReturnType,
117    {
118        scope(RTExposedSecret(&self.0, PhantomData))
119    }
120
121    /// Exposes the secret **without** any runtime checking that the exposure count is not more than the maximally allowed exposure count represented by the type parameter `MEC`.
122    /// Note: It is impossible to return the 'exposed secret' as the return value of the closure.
123    ///
124    /// Example:
125    /// ```rust
126    /// use sosecrets_rs::{
127    ///     prelude::{typenum::U2, SecrecySecret, RTSecret},
128    ///     runtime::traits::RTExposeSecret,
129    /// };
130    /// #[cfg(feature = "zeroize")]
131    /// use zeroize::Zeroize;
132    ///
133    /// struct A {
134    ///     inner: i32,
135    /// }
136    ///
137    /// #[cfg(feature = "zeroize")]
138    /// impl Zeroize for A {
139    ///     fn zeroize(&mut self) {
140    ///         self.inner.zeroize()
141    ///     }
142    /// }
143    ///
144    /// let secret_one = SecrecySecret::<A>::new(A { inner: 69 });
145    /// let returned_value = secret_one.try_expose_secret(|exposed_secret| A { inner: (*exposed_secret).inner + 1});
146    /// assert!(returned_value.is_ok());
147    /// ```
148    ///
149    /// Example (this does **NOT** compile):
150    /// ```compile_fail
151    /// use sosecrets_rs::{
152    ///     prelude::typenum::U2,
153    ///     runtime::{secret::RTSecret, traits::RTExposeSecret},
154    /// };
155    /// #[cfg(feature = "zeroize")]
156    /// use zeroize::Zeroize;
157    ///
158    /// struct A {
159    ///     inner: i32,
160    /// }
161    ///
162    /// #[cfg(feature = "zeroize")]
163    /// impl Zeroize for A {
164    ///     fn zeroize(&mut self) {
165    ///         self.inner.zeroize()
166    ///     }
167    /// }
168    ///
169    /// let secret_one = SecrecySecret::<A>::new(A { inner: 69 });
170    /// let _ = secret_one.try_expose_secret(|exposed_secret| exposed_secret);
171    /// let _ = secret_one.try_expose_secret(|exposed_secret| *exposed_secret); // Only if T is not `Copy`
172    /// ```
173    ///
174    /// # Parameters
175    /// - `self`.
176    /// - `scope`: A closure that takes the exposed secret and returns a value of the `ReturnType`.
177    ///
178    /// # Returns
179    /// An `Ok` variant containing the value of type `ReturnType` which is the type of the returned value from the closure named `scope`.
180    /// This function can **never** fail because no check is done.
181    #[inline(always)]
182    fn try_expose_secret<ReturnType, ClosureType>(
183        &self,
184        scope: ClosureType,
185    ) -> Result<ReturnType, Infallible>
186    where
187        for<'brand> ClosureType: FnOnce(RTExposedSecret<'brand, &'brand T>) -> ReturnType,
188    {
189        Ok(scope(RTExposedSecret(&self.0, PhantomData)))
190    }
191}
192
193impl<'brand, T> Deref for RTExposedSecret<'brand, &'brand T> {
194    type Target = T;
195    fn deref(&self) -> &Self::Target {
196        self.0
197    }
198}
199
200impl<
201        #[cfg(feature = "zeroize")] T: Zeroize,
202        #[cfg(not(feature = "zeroize"))] T,
203        MEC: ChooseMinimallyRepresentableUInt,
204    > RTSecret<T, MEC>
205{
206    /// Creates a new `RTSecret` with the provided secret value `t`.
207    ///
208    /// # Parameters
209    /// - `t`: The secret value.
210    ///
211    /// # Returns
212    /// The newly created `RTSecret`.
213    #[inline(always)]
214    pub const fn new(t: T) -> Self {
215        Self(
216            t,
217            Cell::new(<MEC as ChooseMinimallyRepresentableUInt>::ZERO),
218        )
219    }
220
221    /// Creates a new `RTSecret` with the provided secret value returned by the closure `f`.
222    ///
223    /// # Parameters
224    /// - `f`: A closure that returns the secret value.
225    ///
226    /// # Returns
227    /// The newly created `RTSecret`.
228    #[inline(always)]
229    pub fn new_with(f: impl FnOnce() -> T) -> Self {
230        Self(
231            f(),
232            Cell::new(<MEC as ChooseMinimallyRepresentableUInt>::ZERO),
233        )
234    }
235
236    /// Retrieves the current exposure count of the secret and returns it as an unsigned integer.
237    ///
238    /// Note: The actual unsigned integer type returned depends on the type-level value of the type parameter `MEC`,
239    /// it is the minimal representable Rust's unsigned integer type that can represent the value.
240    /// e.g. if `MEC` is `typenum::consts::U67`, then the returned type is `u8`.
241    #[inline(always)]
242    pub fn exposure_count(&self) -> <MEC as ChooseMinimallyRepresentableUInt>::Output {
243        self.1.get()
244    }
245
246    #[inline(always)]
247    fn can_expose(&self) -> bool
248    where
249        MEC: typenum::Unsigned,
250    {
251        let ec = self.1.get();
252        let mec = MEC::cast_unsigned_to_self_type::<MEC>(__private::SealedToken {});
253        if ec >= mec {
254            return false;
255        };
256        self.1.set(ec + MEC::ONE);
257        true
258    }
259}
260
261impl<
262        'secret,
263        #[cfg(feature = "zeroize")] T: Zeroize,
264        #[cfg(not(feature = "zeroize"))] T,
265        // `IsGreater<U0, Output = True>` so that `RTSecret<T, U0>` cannot call `.expose_secret()`
266        MEC: ChooseMinimallyRepresentableUInt + Unsigned + IsGreater<U0, Output = True> + Debug,
267    > traits::RTExposeSecret<'secret, &'secret T> for RTSecret<T, MEC>
268{
269    type Error = error::ExposeSecretError<MEC>;
270
271    type Exposed<'brand> = RTExposedSecret<'brand, &'brand T>
272    where
273        'secret: 'brand;
274
275    /// Exposes the secret with runtime checking that the exposure count is not more than the maximally allowed exposure count represented by the type parameter `MEC`.
276    /// Note: It is impossible to return the 'exposed secret' as the return value of the closure.
277    ///
278    /// Example:
279    /// ```rust
280    /// use sosecrets_rs::{
281    ///     prelude::typenum::U2,
282    ///     runtime::{secret::RTSecret, traits::RTExposeSecret},
283    /// };
284    /// #[cfg(feature = "zeroize")]
285    /// use zeroize::Zeroize;
286    ///
287    /// struct A {
288    ///     inner: i32,
289    /// }
290    ///
291    /// #[cfg(feature = "zeroize")]
292    /// impl Zeroize for A {
293    ///     fn zeroize(&mut self) {
294    ///         self.inner.zeroize()
295    ///     }
296    /// }
297    ///
298    /// let secret_one = RTSecret::<A, U2>::new(A { inner: 69 });
299    /// let returned_value = secret_one.expose_secret(|exposed_secret| A { inner: (*exposed_secret).inner + 1});
300    /// assert_eq!(returned_value.inner, 70);
301    /// ```
302    ///
303    /// Example (this does **NOT** compile):
304    /// ```compile_fail
305    /// use sosecrets_rs::{
306    ///     prelude::typenum::U2,
307    ///     runtime::{secret::RTSecret, traits::RTExposeSecret},
308    /// };
309    /// #[cfg(feature = "zeroize")]
310    /// use zeroize::Zeroize;
311    ///
312    /// struct A {
313    ///     inner: i32,
314    /// }
315    ///
316    /// #[cfg(feature = "zeroize")]
317    /// impl Zeroize for A {
318    ///     fn zeroize(&mut self) {
319    ///         self.inner.zeroize()
320    ///     }
321    /// }
322    ///
323    /// let secret_one = RTSecret::<A, U2>::new(A { inner: 69 });
324    /// let _ = secret_one.expose_secret(|exposed_secret| exposed_secret);
325    /// let _ = secret_one.expose_secret(|exposed_secret| *exposed_secret); // Only if T is not `Copy`
326    /// ```
327    ///
328    /// # Parameters
329    /// - `self`.
330    /// - `scope`: A closure that takes the exposed secret and returns a value of the `ReturnType`.
331    ///
332    /// # Panics
333    /// This function panics only if the secret is exposed more than the maximally allowed exposure count represented by the type parameter `MEC`.
334    ///
335    /// # Returns
336    /// A value of type `ReturnType` which is the type of the returned value from the closure named `scope`.
337    #[inline(always)]
338    fn expose_secret<ReturnType, ClosureType>(&self, scope: ClosureType) -> ReturnType
339    where
340        for<'brand> ClosureType: FnOnce(RTExposedSecret<'brand, &'brand T>) -> ReturnType,
341    {
342        if self.can_expose() {
343            return scope(RTExposedSecret(&self.0, PhantomData));
344        } else {
345            let ec = self.exposure_count();
346            let mec = MEC::cast_unsigned_to_self_type::<MEC>(__private::SealedToken {});
347            panic!("`RTSecret` has already been exposed for {} times, the maximum number it is allowed to be exposed for is {} times.", ec, mec)
348        }
349    }
350
351    /// Return the `Result` containing `Ok(scope(exposed_secret))`, with runtime checking that the exposure count is not more than the maximally allowed exposure count represented by the type parameter `MEC`.
352    /// Note: It is impossible to return the 'exposed secret' as the return value of the closure.
353    ///
354    /// Example:
355    /// ```rust
356    /// use sosecrets_rs::{
357    ///     prelude::{typenum::U2, RTSecret},
358    ///     runtime::traits::RTExposeSecret,
359    /// };
360    /// #[cfg(feature = "zeroize")]
361    /// use zeroize::Zeroize;
362    ///
363    /// struct A {
364    ///     inner: i32,
365    /// }
366    ///
367    /// #[cfg(feature = "zeroize")]
368    /// impl Zeroize for A {
369    ///     fn zeroize(&mut self) {
370    ///         self.inner.zeroize()
371    ///     }
372    /// }
373    ///
374    /// let secret_one = RTSecret::<A, U2>::new(A { inner: 69 });
375    /// let returned_value = secret_one.try_expose_secret(|exposed_secret| A { inner: (*exposed_secret).inner + 1});
376    /// assert!(returned_value.is_ok());
377    /// ```
378    ///
379    /// Example (this example will **not** compile):
380    /// ```compile_fail
381    /// use sosecrets_rs::{
382    ///     prelude::typenum::U2,
383    ///     runtime::{secret::RTSecret, traits::RTExposeSecret},
384    /// };
385    /// #[cfg(feature = "zeroize")]
386    /// use zeroize::Zeroize;
387    ///
388    /// struct A {
389    ///     inner: i32,
390    /// }
391    ///
392    /// #[cfg(feature = "zeroize")]
393    /// impl Zeroize for A {
394    ///     fn zeroize(&mut self) {
395    ///         self.inner.zeroize()
396    ///     }
397    /// }
398    ///
399    /// let secret_one = RTSecret::<A, U2>::new(A { inner: 69 });
400    /// let _ = secret_one.try_expose_secret(|exposed_secret| exposed_secret);
401    /// let _ = secret_one.try_expose_secret(|exposed_secret| *exposed_secret); // Only if T is not `Copy`
402    /// ```
403    ///
404    /// # Parameters
405    /// - `self`.
406    /// - `scope`: A closure that takes the exposed secret and returns a value of the `ReturnType`.
407    ///
408    ///
409    /// # Returns
410    /// - `Ok`: The value returned by the closure.
411    /// - `Err`: If the exposure count exceeds the maximum allowed, returns an `ExposeSecretError`.
412    #[inline(always)]
413    fn try_expose_secret<ReturnType, ClosureType>(
414        &self,
415        scope: ClosureType,
416    ) -> Result<ReturnType, error::ExposeSecretError<MEC>>
417    where
418        for<'brand> ClosureType: FnOnce(RTExposedSecret<'brand, &'brand T>) -> ReturnType,
419    {
420        if self.can_expose() {
421            Ok(scope(RTExposedSecret(&self.0, PhantomData)))
422        } else {
423            let ec = self.exposure_count();
424            let mec = MEC::cast_unsigned_to_self_type::<MEC>(__private::SealedToken {});
425            Err(error::ExposeSecretError::ExposeMoreThanMaximallyAllow(
426                error::ExposeMoreThanMaximallyAllowError { mec, ec },
427            ))
428        }
429    }
430}
431
432impl<
433        #[cfg(feature = "zeroize")] T: Zeroize,
434        #[cfg(not(feature = "zeroize"))] T,
435        MEC: ChooseMinimallyRepresentableUInt,
436    > Drop for RTSecret<T, MEC>
437{
438    /// Zeroizes the secret value when dropped if the `zeroize` feature is enabled.
439    fn drop(&mut self) {
440        #[cfg(feature = "zeroize")]
441        self.0.zeroize()
442    }
443}
444
445#[cfg(feature = "cloneable-secret")]
446impl<T, MEC> Clone for RTSecret<T, MEC>
447where
448    T: CloneableSecret,
449    MEC: ChooseMinimallyRepresentableUInt + Unsigned,
450{
451    #[inline(always)]
452    fn clone(&self) -> Self {
453        Self(self.0.clone(), self.1.clone())
454    }
455}
456
457#[cfg(feature = "debug-secret")]
458impl<T, MEC> core::fmt::Debug for RTSecret<T, MEC>
459where
460    T: DebugSecret,
461    MEC: ChooseMinimallyRepresentableUInt + Unsigned,
462{
463    #[inline(always)]
464    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
465        f.write_str("RTSecret<")?;
466        T::debug_secret(f)?;
467        f.write_str(">")
468    }
469}
470
471#[cfg(test)]
472mod tests {
473    use super::*;
474    use crate::runtime::traits::RTExposeSecret;
475
476    #[test]
477    #[should_panic(
478        expected = "`RTSecret` has already been exposed for 255 times, the maximum number it is allowed to be exposed for is 255 times."
479    )]
480    fn test_usize_max_expose_secret() {
481        use typenum::U255;
482        let mut secret_one = RTSecret::<isize, U255>::new(69);
483        *secret_one.1.get_mut() = u8::MAX - 6;
484
485        for _ in 0..=5 {
486            let _ = secret_one.expose_secret(|exposed_secret| {
487                assert_eq!(*exposed_secret, 69);
488            });
489        }
490
491        assert_eq!(secret_one.exposure_count(), u8::MAX);
492
493        let _ = secret_one.expose_secret(|exposed_secret| {
494            assert_eq!(*exposed_secret, 69);
495        });
496    }
497}