sosecrets-rs

GitHub Workflow Status Crates.io Version docs.rs

License GitHub issues GitHub forks GitHub stars GitHub license GitHub last commit GitHub contributors GitHub pull requests GitHub release GitHub repo size GitHub commit activity GitHub code size in bytes GitHub language count GitHub top language GitHub download count GitHub watchers GitHub followers GitHub discussions GitHub issues closed GitHub milestones GitHub funding

Secrets Management crate with 1. type level and compile-time guarantees and 2. 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`](https://github.com/iqlusioninc/crates/tree/main/secrecy) crate but with type level and compile-time guarantees that the [`Secret`](prelude::Secret) 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`](https://github.com/paholg/typenum/tree/main) crate for all its compile-time guarantees. ## Features - **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](https://doc.rust-lang.org/nomicon/subtyping.html#variance) 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 the [`CloneableSecret`](traits::CloneableSecret) trait. - **Debugging Secrets:** The "debug-secret" feature enables the debugging of `Secret` values if the underlying type, `T`, implements the [`DebugSecret`](traits::DebugSecret) trait. ## Usage Examples ## Compile Time Checks ```rust 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. ```compile_fail 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. ```compile_fail 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`. ## Runtime Checks ```rust 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: ```bash `RTSecret\` has already been exposed for 2 times, the maximum number it is allowed to be exposed for is 2 times." ``` ```rust,should_panic # 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. ```compile_fail 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 }); ``` ## Substitute for the `secrecy` crate You can use the [`SecrecySecret`](prelude::SecrecySecret) type as a substitute for the [`Secret`](https://docs.rs/secrecy/0.8.0/secrecy/struct.Secret.html) in [`secrecy`](https://crates.io/crates/secrecy) crate. ```rust 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](https://github.com/jymchng/sosecrets-rs/tree/master/examples/jwt) directory. ## Features Configuration To enable features, you can include them in your `Cargo.toml`: ```toml [dependencies] sosecrets-rs = { version = "x.x.x", features = ["zeroize", "cloneable-secret", "debug-secret"] } ``` ## Modules - [`prelude`](prelude): Module for easily importing common items. - [`runtime`](runtime): Module for [`RTSecret`](prelude::RTSecret), [`SecrecySecret`](prelude::SecrecySecret) and [`RTExposeSecret`](runtime::traits::RTExposeSecret). ## Traits - [`ExposeSecret`](traits::ExposeSecret): Trait for safely exposing secrets with a limited exposure count at compile time. - [`RTExposeSecret`](runtime::traits::RTExposeSecret): Trait for safely exposing secrets with a limited exposure count at runtime time. - [`CloneableSecret`](traits::CloneableSecret): Trait for cloneable secrets. - [`DebugSecret`](traits::DebugSecret): Trait for debuggable secrets. For example, if the feature `"cloneable-secret"` is enabled, then you can 'clone' the secret. Example: ```rust #[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 }); }); } ``` # Minimum Supported Rust version 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. # Tests Run ```bash bash scripts/tests-all-features.sh ``` # License Licensed under - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) # Contribution 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. # Credits ## [CAD97](https://github.com/CAD97) * For rendering substantial help in the design and implementations of [`ExposeSecret`](traits::ExposeSecret) [[Rust Forum](https://users.rust-lang.org/t/making-a-value-of-a-type-undroppable-at-compile-time/102628/13?), [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3c2e97e284c60c8a4067b77b6cfd72c7)] trait and its trait method, [`expose_secret(...)`](traits::ExposeSecret::expose_secret) [[Rust Forum](https://users.rust-lang.org/t/making-a-value-of-a-type-undroppable-at-compile-time/102628/6?), [Rust Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=adce4708492654b6ad888f9a6b5bc5d0)]. * For teaching me the concept of [`invariant`](https://github.com/CAD97/generativity/blob/main/README.md) lifetime. ## [Eric Michael Sumner](https://orcid.org/0000-0002-6439-9757) * For creating the macro `impl_choose_int!()` on [Rust Forum](https://users.rust-lang.org/t/making-a-type-level-type-function-with-typenum-crate/107008/3?). The macro helps to implement the trait [`ChooseMinimallyRepresentableUInt`](traits::ChooseMinimallyRepresentableUInt) for all type-level unsigned integers provided by the `typenum` crate that are representable from 1 bit to 64 bits at the type level. ## [Simon Farnsworth](https://users.rust-lang.org/u/farnz/summary) * For providing advice on how to manage the optimizations done on [`RTSecret`](prelude::RTSecret) with regards to having the first field of the struct having different Rust's primitive unsigned integer types according to the type parameter `MEC` [[Link](https://users.rust-lang.org/t/rtsecret-t-std-cell-u8-is-the-same-size-as-rtsecret-t-std-cell-u16-why-and-how-to-optimize-such-that-former-is-smaller-than-latter/107396/20?)].