Secrets Management crate with
- type level and compile-time guarantees and
- each reference corresponds to each secret that can only be exposed or revealed under a lexical scope with an invariant lifetime
It is similar to 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 and is only exposed under a well-defined lexical scope.
It makes use of the typenum
crate for all its compile-time guarantees.
- Exposure Control: Secret values can only be exposed a limited number of times, preventing unintentional information leaks. This is guaranteed at compile time. Secrets are exposed and available for use with an invariant lifetime, identifiable with a clear lexical scope.
- Zeroization: If configured with the "zeroize" feature, secrets are zeroized upon dropping them.
- Cloneable Secrets: With the "cloneable-secret" feature,
Secret
values can be cloned if the underlying type,T
, implements theCloneableSecret
trait. - Debugging Secrets: The "debug-secret" feature enables the debugging of
Secret
values if the underlying type,T
, implements theDebugSecret
trait.
use sosecrets_rs::{
prelude::*,
traits::ExposeSecret,
};
use typenum::U2;
// Define a secret with a maximum exposure count of 2
let secret = Secret::<_, U2>::new("my_secret_value".to_string());
// Expose the secret and perform some operations with the exposed value; secret has been exposed once: `EC` = 1, `MEC` = 2;
let (next_secret, exposed_value) = secret.expose_secret(|exposed_secret| {
// `exposed_secret` is only 'available' from the next line -------
assert_eq!(&*exposed_secret.as_str(), "my_secret_value"); // ^
// Perform operations with the exposed value |
// ... v
// to this line... -----------------------------------------------
});
// Expose the secret again and perform some operations with the exposed value; secret has been exposed twice: `EC` = 2, `MEC` = 2;
let (next_secret, exposed_value) = next_secret.expose_secret(|exposed_secret| {
assert_eq!(&*exposed_secret.as_str(), "my_secret_value");
// Perform operations with the exposed value
// ...
});
Try to expose the secret again and perform some operations with the exposed value; secret has been exposed the third time: EC
= 3, MEC
= 2;
The following is uncompilable.
let (next_secret, exposed_value) = next_secret.expose_secret(|exposed_secret| {
assert_eq!(&*exposed_secret.as_str(), "my_secret_value");
// Perform operations with the exposed value
// ...
});
It is impossible to return the value (e.g. exposed_secret
in the example above) passed into the closure, out of the closure.
The following is uncompilable.
let (next_secret, exposed_value) = next_secret.expose_secret(|exposed_secret| {
assert_eq!(&*exposed_secret.as_str(), "my_secret_value");
// Perform operations with the exposed value
// ...
exposed_secret // impossible to return `exposed_secret` here
});
Note: If T
is Copy
, then the above will compile successfully and expose_secret(...)
method will return a copy of exposed T
.
use sosecrets_rs::{
prelude::*,
// Note, for runtime checks, you have to use the `RTExposeSecret` trait instead.
runtime::traits::RTExposeSecret,
};
use typenum::U2;
// Define a secret with a maximum exposure count of 2
let secret = RTSecret::<_, U2>::new("my_secret_value".to_string());
// Expose the secret and perform some operations with the exposed value; secret has been exposed once: `EC` = 1, `MEC` = 2;
let exposed_value = secret.expose_secret(|exposed_secret| {
// `exposed_secret` is only 'available' from the next line -------
assert_eq!(&*exposed_secret.as_str(), "my_secret_value"); // ^
// Perform operations with the exposed value |
// ... v
// to this line... -----------------------------------------------
});
// Expose the secret again and perform some operations with the exposed value; secret has been exposed twice: `EC` = 2, `MEC` = 2;
let exposed_value = secret.expose_secret(|exposed_secret| {
assert_eq!(&*exposed_secret.as_str(), "my_secret_value");
// Perform operations with the exposed value
// ...
});
Try to expose the secret again and perform some operations with the exposed value; secret has been exposed the third time: EC
= 3, MEC
= 2;
.expose_secret(...)
method will then panic
with the message:
`RTSecret\` has already been exposed for 2 times, the maximum number it is allowed to be exposed for is 2 times."
# use sosecrets_rs::{
# prelude::*,
# // Note, for runtime checks, you have to use the `RTExposeSecret` trait instead.
# runtime::traits::RTExposeSecret,
# };
# use typenum::U2;
#
# // Define a secret with a maximum exposure count of 2
# let secret = RTSecret::<_, U2>::new("my_secret_value".to_string());
#
# // Expose the secret and perform some operations with the exposed value; secret has been exposed once: `EC` = 1, `MEC` = 2;
# let exposed_value = secret.expose_secret(|exposed_secret| {
# // `exposed_secret` is only 'available' from the next line -------
# assert_eq!(&*exposed_secret.as_str(), "my_secret_value"); // ^
# // Perform operations with the exposed value |
# // ... v
# // to this line... -----------------------------------------------
# });
#
# // Expose the secret again and perform some operations with the exposed value; secret has been exposed twice: `EC` = 2, `MEC` = 2;
# let exposed_value = secret.expose_secret(|exposed_secret| {
# assert_eq!(&*exposed_secret.as_str(), "my_secret_value");
# // Perform operations with the exposed value
# // ...
# });
let exposed_value = secret.expose_secret(|exposed_secret| {
assert_eq!(&*exposed_secret.as_str(), "my_secret_value");
// Perform operations with the exposed value
// ...
});
Note: You can use the non-panicking variant of the method expose_secret(...)
which is named as try_expose_secret(...)
.
try_expose_secret(...)
returns a Result::Err
if the exposure count is larger than what is maximally allowed.
It is impossible to return the value (e.g. exposed_secret
in the example above) passed into the closure, out of the closure, unless T
is Copy
. The following is uncompilable.
let exposed_value = secret.expose_secret(|exposed_secret| {
assert_eq!(&*exposed_secret.as_str(), "my_secret_value");
// Perform operations with the exposed value
// ...
exposed_secret // impossible to return `exposed_secret` here
});
You can use the SecrecySecret
type as a substitute for the Secret<T>
in secrecy
crate.
use sosecrets_rs::{
prelude::*,
// Note, for runtime checks, you have to use the `RTExposeSecret` trait instead.
runtime::traits::RTExposeSecret,
};
// Define a secret with NO maximum exposure count
let secret = SecrecySecret::new("my_secret_value".to_string());
// Expose the secret and perform some operations with the exposed value as many times as you like.
for _ in 0..=1_000_000 {
let exposed_value = secret.expose_secret(|exposed_secret| {
// `exposed_secret` is only 'available' from the next line -------
assert_eq!(&*exposed_secret.as_str(), "my_secret_value"); // ^
// Perform operations with the exposed value |
// ... v
// to this line... -----------------------------------------------
});
}
See more in the examples directory.
To enable features, you can include them in your Cargo.toml
:
[dependencies]
sosecrets-rs = { version = "x.x.x", features = ["zeroize", "cloneable-secret", "debug-secret"] }
prelude
: Module for easily importing common items.runtime
: Module forRTSecret<T>
,SecrecySecret
andRTExposeSecret
.
ExposeSecret
: Trait for safely exposing secrets with a limited exposure count at compile time.RTExposeSecret
: Trait for safely exposing secrets with a limited exposure count at runtime time.CloneableSecret
: Trait for cloneable secrets.DebugSecret
: Trait for debuggable secrets.
For example, if the feature "cloneable-secret"
is enabled, then you can 'clone' the secret.
Example:
#[cfg(all(feature = "cloneable-secret", feature = "alloc"))]
// Need to enable feature = "alloc" because `String` requires feature = "alloc".
{
use sosecrets_rs::{
prelude::*,
traits::{CloneableSecret, ExposeSecret},
};
use typenum::U2;
// Define a secret with a maximum exposure count of 2
let secret = Secret::<_, U2>::new("my_secret_value".to_string());
// Clone the secret
let secret2 = secret.clone();
// Expose the secret and perform some operations with the exposed value; secret has been exposed once: `EC` = 1, `MEC` = 2;
let (next_secret, exposed_value) = secret.expose_secret(move |exposed_secret| {
// `exposed_secret` is only 'available' from the next line --------------------------^
let (next_secret2, exposed_value2) = secret2.expose_secret(|exposed_secret2| { // |
assert_eq!(&*exposed_secret.as_str(), "my_secret_value"); // |
assert_eq!(&*exposed_secret2.as_str(), "my_secret_value"); // |
assert_eq!(&*exposed_secret2.as_str(), &*exposed_secret.as_str()); // |
// Perform operations with the exposed value |
// ... |
// to this line... ---------------------------------------------------------------v
});
});
}
The crate currently requires Rust 1.70. I have no intent on increasing the compiler version requirement of this crate beyond this. However, this is only guaranteed within a given minor version number.
Run
bash scripts/tests-all-features.sh
Licensed under
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the MIT license, without any additional terms or conditions.
- For rendering substantial help in the design and implementations of
ExposeSecret
[Rust Forum, Rust Playground] trait and its trait method,expose_secret(...)
[Rust Forum, Rust Playground]. - For teaching me the concept of
invariant
lifetime.
- For creating the macro
impl_choose_int!()
on Rust Forum. The macro helps to implement the traitChooseMinimallyRepresentableUInt
for all type-level unsigned integers provided by thetypenum
crate that are representable from 1 bit to 64 bits at the type level.
- For providing advice on how to manage the optimizations done on
RTSecret
with regards to having the first field of the struct having different Rust's primitive unsigned integer types according to the type parameterMEC
[Link].