Skip to content

Commit

Permalink
Merge pull request #47 from jymchng/pre-releases/0.2.0
Browse files Browse the repository at this point in the history
Pre releases/0.2.0
  • Loading branch information
jymchng authored Mar 16, 2024
2 parents 36e1c4e + 642e587 commit a88abab
Show file tree
Hide file tree
Showing 31 changed files with 2,311 additions and 72 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CHANGELOG

## 17 March 2024
<center><h1>V0.2.0 IS RELEASED</h1></center>

1. `RTSecret<T, MEC>` is now available. `RTSecret<T, MEC>` is the runtime version of `Secret<T, MEC, EC>`.
2. Substitute of the `Secret<S>` in `secrecy` crate is available as `SecrecySecret<T>` albeit with a different function signature of the method `.expose_secret(...)`.
3. Following optimizations are done:

a. Given the type parameter `MEC` as an unsigned type-level integer, the second field of `RTSecret<T, MEC>` will be one of the following Rust's primitive unsigned integer types, `u8`, `u16`, `u32` and `u64`, depending on the projected runtime value of `MEC`.

For example, if `MEC` is `typenum::consts::U69`, then the second field of `RTSecret<T, MEC>` will be of type `u8`. This optimization is done because if `MEC` at runtime is projected to a value, e.g. 69, and the exposure counter, which is the second field of the type, will never exceed the `MEC`'s projected runtime value, then it only needs to be represented by an unsigned integer type (e.g. `u8`) that `MEC`'s projected runtime value (e.g. 69) is minimally representable by (since 69 < `u8::MAX` = 255).

b. `SecrecySecret<T>` is the same size as `T`.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sosecrets-rs"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
authors = ["Jim Chng <jimchng@outlook.com>"]
rust-version = "1.70"
Expand All @@ -22,6 +22,7 @@ exclude = [
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
num-traits = "0.2.18"
typenum = "1.17.0"
zeroize = { version = "1.6.0", optional = true}

Expand Down
190 changes: 162 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@
Secrets Management crate with

1. type level and compile-time guarantees and
2. each references corresponding to each secrets can only be exposed or revealed under a lexical scope with an invariant lifetime and
3. it is only exposed under an identifiable lexical scope and
4. with an invariant lifetime.
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<T, MEC, EC>` value is not ’exposed’ more than `MEC` number of times.
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<T, MEC, EC>`](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` trait.
- **Debugging Secrets:** The "debug-secret" feature enables the debugging of `Secret` values if the underlying type, `T`, implements the `DebugSecret` trait.
- **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 Example
## Usage Examples

## Compile Time Checks

```rust
use sosecrets_rs::{
Expand All @@ -37,29 +37,121 @@ 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... -----------------------------------------------
// `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 once: `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 once: `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
});
```

## 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... -----------------------------------------------
});

// **Try** to expose the secret again and perform some operations with the exposed value; secret has been exposed once: `EC` = 3, `MEC` = 2;
// The following when uncommented 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
// // ...
// });
// Expose the secret again and perform some operations with the exposed value; secret has been exposed once: `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
// ...
});
```
See more at the [examples](https://github.com/jymchng/sosecrets-rs/tree/master/examples/jwt) directory.

**Try** to expose the secret again and perform some operations with the exposed value; secret has been exposed once: `EC` = 3, `MEC` = 2;
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
// ...
});
```

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 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<T>`](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,
};
use typenum::U2;

// Define a secret with a maximum exposure count of 2
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

Expand All @@ -72,19 +164,53 @@ sosecrets-rs = { version = "x.x.x", features = ["zeroize", "cloneable-secret", "

## Modules

- `prelude`: Module for easily importing common items.
- [`prelude`](prelude): Module for easily importing common items.
- [`runtime`](runtime): Module for [`RTSecret<T>`](prelude::RTSecret) and [`RTExposeSecret`](runtime::traits::RTExposeSecret).

## Traits

- `ExposeSecret`: Trait for safely exposing secrets with a limited exposure count.
- `CloneableSecret`: Trait for cloneable secrets.
- `DebugSecret`: Trait for debuggable secrets.
- [`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 of increasing the
compiler version requirement of this crate beyond this. However, this is only
guaranteed within a given minor version number.
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

Expand All @@ -109,5 +235,13 @@ for inclusion in the work by you, as defined in the MIT license, without any add

## [CAD97](https://github.com/CAD97)

