Rust’s Secrets Management crate with
- type level and compile-time guarantees and
- each references corresponding to each secrets can only be exposed or revealed under a lexical scope with an invariant lifetime.
It is like the secrecy
crate but with type level and compile-time guarantees that the Secret<T, MEC, EC>
value is not ’exposed’ more than MEC
number of times.
Invariant Lifetimes
Exposure of Secrets (i.e. getting references to them) are only allowed under certain lexical scope and each of the references only exist with an invariant lifetime.
Example:
// Define a wrapper struct `UseSecret` that holds a value of type `T`.
#[derive(Debug)]
pub struct UseSecret<T> {
pub inner: T,
}
impl<T> UseSecret<T> {
// Implement a constructor method `new` for `UseSecret` to create a new instance.
pub fn new(value: T) -> Self {
Self { inner: value }
}
}
fn main {
// Create a secret string.
let secret = "MySecret".to_owned();
// Create a new secret using the `Secret` type from your library.
// `U5` indicates the maximum exposure count (5 times),
// and the underscore in the last position allows Rust to infer the type.
let new_secret: Secret<_, U5, _> = Secret::new(secret);
// Expose the secret safely within a closure. This pattern is identical to
// `[std:๐งต:scoped](https://doc.rust-lang.org/std/thread/fn.scope.html)`
let (_new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
// `exposed_secret` is of type `&T` and it only exists from HERE to below --- #####
// Create a new `UseSecret` instance by cloning the exposed secret. ^
// You cannot bring `exposed_secret` out of this scope, never. |
// However, because `String` is `Clone`, so you can 'copy' `exposed_secret` |
// out of this scope. |
let returned_value = UseSecret::new((*exposed_secret).to_owned()); // |
// Return the new `UseSecret` instance. |
returned_value // v
// `exposed_secret` is of type `&T` and it only exists from above to HERE --- #####
});
// Assert that the inner value of `returned_value` matches the original secret.
assert_eq!(returned_value.inner, "MySecret".to_owned());
}
Compile-time Guarantees ๐๐๐
Of course, another cool part of this is the compile time guarantees.
Limited Secret Exposures
Secret<T, MEC, EC>
’s cannot be ’exposed’ more than MEC
times
Example:
This program will fail to compile.
// The following code is a demonstration of using the `sosecrets_rs` crate in a `no_std` environment.
#![no_std]
// Entry point of the program
fn main() {
// Importing necessary items from the `sosecrets_rs` crate and other dependencies
use common::UseSecret; // `UseSecret` is a utility struct for testing
use sosecrets_rs::prelude::*;
use sosecrets_rs::traits::ExposeSecret;
use typenum::consts::U2;
// Conditionally importing the `std` crate if the "alloc" feature is enabled
#[cfg(feature = "alloc")]
extern crate std;
// Conditionally using items from the `std` crate if the "alloc" feature is enabled
#[cfg(feature = "alloc")]
use std::{borrow::ToOwned, vec};
// Creating a vector containing a secret value ("MySecret")
let secret_vec = vec!["MySecret".to_owned()];
// Creating a new secret of type `Secret<_, U2, _>` using the vector
let new_secret: Secret<_, U2, _> = Secret::new(secret_vec);
// Exposing the secret and capturing the returned value; `EC` = 1, `MEC` = 2
let (new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
// Creating a new `UseSecret` instance from the exposed secret
let returned_value = UseSecret::new((*exposed_secret).to_owned());
returned_value
});
// Asserting that the returned value contains the original secret
assert_eq!(returned_value.inner, vec!["MySecret".to_owned()]);
// Performing another exposure of the secret and capturing the returned value; `EC` = 2, `MEC` = 2
let (new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
let returned_value = UseSecret::new((*exposed_secret).to_owned());
returned_value
});
// Asserting again that the returned value contains the original secret
assert_eq!(returned_value.inner, vec!["MySecret".to_owned()]);
// Performing one final exposure; `EC` = 3, `MEC` = 2 => COMPILE TIME ERROR ๐ฅ
let (_new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
let returned_value = UseSecret::new((*exposed_secret).to_owned());
returned_value
});
assert_eq!(returned_value.inner, vec![]);
}
Compilation fails with this obscene error message.
error[E0271]: type mismatch resolving `<UInt<UInt<UTerm, B1>, B1> as IsLessOrEqual<UInt<UInt<UTerm, B1>, B0>>>::Output == B1`
--> trybuild_tests/test_compile_fail_eight.rs:32:52
|
32 | let (_new_secret, returned_value) = new_secret.expose_secret(|exposed_secret| {
| ^^^^^^^^^^^^^ expected `B1`, found `B0`
|
note: required by a bound in `sosecrets_rs::traits::ExposeSecret::Next`
--> src/traits.rs
|
| type Next: ExposeSecret<'max, T, MEC, Sum<EC, U1>>
| ---- required by a bound in this associated type
...
| Sum<EC, U1>: Unsigned + IsLessOrEqual<MEC, Output = True> + Add<U1>;
| ^^^^^^^^^^^^^ required by this bound in `ExposeSecret::Next`
Invariants Always Maintained
Secret<T, MEC, EC>
cannot be constructed with EC
strictly larger than MEC
They can be equal but you cannot do anything with it, lol ๐
Example:
use sosecrets_rs::prelude::Secret;
use typenum::consts::{U5, U67};
fn main() {
let secret = "MySecret".to_owned();
// Simple, creating a type with `EC` = 67 and `MEC` = 5; compile error
let new_secret: Secret<String, U5, U67> = Secret::new(secret);
}
Compilation fails with another yet obscene error message.
error[E0271]: type mismatch resolving `<UInt<UInt<UInt<UInt<UInt<UInt<UInt<UTerm, B1>, B0>, B0>, ...>, ...>, ...>, ...> as IsLessOrEqual<...>>::Output == B1`
--> trybuild_tests/test_compile_fail_three.rs:7:21
|
7 | let new_secret: Secret<String, U5, U67> = Secret::new(secret);
| ^^^^^^^^^^^^^^^^^^^^^^^ expected `B1`, found `B0`
|
note: required by a bound in `Secret`
--> src/secret.rs
|
| pub struct Secret<
| ------ required by a bound in this struct
...
| EC: Add<U1> + IsLessOrEqual<MEC, Output = True> + Unsigned = U0,
| ^^^^^^^^^^^^^ required by this bound in `Secret`
Comments