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}