* For rendering substantial help in the design and implementations of `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(...)` [[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 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?)].
8 changes: 7 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
#![no_std]
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
// Apply #![recursion_limit = "256"] only if #![cfg(target_pointer_width = "128")]
#![recursion_limit = "2048"]

#[cfg(feature = "alloc")]
extern crate alloc;

// #[cfg(feature = "runtime-secret")]
pub mod runtime;

mod macros;
mod secret;

pub mod traits;
pub mod types;

pub mod prelude {
pub use crate::secret::*;
pub use crate::{runtime::*, secret::*, types::*};
}
60 changes: 60 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,63 @@ macro_rules! impl_debug_secret_for_numbers {

#[cfg(feature = "debug-secret")]
pub(crate) use impl_debug_secret_for_numbers;

macro_rules! impl_sealed_trait_for_uint {
($($t:ty),*) => {
$(
impl $crate::traits::__private::SealedTrait for $t {}
)*
};
}
pub(crate) use impl_sealed_trait_for_uint;

macro_rules! impl_choose_int {
// Entry point
($($arg:ident => $out:ty;)*) => {
impl_choose_int! {
@prev_args ();
@prev_num $crate::prelude::typenum::UTerm;
@rest_args ($($arg,)*);
@rest_out ($($out;)*);
}
};

// Implement one
(
@prev_args ($($prev_args:ident,)*);
@prev_num $prev_num:ty;
@rest_args ($arg:ident, $($rest_args:ident,)*);
@rest_out ($out:ty; $($rest_out:ty;)*);
)
=> {
impl<$($prev_args,)* $arg> $crate::traits::__private::SealedTrait for $crate::prelude::typenum::uint::UInt<$prev_num, $arg> {}

impl<$($prev_args,)* $arg> $crate::traits::ChooseMinimallyRepresentableUInt for $crate::prelude::typenum::uint::UInt<$prev_num, $arg> {
type Output = $out;
type AtomicOutput = <$out as $crate::traits::AsAtomic>::Output;
const ZERO: Self::Output = Self::Output::MIN;
const ONE: Self::Output = 1;

fn cast_unsigned_to_self_type<T: $crate::prelude::typenum::uint::Unsigned>(_: $crate::traits::__private::SealedToken) -> Self::Output {
<T as $crate::prelude::typenum::uint::Unsigned>::USIZE as Self::Output
}
}

impl_choose_int!{
@prev_args ($($prev_args,)* $arg,);
@prev_num $crate::prelude::typenum::uint::UInt<$prev_num, $arg>;
@rest_args ($($rest_args,)*);
@rest_out ($($rest_out;)*);
}
};

// Base case; stop iteration
(
@prev_args ($($prev_args:ident,)*);
@prev_num $prev_num:ty;
@rest_args ();
@rest_out ();
) => {};
}

pub(crate) use impl_choose_int;
4 changes: 4 additions & 0 deletions src/runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod error;
pub mod secret;
pub use secret::*;
pub mod traits;
31 changes: 31 additions & 0 deletions src/runtime/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::traits::ChooseMinimallyRepresentableUInt;

/// An error representing that the secret has been exposed more times than allowed.
#[derive(Debug)]
#[non_exhaustive]
pub enum ExposeSecretError<MEC: ChooseMinimallyRepresentableUInt> {
ExposeMoreThanMaximallyAllow(ExposeMoreThanMaximallyAllowError<MEC>),
}

/// An error representing that the secret has been exposed more times than allowed.
#[derive(Debug)]
pub struct ExposeMoreThanMaximallyAllowError<MEC: ChooseMinimallyRepresentableUInt> {
pub mec: <MEC as ChooseMinimallyRepresentableUInt>::Output,
pub ec: <MEC as ChooseMinimallyRepresentableUInt>::Output,
}

impl<MEC: ChooseMinimallyRepresentableUInt> core::fmt::Display
for ExposeMoreThanMaximallyAllowError<MEC>
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "`Secret` is exposed more than what it is maximally allowed to; it is exposed for {} times and it is only allowed to be exposed for {} times", self.ec, self.mec)
}
}

impl<MEC: ChooseMinimallyRepresentableUInt> core::fmt::Display for ExposeSecretError<MEC> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ExposeMoreThanMaximallyAllow(err) => err.fmt(f),
}
}
}
Loading

0 comments on commit a88abab

Please sign in to comment.