#[cfg(feature = "alloc")]
use alloc::{boxed::Box, string::String, vec::Vec};
use core::{
fmt,
hint::black_box,
marker::PhantomData,
sync::atomic::{compiler_fence, Ordering},
};
use crate::{ct, wipe, wipe_backend};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LengthError {
pub expected: usize,
pub actual: usize,
}
impl fmt::Display for LengthError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"length mismatch: expected {} bytes, got {} bytes",
self.expected, self.actual
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for LengthError {}
pub trait SecureSanitize {
fn secure_sanitize(&mut self);
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not approved for destructor-path sanitization",
label = "`SecureSanitizeOnDrop` requires `DropSafeSanitize`",
note = "derive `SecureSanitize` for a generated field-wise sanitizer, or explicitly implement `DropSafeSanitize` after reviewing a manual aggregate sanitizer"
)]
pub trait DropSafeSanitize: SecureSanitize {}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not guarantee stable shared secret storage",
label = "generic shared secret exposure requires `StableSharedSecretStorage`",
note = "use a dedicated secret container or implement and document the storage contract for a reviewed fixed-storage type"
)]
pub trait StableSharedSecretStorage: SecureSanitize {}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not guarantee stable mutable secret storage",
label = "generic mutable secret exposure requires `StableMutableSecretStorage`",
note = "use a dedicated secret container or implement and document the storage contract for a reviewed fixed-storage type"
)]
pub trait StableMutableSecretStorage: StableSharedSecretStorage {}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not allow-list `{T}` as secret storage",
label = "this exact storage type is absent from the selected policy",
note = "review the type and add it with `define_secret_storage_policy!`, or select an already approved type"
)]
pub trait SecretStoragePolicy<T: SecureSanitize> {
const RATIONALE: &'static str;
}
#[macro_export]
macro_rules! define_secret_storage_policy {
(
$(#[$metadata:meta])*
$visibility:vis $policy:ident {
$($storage:ty => $reason:literal),+ $(,)?
}
) => {
$(#[$metadata])*
$visibility enum $policy {}
$(
const _: () = {
let reason: &str = $reason;
let bytes = reason.as_bytes();
let mut index = 0;
let mut has_content = false;
while index < bytes.len() {
let byte = bytes[index];
if byte != b' '
&& byte != b'\t'
&& byte != b'\n'
&& byte != b'\r'
&& byte != 0x0b
&& byte != 0x0c
{
has_content = true;
}
index += 1;
}
assert!(
has_content,
"secret storage policy rationale must not be empty or ASCII-whitespace-only",
);
};
impl $crate::SecretStoragePolicy<$storage> for $policy {
const RATIONALE: &'static str = $reason;
}
)+
};
}
#[inline]
pub fn secure_replace<T: SecureSanitize>(slot: &mut T, replacement: T) {
slot.secure_sanitize();
*slot = replacement;
}
#[cfg(feature = "std")]
#[inline(never)]
pub fn sanitize_then_abort<T: SecureSanitize + ?Sized>(root: &mut T) -> ! {
root.secure_sanitize();
compiler_fence(Ordering::SeqCst);
black_box(&mut *root);
std::process::abort()
}
macro_rules! impl_secure_sanitize_scalar {
($($ty:ty),+ $(,)?) => {
$(
impl SecureSanitize for $ty {
#[inline(never)]
fn secure_sanitize(&mut self) {
wipe_backend::erase_plain_data(self);
}
}
impl StableSharedSecretStorage for $ty {}
impl StableMutableSecretStorage for $ty {}
)+
};
}
impl_secure_sanitize_scalar!(
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, bool, char, f32, f64,
);
#[macro_export]
macro_rules! secure_sanitize_struct {
(
$(#[$attr:meta])*
$vis:vis struct $name:ident {
$(
$(#[$field_attr:meta])*
$field_vis:vis $field:ident: $field_ty:ty
),* $(,)?
}
) => {
$(#[$attr])*
$vis struct $name {
$(
$(#[$field_attr])*
$field_vis $field: $field_ty,
)*
}
impl $crate::SecureSanitize for $name {
#[inline]
fn secure_sanitize(&mut self) {
$(
$crate::SecureSanitize::secure_sanitize(&mut self.$field);
)*
}
}
impl $crate::DropSafeSanitize for $name {}
};
}
#[macro_export]
macro_rules! secure_drop_struct {
(
$(#[$attr:meta])*
$vis:vis struct $name:ident {
$(
$(#[$field_attr:meta])*
$field_vis:vis $field:ident: $field_ty:ty
),* $(,)?
}
) => {
$crate::secure_sanitize_struct! {
$(#[$attr])*
$vis struct $name {
$(
$(#[$field_attr])*
$field_vis $field: $field_ty,
)*
}
}
impl Drop for $name {
#[inline]
fn drop(&mut self) {
fn require_drop_contract<T: ?Sized + $crate::DropSafeSanitize + ::core::marker::Unpin>() {}
require_drop_contract::<Self>();
$crate::SecureSanitize::secure_sanitize(self);
}
}
};
}
#[cfg(feature = "alloc")]
#[inline(never)]
pub(crate) fn sanitize_vec_capacity(bytes: &mut Vec<u8>) {
wipe::vec(bytes);
}
#[cfg(all(feature = "alloc", feature = "multi-pass-clear"))]
#[inline(never)]
fn sanitize_vec_capacity_multi_pass(bytes: &mut Vec<u8>) {
wipe::vec_multi_pass(bytes);
}
#[cfg(feature = "alloc")]
#[inline]
fn next_secret_capacity(current: usize, required: usize) -> usize {
current.saturating_mul(2).max(required).max(8)
}
#[cfg(feature = "alloc")]
const MAX_UTF8_CHAR_BYTES: usize = 4;
impl<T: SecureSanitize> SecureSanitize for [T] {
#[inline(never)]
fn secure_sanitize(&mut self) {
for item in self.iter_mut() {
item.secure_sanitize();
}
compiler_fence(Ordering::SeqCst);
}
}
impl<T: SecureSanitize, const N: usize> SecureSanitize for [T; N] {
#[inline(never)]
fn secure_sanitize(&mut self) {
self.as_mut_slice().secure_sanitize();
}
}
impl<T: StableSharedSecretStorage> StableSharedSecretStorage for [T] {}
impl<T: StableMutableSecretStorage> StableMutableSecretStorage for [T] {}
impl<T: StableSharedSecretStorage, const N: usize> StableSharedSecretStorage for [T; N] {}
impl<T: StableMutableSecretStorage, const N: usize> StableMutableSecretStorage for [T; N] {}
impl SecureSanitize for () {
#[inline]
fn secure_sanitize(&mut self) {}
}
impl StableSharedSecretStorage for () {}
impl StableMutableSecretStorage for () {}
macro_rules! impl_tuple_storage_contracts {
($(($($type:ident:$index:tt),+)),+ $(,)?) => {
$(
impl<$($type: SecureSanitize),+> SecureSanitize for ($($type,)+) {
#[inline]
fn secure_sanitize(&mut self) {
$(
self.$index.secure_sanitize();
)+
compiler_fence(Ordering::SeqCst);
}
}
impl<$($type: StableSharedSecretStorage),+> StableSharedSecretStorage
for ($($type,)+)
{}
impl<$($type: StableMutableSecretStorage),+> StableMutableSecretStorage
for ($($type,)+)
{}
)+
};
}
impl_tuple_storage_contracts!(
(A:0),
(A:0, B:1),
(A:0, B:1, C:2),
(A:0, B:1, C:2, D:3),
(A:0, B:1, C:2, D:3, E:4),
(A:0, B:1, C:2, D:3, E:4, F:5),
(A:0, B:1, C:2, D:3, E:4, F:5, G:6),
(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7),
(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8),
(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9),
(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10),
(A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11),
);
impl<T: SecureSanitize> SecureSanitize for Option<T> {
#[inline]
fn secure_sanitize(&mut self) {
if let Some(value) = self.as_mut() {
value.secure_sanitize();
}
*self = None;
compiler_fence(Ordering::SeqCst);
}
}
impl<T: SecureSanitize, E: SecureSanitize> SecureSanitize for Result<T, E> {
#[inline]
fn secure_sanitize(&mut self) {
match self {
Ok(value) => value.secure_sanitize(),
Err(error) => error.secure_sanitize(),
}
compiler_fence(Ordering::SeqCst);
}
}
impl<T> SecureSanitize for PhantomData<T> {
#[inline]
fn secure_sanitize(&mut self) {}
}
impl<T> StableSharedSecretStorage for PhantomData<T> {}
impl<T> StableMutableSecretStorage for PhantomData<T> {}
#[cfg(feature = "alloc")]
impl<T: SecureSanitize + ?Sized> SecureSanitize for Box<T> {
#[inline]
fn secure_sanitize(&mut self) {
self.as_mut().secure_sanitize();
}
}
#[cfg(feature = "alloc")]
impl<T: SecureSanitize> SecureSanitize for Vec<T> {
#[inline]
fn secure_sanitize(&mut self) {
for item in self.iter_mut() {
item.secure_sanitize();
}
self.clear();
wipe_backend::erase(
self.as_mut_ptr().cast::<u8>(),
self.capacity().saturating_mul(core::mem::size_of::<T>()),
);
compiler_fence(Ordering::SeqCst);
}
}
#[cfg(feature = "alloc")]
impl SecureSanitize for String {
#[inline(never)]
fn secure_sanitize(&mut self) {
wipe_backend::erase(self.as_mut_ptr(), self.capacity());
self.clear();
}
}
#[inline]
pub(crate) fn constant_time_eq_slices(left: &[u8], right: &[u8]) -> bool {
if left.len() != right.len() {
return false;
}
constant_time_eq_equal_len(left, right)
}
#[inline]
pub(crate) fn constant_time_eq_equal_len(left: &[u8], right: &[u8]) -> bool {
debug_assert_eq!(left.len(), right.len());
#[cfg(all(
feature = "asm-compare",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
))]
{
crate::compare_asm::constant_time_eq_equal_len(left, right)
}
#[cfg(not(all(
feature = "asm-compare",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
)))]
{
portable_constant_time_eq_equal_len(left, right)
}
}
#[inline]
#[cfg_attr(
all(
feature = "asm-compare",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(miri)
),
allow(dead_code)
)]
pub(crate) fn portable_constant_time_eq_equal_len(left: &[u8], right: &[u8]) -> bool {
debug_assert_eq!(left.len(), right.len());
let mut diff = 0usize;
let mut index = 0;
while index < left.len() {
diff = black_box(diff | usize::from(left[index] ^ right[index]));
index += 1;
}
black_box(diff) == 0
}
#[cfg(kani)]
mod kani_verification {
use super::*;
use core::cmp::Ordering;
fn assert_ct_ordering_matches(ordering: ct::CtOrdering, expected: Ordering) {
match expected {
Ordering::Less => {
assert_eq!(
ordering
.is_less()
.declassify_u8("test or verification observes normalized choice"),
1
);
assert_eq!(
ordering
.is_equal()
.declassify_u8("test or verification observes normalized choice"),
0
);
assert_eq!(
ordering
.is_greater()
.declassify_u8("test or verification observes normalized choice"),
0
);
}
Ordering::Equal => {
assert_eq!(
ordering
.is_less()
.declassify_u8("test or verification observes normalized choice"),
0
);
assert_eq!(
ordering
.is_equal()
.declassify_u8("test or verification observes normalized choice"),
1
);
assert_eq!(
ordering
.is_greater()
.declassify_u8("test or verification observes normalized choice"),
0
);
}
Ordering::Greater => {
assert_eq!(
ordering
.is_less()
.declassify_u8("test or verification observes normalized choice"),
0
);
assert_eq!(
ordering
.is_equal()
.declassify_u8("test or verification observes normalized choice"),
0
);
assert_eq!(
ordering
.is_greater()
.declassify_u8("test or verification observes normalized choice"),
1
);
}
}
}
fn lexicographic_cmp_4(left: &[u8; 4], right: &[u8; 4]) -> Ordering {
let mut index = 0;
while index < 4 {
if left[index] < right[index] {
return Ordering::Less;
}
if left[index] > right[index] {
return Ordering::Greater;
}
index += 1;
}
Ordering::Equal
}
#[kani::proof]
fn prove_wipe_bytes_clears_fixed_buffer() {
let mut bytes: [u8; 4] = kani::any();
wipe::bytes(&mut bytes);
assert_eq!(bytes, [0; 4]);
}
#[kani::proof]
fn prove_secret_bytes_clear_erases_visible_contents() {
let source: [u8; 4] = kani::any();
let mut secret = SecretBytes::<4>::from_array(source);
let mut output = [0xA5; 4];
secret.secure_clear();
assert!(secret
.export_to_slice("test exports bytes for verification output", &mut output)
.is_ok());
assert_eq!(output, [0; 4]);
}
#[kani::proof]
fn prove_secret_bytes_constant_time_eq_matches_byte_equality() {
let left: [u8; 4] = kani::any();
let right: [u8; 4] = kani::any();
let secret = SecretBytes::<4>::from_array(left);
let mut expected = true;
let mut index = 0;
while index < 4 {
expected &= left[index] == right[index];
index += 1;
}
assert_eq!(secret.constant_time_eq(&right), expected);
}
#[kani::proof]
fn prove_ct_choice_is_normalized() {
let value: u8 = kani::any();
let choice = ct::Choice::from_u8(value);
let unwrapped = choice.declassify_u8("test or verification observes normalized choice");
assert!(unwrapped == 0 || unwrapped == 1);
}
#[kani::proof]
fn prove_ct_choice_boolean_algebra_matches_public_bits() {
let left_byte: u8 = kani::any();
let right_byte: u8 = kani::any();
let left = ct::Choice::from_u8(left_byte);
let right = ct::Choice::from_u8(right_byte);
let left_bit = left.declassify_u8("test or verification observes normalized choice");
let right_bit = right.declassify_u8("test or verification observes normalized choice");
assert_eq!(
(left & right).declassify_u8("test or verification observes normalized choice"),
left_bit & right_bit
);
assert_eq!(
(left | right).declassify_u8("test or verification observes normalized choice"),
left_bit | right_bit
);
assert_eq!(
(left ^ right).declassify_u8("test or verification observes normalized choice"),
left_bit ^ right_bit
);
assert_eq!(
(!left).declassify_u8("test or verification observes normalized choice"),
left_bit ^ 1
);
}
#[kani::proof]
fn prove_ct_fixed_equality_matches_byte_equality() {
let left: [u8; 4] = kani::any();
let right: [u8; 4] = kani::any();
let mut expected = true;
let mut index = 0;
while index < 4 {
expected &= left[index] == right[index];
index += 1;
}
assert_eq!(
ct::eq_fixed(&left, &right)
.declassify_u8("test or verification observes normalized choice")
== 1,
expected
);
}
#[kani::proof]
fn prove_ct_public_length_equality_rejects_mismatch() {
let left: [u8; 4] = kani::any();
let right: [u8; 3] = kani::any();
assert_eq!(
ct::eq_public_len(&left, &right)
.declassify_u8("test or verification observes normalized choice"),
0
);
}
#[kani::proof]
fn prove_ct_fixed_ordering_matches_lexicographic_ordering() {
let left: [u8; 4] = kani::any();
let right: [u8; 4] = kani::any();
assert_ct_ordering_matches(
ct::cmp_fixed(&left, &right),
lexicographic_cmp_4(&left, &right),
);
}
#[kani::proof]
fn prove_ct_unsigned_ordering_matches_rust_ordering() {
let left: u16 = kani::any();
let right: u16 = kani::any();
assert_ct_ordering_matches(
<u16 as ct::ConstantTimeOrd>::ct_cmp(&left, &right),
left.cmp(&right),
);
}
#[kani::proof]
fn prove_ct_signed_ordering_matches_rust_ordering() {
let left: i16 = kani::any();
let right: i16 = kani::any();
assert_ct_ordering_matches(
<i16 as ct::ConstantTimeOrd>::ct_cmp(&left, &right),
left.cmp(&right),
);
}
#[kani::proof]
fn prove_ct_conditional_copy_matches_choice() {
let initial: [u8; 4] = kani::any();
let source: [u8; 4] = kani::any();
let choice_byte: u8 = kani::any();
let choice = ct::Choice::from_u8(choice_byte);
let mut destination = initial;
assert!(ct::conditional_copy(&mut destination, &source, choice).is_ok());
if choice.declassify_u8("test or verification observes normalized choice") == 1 {
assert_eq!(destination, source);
} else {
assert_eq!(destination, initial);
}
}
#[kani::proof]
fn prove_ct_conditional_swap_matches_choice() {
let initial_left: [u8; 4] = kani::any();
let initial_right: [u8; 4] = kani::any();
let choice_byte: u8 = kani::any();
let choice = ct::Choice::from_u8(choice_byte);
let mut left = initial_left;
let mut right = initial_right;
assert!(ct::conditional_swap(&mut left, &mut right, choice).is_ok());
if choice.declassify_u8("test or verification observes normalized choice") == 1 {
assert_eq!(left, initial_right);
assert_eq!(right, initial_left);
} else {
assert_eq!(left, initial_left);
assert_eq!(right, initial_right);
}
}
#[kani::proof]
fn prove_ct_oblivious_lookup_matches_public_index() {
let table: [u8; 4] = kani::any();
let fallback: u8 = kani::any();
let index: usize = kani::any();
let selected = ct::oblivious_lookup(&table, ct::SecretIndex::new(index), &fallback);
if index < 4 {
assert_eq!(selected, table[index]);
} else {
assert_eq!(selected, fallback);
}
}
#[kani::proof]
fn prove_ct_select_slice_matches_choice() {
let left: [u8; 4] = kani::any();
let right: [u8; 4] = kani::any();
let choice_byte: u8 = kani::any();
let choice = ct::Choice::from_u8(choice_byte);
let mut destination = [0u8; 4];
assert!(ct::select_slice(&mut destination, &left, &right, choice).is_ok());
if choice.declassify_u8("test or verification observes normalized choice") == 1 {
assert_eq!(destination, right);
} else {
assert_eq!(destination, left);
}
}
#[kani::proof]
fn prove_ct_option_unwrap_or_matches_presence() {
let value: u8 = kani::any();
let fallback: u8 = kani::any();
let presence_byte: u8 = kani::any();
let presence = ct::Choice::from_u8(presence_byte);
let option = ct::PublicCtOption::new(value, presence);
let selected = option.unwrap_or(&fallback);
if presence.declassify_u8("test or verification observes normalized choice") == 1 {
assert_eq!(selected, value);
} else {
assert_eq!(selected, fallback);
}
}
#[kani::proof]
fn prove_ct_option_and_or_match_presence_bits() {
let left_value: u8 = kani::any();
let right_value: u8 = kani::any();
let fallback: u8 = kani::any();
let left_presence_byte: u8 = kani::any();
let right_presence_byte: u8 = kani::any();
let left_presence = ct::Choice::from_u8(left_presence_byte);
let right_presence = ct::Choice::from_u8(right_presence_byte);
let left = ct::PublicCtOption::new(left_value, left_presence);
let right = ct::PublicCtOption::new(right_value, right_presence);
let and_selected = left.and(right).unwrap_or(&fallback);
let or_selected = left.or(right).unwrap_or(&fallback);
if left_presence.declassify_u8("test or verification observes normalized choice") == 1
&& right_presence.declassify_u8("test or verification observes normalized choice") == 1
{
assert_eq!(and_selected, right_value);
} else {
assert_eq!(and_selected, fallback);
}
if left_presence.declassify_u8("test or verification observes normalized choice") == 1 {
assert_eq!(or_selected, left_value);
} else if right_presence.declassify_u8("test or verification observes normalized choice")
== 1
{
assert_eq!(or_selected, right_value);
} else {
assert_eq!(or_selected, fallback);
}
}
#[kani::proof]
fn prove_ct_result_unwrap_or_and_maps_match_success_bit() {
let value: u8 = kani::any();
let error: u8 = kani::any();
let fallback: u8 = kani::any();
let success_byte: u8 = kani::any();
let success = ct::Choice::from_u8(success_byte);
let result = ct::PublicCtResult::new(value, error, success);
let selected = result.unwrap_or(&fallback);
let mapped = result.map(|inner| inner.wrapping_add(1));
let mapped_error = result.map_err(|inner| inner.wrapping_add(1));
if success.declassify_u8("test or verification observes normalized choice") == 1 {
assert_eq!(selected, value);
assert_eq!(
mapped.declassify("Kani exposes mapped success bit"),
Ok(value.wrapping_add(1))
);
assert_eq!(
mapped_error.declassify("Kani exposes mapped success bit"),
Ok(value)
);
} else {
assert_eq!(selected, fallback);
assert_eq!(
mapped.declassify("Kani exposes mapped error bit"),
Err(error)
);
assert_eq!(
mapped_error.declassify("Kani exposes mapped error bit"),
Err(error.wrapping_add(1))
);
}
}
#[kani::proof]
fn prove_ct_option_and_result_conditional_select_match_choice() {
let left_value: u8 = kani::any();
let right_value: u8 = kani::any();
let choice_byte: u8 = kani::any();
let choice = ct::Choice::from_u8(choice_byte);
let left_option = ct::PublicCtOption::some(left_value);
let right_option = ct::PublicCtOption::some(right_value);
let left_result = ct::PublicCtResult::new(left_value, 11u8, ct::Choice::TRUE);
let right_result = ct::PublicCtResult::new(right_value, 22u8, ct::Choice::TRUE);
let selected_option =
<ct::PublicCtOption<u8> as ct::ConditionallySelectable>::conditional_select(
&left_option,
&right_option,
choice,
);
let selected_result =
<ct::PublicCtResult<u8, u8> as ct::ConditionallySelectable>::conditional_select(
&left_result,
&right_result,
choice,
);
if choice.declassify_u8("test or verification observes normalized choice") == 1 {
assert_eq!(selected_option.unwrap_or(&0), right_value);
assert_eq!(selected_result.unwrap_or(&0), right_value);
} else {
assert_eq!(selected_option.unwrap_or(&0), left_value);
assert_eq!(selected_result.unwrap_or(&0), left_value);
}
}
#[kani::proof]
fn prove_constant_time_eq_rejects_length_mismatch() {
let left: [u8; 4] = kani::any();
let right: [u8; 3] = kani::any();
assert!(!constant_time_eq_slices(&left, &right));
}
#[kani::proof]
fn prove_secret_bytes_replacement_commits_complete_new_value() {
let initial: [u8; 4] = kani::any();
let replacement: [u8; 4] = kani::any();
let mut secret = SecretBytes::<4>::from_array(initial);
let mut observed = [0_u8; 4];
secret.copy_from_slice(&replacement).unwrap();
secret
.export_to_slice("test exports bytes for verification output", &mut observed)
.unwrap();
assert_eq!(observed, replacement);
}
#[kani::proof]
#[cfg(feature = "alloc")]
fn prove_next_secret_capacity_never_under_allocates() {
let current: usize = kani::any();
let required: usize = kani::any();
let capacity = next_secret_capacity(current, required);
assert!(capacity >= required);
assert!(capacity >= 8);
}
}
struct TemporaryBytes<'a, const N: usize> {
bytes: &'a mut [u8; N],
}
impl<const N: usize> Drop for TemporaryBytes<'_, N> {
#[inline]
fn drop(&mut self) {
wipe::bytes(self.bytes);
}
}
pub(crate) fn expose_array_copy<const N: usize, R>(
source: &[u8; N],
inspect: impl FnOnce(&[u8; N]) -> R,
) -> R {
let mut temporary = [0; N];
temporary.copy_from_slice(source);
compiler_fence(Ordering::SeqCst);
let guard = TemporaryBytes {
bytes: &mut temporary,
};
let result = inspect(guard.bytes);
wipe::bytes(guard.bytes);
result
}
#[cfg(all(test, feature = "std"))]
mod temporary_bytes_tests {
use super::TemporaryBytes;
use std::panic::{catch_unwind, AssertUnwindSafe};
#[test]
fn temporary_bytes_clear_during_unwind() {
let mut bytes = [7_u8; 32];
let result = catch_unwind(AssertUnwindSafe(|| {
let _guard = TemporaryBytes { bytes: &mut bytes };
panic!("exercise temporary cleanup");
}));
assert!(result.is_err());
assert_eq!(bytes, [0; 32]);
}
}
pub struct SecretBytes<const N: usize> {
bytes: [u8; N],
}
impl<const N: usize> SecretBytes<N> {
#[must_use]
#[inline]
pub const fn zeroed() -> Self {
Self { bytes: [0; N] }
}
#[must_use]
#[inline]
pub fn from_array(mut bytes: [u8; N]) -> Self {
let mut secret = Self::zeroed();
for (index, byte) in bytes.iter().copied().enumerate() {
secret.store(index, byte);
}
secret.after_secret_write();
wipe::bytes(&mut bytes);
secret
}
#[must_use]
#[inline]
pub fn from_fn(mut make_byte: impl FnMut(usize) -> u8) -> Self {
let mut secret = Self::zeroed();
let mut index = 0;
while index < N {
secret.store(index, make_byte(index));
index += 1;
}
secret.after_secret_write();
secret
}
#[inline]
pub fn try_from_fn<E>(mut make_byte: impl FnMut(usize) -> Result<u8, E>) -> Result<Self, E> {
let mut secret = Self::zeroed();
let mut index = 0;
while index < N {
let byte = make_byte(index)?;
secret.store(index, byte);
index += 1;
}
secret.after_secret_write();
Ok(secret)
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
N
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
N == 0
}
#[inline]
pub fn copy_from_slice(&mut self, source: &[u8]) -> Result<(), LengthError> {
if source.len() != N {
return Err(LengthError {
expected: N,
actual: source.len(),
});
}
for (index, byte) in source.iter().copied().enumerate() {
self.store(index, byte);
}
self.after_secret_write();
Ok(())
}
#[inline]
pub fn replace_from_array(&mut self, mut bytes: [u8; N]) {
for (index, byte) in bytes.iter().copied().enumerate() {
self.store(index, byte);
}
self.after_secret_write();
wipe::bytes(&mut bytes);
}
#[inline]
pub fn replace_from_fn(&mut self, make_byte: impl FnMut(usize) -> u8) {
let mut replacement = Self::from_fn(make_byte);
self.secure_clear();
core::mem::swap(self, &mut replacement);
}
#[inline]
pub fn try_replace_from_fn<E>(
&mut self,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<(), E> {
let mut replacement = Self::try_from_fn(make_byte)?;
self.secure_clear();
core::mem::swap(self, &mut replacement);
Ok(())
}
#[inline]
pub fn transform(&mut self, edit: impl FnOnce(&mut [u8; N])) {
edit(&mut self.bytes);
self.after_secret_write();
}
#[inline]
pub fn try_transform<E>(
&mut self,
edit: impl FnOnce(&mut [u8; N]) -> Result<(), E>,
) -> Result<(), E> {
edit(&mut self.bytes)?;
self.after_secret_write();
Ok(())
}
#[must_use]
#[inline]
pub fn derive<const M: usize>(
&self,
derive: impl FnOnce(&[u8; N], &mut [u8; M]),
) -> SecretBytes<M> {
let mut output = SecretBytes::<M>::zeroed();
derive(&self.bytes, &mut output.bytes);
output.after_secret_write();
output
}
#[inline]
pub fn try_derive<const M: usize, E>(
&self,
derive: impl FnOnce(&[u8; N], &mut [u8; M]) -> Result<(), E>,
) -> Result<SecretBytes<M>, E> {
let mut output = SecretBytes::<M>::zeroed();
derive(&self.bytes, &mut output.bytes)?;
output.after_secret_write();
Ok(output)
}
#[inline]
pub fn export_to_slice(
&self,
reason: &'static str,
destination: &mut [u8],
) -> Result<(), LengthError> {
black_box(reason);
if destination.len() != N {
return Err(LengthError {
expected: N,
actual: destination.len(),
});
}
for (index, byte) in destination.iter_mut().enumerate() {
*byte = self.load(index);
}
compiler_fence(Ordering::SeqCst);
black_box(destination);
Ok(())
}
#[must_use]
#[inline]
pub fn export_byte(&self, reason: &'static str, index: usize) -> Option<u8> {
black_box(reason);
if index < N {
Some(self.load(index))
} else {
None
}
}
#[inline]
pub fn write_byte(&mut self, index: usize, value: u8) -> Result<(), LengthError> {
if index >= N {
return Err(LengthError {
expected: N,
actual: index.saturating_add(1),
});
}
self.store(index, value);
self.after_secret_write();
Ok(())
}
#[inline]
pub fn expose_secret<R>(&self, inspect: impl FnOnce(&[u8; N]) -> R) -> R {
inspect(&self.bytes)
}
#[inline]
pub fn export_secret_copy<R>(
&self,
reason: &'static str,
inspect: impl FnOnce(&[u8; N]) -> R,
) -> R {
black_box(reason);
expose_array_copy(&self.bytes, inspect)
}
#[must_use]
#[inline]
pub fn constant_time_eq(&self, other: &[u8]) -> bool {
constant_time_eq_slices(self.bytes.as_slice(), other)
}
#[must_use]
#[inline]
pub fn constant_time_eq_secret(&self, other: &Self) -> bool {
constant_time_eq_equal_len(self.bytes.as_slice(), other.bytes.as_slice())
}
#[inline(never)]
pub fn secure_clear(&mut self) {
wipe_backend::erase(self.bytes.as_mut_ptr(), N);
}
#[cfg(feature = "multi-pass-clear")]
#[inline(never)]
pub fn secure_clear_multi_pass(&mut self) {
wipe_backend::erase_multi_pass(self.bytes.as_mut_ptr(), N);
}
#[inline]
pub fn into_cleared(mut self) {
self.secure_clear();
}
#[cfg(feature = "cache-flush")]
#[inline(never)]
pub fn secure_clear_and_flush(
&mut self,
) -> Result<crate::cache_flush::CacheFlushReport, crate::cache_flush::CacheFlushError> {
crate::cache_flush::cache_flush_sanitize_bytes(self.bytes.as_mut_slice())
}
#[inline]
fn load(&self, index: usize) -> u8 {
self.bytes[index]
}
#[inline]
pub(crate) fn store(&mut self, index: usize, value: u8) {
self.bytes[index] = value;
}
#[inline]
pub(crate) fn after_secret_write(&self) {
compiler_fence(Ordering::SeqCst);
}
}
impl<const N: usize> Default for SecretBytes<N> {
#[inline]
fn default() -> Self {
Self::zeroed()
}
}
impl<const N: usize> Drop for SecretBytes<N> {
#[inline]
fn drop(&mut self) {
self.secure_clear();
}
}
impl<const N: usize> SecureSanitize for SecretBytes<N> {
#[inline]
fn secure_sanitize(&mut self) {
self.secure_clear();
}
}
impl<const N: usize> StableSharedSecretStorage for SecretBytes<N> {}
impl<const N: usize> StableMutableSecretStorage for SecretBytes<N> {}
impl<const N: usize> fmt::Debug for SecretBytes<N> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SecretBytes")
.field("len", &N)
.field("contents", &"<redacted>")
.finish()
}
}
impl<const N: usize> ct::ConstantTimeEq for SecretBytes<N> {
#[inline]
fn ct_eq(&self, other: &Self) -> ct::Choice {
ct::eq_fixed(&self.bytes, &other.bytes)
}
}
impl<const N: usize> ct::ConstantTimeEq<[u8]> for SecretBytes<N> {
#[inline]
fn ct_eq(&self, other: &[u8]) -> ct::Choice {
ct::eq_public_len(self.bytes.as_slice(), other)
}
}
impl<const N: usize> ct::ConditionallySelectable for SecretBytes<N> {
#[inline]
fn conditional_select(left: &Self, right: &Self, choice: ct::Choice) -> Self {
let mut output = Self::zeroed();
let mut index = 0usize;
while index < N {
output.bytes[index] = <u8 as ct::ConditionallySelectable>::conditional_select(
&left.bytes[index],
&right.bytes[index],
choice,
);
index += 1;
}
output.after_secret_write();
output
}
}
#[cfg(feature = "split-secret")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SplitSecretError {
TooFewShares,
TrivialMask,
}
#[cfg(feature = "split-secret")]
impl fmt::Display for SplitSecretError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooFewShares => formatter.write_str("split secrets require at least two shares"),
Self::TrivialMask => formatter.write_str(
"split-secret mask shares are trivially constant; use cryptographically random mask bytes",
),
}
}
}
#[cfg(all(feature = "split-secret", feature = "std"))]
impl std::error::Error for SplitSecretError {}
#[cfg(feature = "split-secret")]
pub struct SplitSecretBytes<const N: usize, const SHARES: usize> {
shares: [SecretBytes<N>; SHARES],
}
#[cfg(feature = "split-secret")]
impl<const N: usize, const SHARES: usize> SplitSecretBytes<N, SHARES> {
pub fn from_array_with_generator(
mut secret: [u8; N],
mut make_mask_byte: impl FnMut(usize, usize) -> u8,
) -> Result<Self, SplitSecretError> {
let guard = TemporaryBytes { bytes: &mut secret };
if SHARES < 2 {
return Err(SplitSecretError::TooFewShares);
}
let split = Self::from_secret_bytes_with_generator(guard.bytes, &mut make_mask_byte)?;
wipe::bytes(guard.bytes);
Ok(split)
}
pub fn from_secret_with_generator(
secret: &SecretBytes<N>,
mut make_mask_byte: impl FnMut(usize, usize) -> u8,
) -> Result<Self, SplitSecretError> {
if SHARES < 2 {
return Err(SplitSecretError::TooFewShares);
}
Self::from_secret_bytes_with_generator(&secret.bytes, &mut make_mask_byte)
}
pub fn from_secret_consuming_with_generator(
mut secret: SecretBytes<N>,
mut make_mask_byte: impl FnMut(usize, usize) -> u8,
) -> Result<Self, SplitSecretError> {
let split = Self::from_secret_bytes_with_generator(&secret.bytes, &mut make_mask_byte)?;
secret.secure_clear();
Ok(split)
}
#[must_use]
pub fn reconstruct(&self) -> SecretBytes<N> {
let mut output = SecretBytes::<N>::zeroed();
let mut byte_index = 0;
while byte_index < N {
let mut value = 0;
let mut share_index = 0;
while share_index < SHARES {
value ^= self.shares[share_index].load(byte_index);
share_index += 1;
}
output.store(byte_index, value);
byte_index += 1;
}
output.after_secret_write();
output
}
#[inline]
pub fn expose_secret_copy<R>(&self, inspect: impl FnOnce(&[u8; N]) -> R) -> R {
let reconstructed = self.reconstruct();
reconstructed.expose_secret(inspect)
}
#[must_use]
#[inline]
pub const fn shares(&self) -> &[SecretBytes<N>; SHARES] {
&self.shares
}
#[must_use]
#[inline]
pub fn share(&self, index: usize) -> Option<&SecretBytes<N>> {
self.shares.get(index)
}
#[must_use]
#[inline]
pub fn into_shares(self) -> [SecretBytes<N>; SHARES] {
self.shares
}
fn from_secret_bytes_with_generator(
secret: &[u8; N],
make_mask_byte: &mut impl FnMut(usize, usize) -> u8,
) -> Result<Self, SplitSecretError> {
if SHARES < 2 {
return Err(SplitSecretError::TooFewShares);
}
let mut shares = core::array::from_fn(|_| SecretBytes::<N>::zeroed());
let mut byte_index = 0;
while byte_index < N {
let mut accumulator = 0;
let mut share_index = 0;
while share_index + 1 < SHARES {
let mask = make_mask_byte(share_index, byte_index);
shares[share_index].store(byte_index, mask);
accumulator ^= mask;
share_index += 1;
}
shares[SHARES - 1].store(byte_index, secret[byte_index] ^ accumulator);
byte_index += 1;
}
let trivial_mask = u8::from(Self::mask_shares_are_trivially_constant(&shares))
| u8::from(Self::mask_accumulator_is_trivial(&shares));
if trivial_mask != 0 {
shares.secure_sanitize();
return Err(SplitSecretError::TrivialMask);
}
for share in shares.iter() {
share.after_secret_write();
}
Ok(Self { shares })
}
#[inline]
fn mask_shares_are_trivially_constant(shares: &[SecretBytes<N>; SHARES]) -> bool {
if N < 2 {
return false;
}
let mut any_trivial = false;
let mut share_index = 0;
while share_index + 1 < SHARES {
let first = shares[share_index].load(0);
let mut byte_index = 1;
let mut all_same = true;
while byte_index < N {
let diff = shares[share_index].load(byte_index) ^ first;
all_same &= diff == 0;
byte_index += 1;
}
any_trivial |= all_same;
share_index += 1;
}
any_trivial
}
#[inline]
fn mask_accumulator_is_trivial(shares: &[SecretBytes<N>; SHARES]) -> bool {
if N == 0 || SHARES < 2 {
return false;
}
let mut first_accumulator = 0u8;
let mut share_index = 0;
while share_index + 1 < SHARES {
first_accumulator ^= shares[share_index].load(0);
share_index += 1;
}
let mut all_same = true;
let mut byte_index = 1;
while byte_index < N {
let mut accumulator = 0u8;
let mut share_index = 0;
while share_index + 1 < SHARES {
accumulator ^= shares[share_index].load(byte_index);
share_index += 1;
}
all_same &= accumulator == first_accumulator;
byte_index += 1;
}
if N == 1 {
first_accumulator == 0
} else {
all_same
}
}
}
#[cfg(feature = "split-secret")]
impl<const N: usize, const SHARES: usize> SecureSanitize for SplitSecretBytes<N, SHARES> {
#[inline]
fn secure_sanitize(&mut self) {
self.shares.secure_sanitize();
}
}
#[cfg(feature = "split-secret")]
impl<const N: usize, const SHARES: usize> StableSharedSecretStorage
for SplitSecretBytes<N, SHARES>
{
}
#[cfg(feature = "split-secret")]
impl<const N: usize, const SHARES: usize> StableMutableSecretStorage
for SplitSecretBytes<N, SHARES>
{
}
#[cfg(feature = "split-secret")]
impl<const N: usize, const SHARES: usize> fmt::Debug for SplitSecretBytes<N, SHARES> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SplitSecretBytes")
.field("len", &N)
.field("shares", &SHARES)
.field("contents", &"<redacted>")
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SecretExpiredError;
impl fmt::Display for SecretExpiredError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("secret has expired")
}
}
#[cfg(feature = "std")]
impl std::error::Error for SecretExpiredError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExpiringSecretError {
Expired(SecretExpiredError),
Length(LengthError),
}
impl fmt::Display for ExpiringSecretError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Expired(error) => error.fmt(formatter),
Self::Length(error) => error.fmt(formatter),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ExpiringSecretError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Expired(error) => Some(error),
Self::Length(error) => Some(error),
}
}
}
impl From<SecretExpiredError> for ExpiringSecretError {
#[inline]
fn from(error: SecretExpiredError) -> Self {
Self::Expired(error)
}
}
impl From<LengthError> for ExpiringSecretError {
#[inline]
fn from(error: LengthError) -> Self {
Self::Length(error)
}
}
pub trait MonotonicClock {
fn now(&self) -> u64;
}
impl<C: MonotonicClock + ?Sized> MonotonicClock for &C {
#[inline]
fn now(&self) -> u64 {
(**self).now()
}
}
pub struct MonotonicExpiringSecretBytes<const N: usize, C: MonotonicClock> {
inner: SecretBytes<N>,
clock: C,
created_at: u64,
max_age: u64,
}
impl<const N: usize, C: MonotonicClock> MonotonicExpiringSecretBytes<N, C> {
#[must_use]
#[inline]
pub fn zeroed(clock: C, max_age: u64) -> Self {
let created_at = clock.now();
Self {
inner: SecretBytes::zeroed(),
clock,
created_at,
max_age,
}
}
#[must_use]
#[inline]
pub fn from_array(bytes: [u8; N], clock: C, max_age: u64) -> Self {
let created_at = clock.now();
Self {
inner: SecretBytes::from_array(bytes),
clock,
created_at,
max_age,
}
}
#[must_use]
#[inline]
pub fn from_fn(clock: C, max_age: u64, make_byte: impl FnMut(usize) -> u8) -> Self {
let created_at = clock.now();
Self {
inner: SecretBytes::from_fn(make_byte),
clock,
created_at,
max_age,
}
}
#[inline]
pub fn try_from_fn<E>(
clock: C,
max_age: u64,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, E> {
let created_at = clock.now();
Ok(Self {
inner: SecretBytes::try_from_fn(make_byte)?,
clock,
created_at,
max_age,
})
}
#[must_use]
#[inline]
pub fn from_secret(secret: SecretBytes<N>, clock: C, max_age: u64) -> Self {
let created_at = clock.now();
Self {
inner: secret,
clock,
created_at,
max_age,
}
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
N
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
N == 0
}
#[must_use]
#[inline]
pub const fn max_age_ticks(&self) -> u64 {
self.max_age
}
#[must_use]
#[inline]
pub fn age_ticks(&self) -> u64 {
self.clock.now().saturating_sub(self.created_at)
}
#[must_use]
#[inline]
pub fn is_expired(&self) -> bool {
self.age_ticks() >= self.max_age
}
#[must_use]
#[inline]
pub const fn clock(&self) -> &C {
&self.clock
}
#[inline]
pub fn replace_from_slice(&mut self, source: &[u8]) -> Result<(), LengthError> {
if source.len() != N {
if self.is_expired() {
self.inner.secure_clear();
}
return Err(LengthError {
expected: N,
actual: source.len(),
});
}
let mut replacement = SecretBytes::<N>::zeroed();
replacement.copy_from_slice(source)?;
self.inner.secure_clear();
self.inner = replacement;
self.created_at = self.clock.now();
Ok(())
}
#[inline]
pub fn replace_from_array(&mut self, bytes: [u8; N]) {
let replacement = SecretBytes::from_array(bytes);
self.inner.secure_clear();
self.inner = replacement;
self.created_at = self.clock.now();
}
#[inline]
pub fn replace_from_fn(&mut self, make_byte: impl FnMut(usize) -> u8) {
let expired = self.is_expired();
if expired {
self.inner.secure_clear();
}
let replacement = SecretBytes::from_fn(make_byte);
if !expired {
self.inner.secure_clear();
}
self.inner = replacement;
self.created_at = self.clock.now();
}
#[inline]
pub fn try_replace_from_fn<E>(
&mut self,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<(), E> {
let expired = self.is_expired();
if expired {
self.inner.secure_clear();
}
let replacement = SecretBytes::try_from_fn(make_byte)?;
if !expired {
self.inner.secure_clear();
}
self.inner = replacement;
self.created_at = self.clock.now();
Ok(())
}
#[inline]
pub fn try_export_to_slice(
&mut self,
reason: &'static str,
destination: &mut [u8],
) -> Result<(), ExpiringSecretError> {
self.enforce_live()?;
self.inner
.export_to_slice(reason, destination)
.map_err(Into::into)
}
#[inline]
pub fn try_expose_secret<R>(
&mut self,
inspect: impl FnOnce(&[u8; N]) -> R,
) -> Result<R, SecretExpiredError> {
self.enforce_live()?;
Ok(self.inner.expose_secret(inspect))
}
#[inline]
pub fn try_export_secret_copy<R>(
&mut self,
reason: &'static str,
inspect: impl FnOnce(&[u8; N]) -> R,
) -> Result<R, SecretExpiredError> {
self.enforce_live()?;
Ok(self.inner.export_secret_copy(reason, inspect))
}
#[inline]
pub fn try_constant_time_eq(&mut self, other: &[u8]) -> Result<bool, SecretExpiredError> {
self.enforce_live()?;
Ok(self.inner.constant_time_eq(other))
}
#[inline(never)]
pub fn secure_clear(&mut self) {
self.inner.secure_clear();
}
#[inline]
pub fn into_cleared(mut self) {
self.secure_clear();
}
#[inline]
fn enforce_live(&mut self) -> Result<(), SecretExpiredError> {
if self.is_expired() {
self.inner.secure_clear();
Err(SecretExpiredError)
} else {
Ok(())
}
}
}
impl<const N: usize, C: MonotonicClock> Drop for MonotonicExpiringSecretBytes<N, C> {
#[inline]
fn drop(&mut self) {
self.secure_clear();
}
}
impl<const N: usize, C: MonotonicClock> SecureSanitize for MonotonicExpiringSecretBytes<N, C> {
#[inline]
fn secure_sanitize(&mut self) {
self.secure_clear();
}
}
impl<const N: usize, C: MonotonicClock> fmt::Debug for MonotonicExpiringSecretBytes<N, C> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("MonotonicExpiringSecretBytes")
.field("len", &N)
.field("age_ticks", &self.age_ticks())
.field("max_age_ticks", &self.max_age)
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "std")]
pub struct ExpiringSecretBytes<const N: usize> {
inner: SecretBytes<N>,
created_at: std::time::Instant,
max_age: std::time::Duration,
}
#[cfg(feature = "std")]
impl<const N: usize> ExpiringSecretBytes<N> {
#[must_use]
#[inline]
pub fn zeroed(max_age: std::time::Duration) -> Self {
Self {
inner: SecretBytes::zeroed(),
created_at: std::time::Instant::now(),
max_age,
}
}
#[must_use]
#[inline]
pub fn from_array(bytes: [u8; N], max_age: std::time::Duration) -> Self {
Self {
inner: SecretBytes::from_array(bytes),
created_at: std::time::Instant::now(),
max_age,
}
}
#[must_use]
#[inline]
pub fn from_fn(max_age: std::time::Duration, make_byte: impl FnMut(usize) -> u8) -> Self {
Self {
inner: SecretBytes::from_fn(make_byte),
created_at: std::time::Instant::now(),
max_age,
}
}
#[inline]
pub fn try_from_fn<E>(
max_age: std::time::Duration,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, E> {
Ok(Self {
inner: SecretBytes::try_from_fn(make_byte)?,
created_at: std::time::Instant::now(),
max_age,
})
}
#[must_use]
#[inline]
pub fn from_secret(secret: SecretBytes<N>, max_age: std::time::Duration) -> Self {
Self {
inner: secret,
created_at: std::time::Instant::now(),
max_age,
}
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
N
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
N == 0
}
#[must_use]
#[inline]
pub const fn max_age(&self) -> std::time::Duration {
self.max_age
}
#[must_use]
#[inline]
pub fn age(&self) -> std::time::Duration {
self.created_at.elapsed()
}
#[must_use]
#[inline]
pub fn is_expired(&self) -> bool {
self.age() >= self.max_age
}
#[inline]
pub fn replace_from_slice(&mut self, source: &[u8]) -> Result<(), LengthError> {
if source.len() != N {
if self.is_expired() {
self.inner.secure_clear();
}
return Err(LengthError {
expected: N,
actual: source.len(),
});
}
let mut replacement = SecretBytes::<N>::zeroed();
replacement.copy_from_slice(source)?;
self.inner.secure_clear();
self.inner = replacement;
self.created_at = std::time::Instant::now();
Ok(())
}
#[inline]
pub fn replace_from_array(&mut self, bytes: [u8; N]) {
let replacement = SecretBytes::from_array(bytes);
self.inner.secure_clear();
self.inner = replacement;
self.created_at = std::time::Instant::now();
}
#[inline]
pub fn replace_from_fn(&mut self, make_byte: impl FnMut(usize) -> u8) {
let expired = self.is_expired();
if expired {
self.inner.secure_clear();
}
let replacement = SecretBytes::from_fn(make_byte);
if !expired {
self.inner.secure_clear();
}
self.inner = replacement;
self.created_at = std::time::Instant::now();
}
#[inline]
pub fn try_replace_from_fn<E>(
&mut self,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<(), E> {
let expired = self.is_expired();
if expired {
self.inner.secure_clear();
}
let replacement = SecretBytes::try_from_fn(make_byte)?;
if !expired {
self.inner.secure_clear();
}
self.inner = replacement;
self.created_at = std::time::Instant::now();
Ok(())
}
#[inline]
pub fn try_export_to_slice(
&mut self,
reason: &'static str,
destination: &mut [u8],
) -> Result<(), ExpiringSecretError> {
self.enforce_live()?;
self.inner
.export_to_slice(reason, destination)
.map_err(Into::into)
}
#[inline]
pub fn try_expose_secret<R>(
&mut self,
inspect: impl FnOnce(&[u8; N]) -> R,
) -> Result<R, SecretExpiredError> {
self.enforce_live()?;
Ok(self.inner.expose_secret(inspect))
}
#[inline]
pub fn try_export_secret_copy<R>(
&mut self,
reason: &'static str,
inspect: impl FnOnce(&[u8; N]) -> R,
) -> Result<R, SecretExpiredError> {
self.enforce_live()?;
Ok(self.inner.export_secret_copy(reason, inspect))
}
#[inline]
pub fn try_constant_time_eq(&mut self, other: &[u8]) -> Result<bool, SecretExpiredError> {
self.enforce_live()?;
Ok(self.inner.constant_time_eq(other))
}
#[inline(never)]
pub fn secure_clear(&mut self) {
self.inner.secure_clear();
}
#[inline]
pub fn into_cleared(mut self) {
self.secure_clear();
}
#[inline]
fn enforce_live(&mut self) -> Result<(), SecretExpiredError> {
if self.is_expired() {
self.inner.secure_clear();
Err(SecretExpiredError)
} else {
Ok(())
}
}
}
#[cfg(feature = "std")]
impl<const N: usize> Drop for ExpiringSecretBytes<N> {
#[inline]
fn drop(&mut self) {
self.secure_clear();
}
}
#[cfg(feature = "std")]
impl<const N: usize> SecureSanitize for ExpiringSecretBytes<N> {
#[inline]
fn secure_sanitize(&mut self) {
self.secure_clear();
}
}
#[cfg(feature = "std")]
impl<const N: usize> StableSharedSecretStorage for ExpiringSecretBytes<N> {}
#[cfg(feature = "std")]
impl<const N: usize> StableMutableSecretStorage for ExpiringSecretBytes<N> {}
#[cfg(feature = "std")]
impl<const N: usize> fmt::Debug for ExpiringSecretBytes<N> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ExpiringSecretBytes")
.field("len", &N)
.field("max_age", &self.max_age)
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "alloc")]
pub struct SecretBoxBytes {
pub(crate) inner: Vec<u8>,
}
#[cfg(feature = "alloc")]
impl SecretBoxBytes {
#[must_use]
#[inline]
pub fn zeroed(len: usize) -> Self {
Self {
inner: alloc::vec![0; len],
}
}
#[inline]
pub fn try_zeroed(len: usize, maximum: usize) -> Result<Self, SecretBoxBytesBuildError> {
if len > maximum {
return Err(SecretBoxBytesBuildError::TooLong {
maximum,
actual: len,
});
}
let mut inner = Vec::new();
inner
.try_reserve_exact(len)
.map_err(SecretBoxBytesBuildError::Allocation)?;
inner.resize(len, 0);
Ok(Self { inner })
}
#[must_use]
#[inline]
pub fn from_boxed_slice(inner: Box<[u8]>) -> Self {
Self {
inner: inner.into_vec(),
}
}
#[must_use]
#[inline]
pub fn from_slice(bytes: &[u8]) -> Self {
Self {
inner: Vec::from(bytes),
}
}
#[inline]
pub fn try_from_slice(bytes: &[u8], maximum: usize) -> Result<Self, SecretBoxBytesBuildError> {
let mut secret = Self::try_zeroed(bytes.len(), maximum)?;
secret.inner.copy_from_slice(bytes);
Ok(secret)
}
#[must_use]
#[inline]
pub fn from_fn(len: usize, mut make_byte: impl FnMut(usize) -> u8) -> Self {
let mut secret = Self::zeroed(len);
let mut index = 0;
while index < len {
secret.inner[index] = make_byte(index);
index += 1;
}
secret
}
#[inline]
pub fn try_from_fn<E>(
len: usize,
mut make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, E> {
let mut secret = Self::zeroed(len);
let mut index = 0;
while index < len {
secret.inner[index] = make_byte(index)?;
index += 1;
}
Ok(secret)
}
#[inline]
pub fn try_from_fn_bounded<E>(
len: usize,
maximum: usize,
mut make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, SecretBoxBytesGenerateError<E>> {
let mut secret =
Self::try_zeroed(len, maximum).map_err(SecretBoxBytesGenerateError::Build)?;
let mut index = 0;
while index < len {
secret.inner[index] =
make_byte(index).map_err(SecretBoxBytesGenerateError::Generate)?;
index += 1;
}
Ok(secret)
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn with_secret<R>(&self, inspect: impl FnOnce(&[u8]) -> R) -> R {
inspect(self.inner.as_slice())
}
#[inline]
pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [u8]) -> R) -> R {
edit(self.inner.as_mut_slice())
}
#[inline]
pub fn copy_to_slice(&self, destination: &mut [u8]) -> Result<(), LengthError> {
if destination.len() != self.len() {
return Err(LengthError {
expected: self.len(),
actual: destination.len(),
});
}
destination.copy_from_slice(self.inner.as_slice());
Ok(())
}
#[inline]
pub fn replace_from_slice(&mut self, bytes: &[u8]) -> Result<(), LengthError> {
self.ensure_replacement_len(bytes.len())?;
let replacement = Self::from_slice(bytes);
self.replace_staged(replacement);
Ok(())
}
#[inline]
pub fn replace_from_boxed_slice(&mut self, bytes: Box<[u8]>) -> Result<(), LengthError> {
let replacement = Self::from_boxed_slice(bytes);
self.ensure_replacement_len(replacement.len())?;
self.replace_staged(replacement);
Ok(())
}
#[inline]
pub fn replace_from_fn(&mut self, make_byte: impl FnMut(usize) -> u8) {
let replacement = Self::from_fn(self.len(), make_byte);
self.replace_staged(replacement);
}
#[inline]
pub fn try_replace_from_fn<E>(
&mut self,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<(), E> {
let replacement = Self::try_from_fn(self.len(), make_byte)?;
self.replace_staged(replacement);
Ok(())
}
#[inline(never)]
pub fn clear_secret(&mut self) {
wipe_backend::erase(self.inner.as_mut_ptr(), self.inner.capacity());
}
#[inline]
pub fn into_cleared(mut self) {
self.clear_secret();
}
#[must_use]
#[inline]
pub fn constant_time_eq(&self, other: &[u8]) -> bool {
constant_time_eq_slices(self.inner.as_slice(), other)
}
#[inline]
fn ensure_replacement_len(&self, actual: usize) -> Result<(), LengthError> {
if actual == self.len() {
Ok(())
} else {
Err(LengthError {
expected: self.len(),
actual,
})
}
}
#[inline]
fn replace_staged(&mut self, mut replacement: Self) {
self.clear_secret();
core::mem::swap(&mut self.inner, &mut replacement.inner);
}
}
#[cfg(feature = "alloc")]
impl Drop for SecretBoxBytes {
#[inline]
fn drop(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl Default for SecretBoxBytes {
#[inline]
fn default() -> Self {
Self::zeroed(0)
}
}
#[cfg(feature = "alloc")]
impl SecureSanitize for SecretBoxBytes {
#[inline]
fn secure_sanitize(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl StableSharedSecretStorage for SecretBoxBytes {}
#[cfg(feature = "alloc")]
impl StableMutableSecretStorage for SecretBoxBytes {}
#[cfg(feature = "alloc")]
impl fmt::Debug for SecretBoxBytes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SecretBoxBytes")
.field("len", &self.len())
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "alloc")]
impl ct::ConstantTimeEq for SecretBoxBytes {
#[inline]
fn ct_eq(&self, other: &Self) -> ct::Choice {
ct::eq_public_len(self.inner.as_slice(), other.inner.as_slice())
}
}
#[cfg(feature = "alloc")]
impl ct::ConstantTimeEq<[u8]> for SecretBoxBytes {
#[inline]
fn ct_eq(&self, other: &[u8]) -> ct::Choice {
ct::eq_public_len(self.inner.as_slice(), other)
}
}
#[cfg(feature = "alloc")]
#[derive(Debug)]
pub enum SecretAllocationError {
TooLong {
maximum: usize,
actual: usize,
},
CapacityOverflow,
Allocation(alloc::collections::TryReserveError),
}
#[cfg(feature = "alloc")]
impl From<alloc::collections::TryReserveError> for SecretAllocationError {
#[inline]
fn from(error: alloc::collections::TryReserveError) -> Self {
Self::Allocation(error)
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for SecretAllocationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooLong { maximum, actual } => write!(
formatter,
"secret length exceeds limit: maximum {maximum} bytes, got {actual} bytes"
),
Self::CapacityOverflow => formatter.write_str("secret capacity calculation overflowed"),
Self::Allocation(error) => write!(formatter, "secret allocation failed: {error}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SecretAllocationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::TooLong { .. } | Self::CapacityOverflow => None,
Self::Allocation(error) => Some(error),
}
}
}
#[cfg(feature = "alloc")]
#[derive(Debug)]
pub enum SecretGenerateError<E> {
Build(SecretAllocationError),
Generate(E),
}
#[cfg(feature = "alloc")]
impl<E> From<SecretAllocationError> for SecretGenerateError<E> {
#[inline]
fn from(error: SecretAllocationError) -> Self {
Self::Build(error)
}
}
#[cfg(feature = "alloc")]
impl<E: fmt::Display> fmt::Display for SecretGenerateError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Build(error) => error.fmt(formatter),
Self::Generate(error) => write!(formatter, "secret generation failed: {error}"),
}
}
}
#[cfg(feature = "std")]
impl<E> std::error::Error for SecretGenerateError<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Build(error) => Some(error),
Self::Generate(error) => Some(error),
}
}
}
#[cfg(feature = "alloc")]
pub type SecretBoxBytesBuildError = SecretAllocationError;
#[cfg(feature = "alloc")]
pub type SecretBoxBytesGenerateError<E> = SecretGenerateError<E>;
#[cfg(feature = "alloc")]
pub struct SecretVec {
pub(crate) inner: Vec<u8>,
}
#[cfg(feature = "alloc")]
pub const DEFAULT_SECRET_VEC_SERDE_MAX_LEN: usize = 1024 * 1024;
#[cfg(feature = "alloc")]
impl SecretVec {
#[must_use]
#[inline]
pub const fn new(inner: Vec<u8>) -> Self {
Self { inner }
}
#[must_use]
#[inline]
pub const fn from_vec(bytes: Vec<u8>) -> Self {
Self::new(bytes)
}
#[must_use]
#[inline]
pub const fn empty() -> Self {
Self::new(Vec::new())
}
#[must_use]
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self::new(Vec::with_capacity(capacity))
}
#[inline]
pub fn try_with_capacity(capacity: usize) -> Result<Self, SecretAllocationError> {
let mut inner = Vec::new();
inner
.try_reserve_exact(capacity)
.map_err(SecretAllocationError::Allocation)?;
Ok(Self::new(inner))
}
#[must_use]
#[inline]
pub fn from_slice(bytes: &[u8]) -> Self {
Self::new(Vec::from(bytes))
}
#[inline]
pub fn try_from_slice_bounded(
bytes: &[u8],
maximum: usize,
) -> Result<Self, SecretAllocationError> {
if bytes.len() > maximum {
return Err(SecretAllocationError::TooLong {
maximum,
actual: bytes.len(),
});
}
let mut secret = Self::try_with_capacity(bytes.len())?;
secret.inner.extend_from_slice(bytes);
Ok(secret)
}
#[must_use]
#[inline]
pub fn from_fn(len: usize, mut make_byte: impl FnMut(usize) -> u8) -> Self {
let mut secret = Self::with_capacity(len);
let mut index = 0;
while index < len {
secret.inner.push(make_byte(index));
index += 1;
}
secret
}
#[inline]
pub fn try_from_fn<E>(
len: usize,
mut make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, SecretGenerateError<E>> {
let mut secret = Self::try_with_capacity(len).map_err(SecretGenerateError::Build)?;
let mut index = 0;
while index < len {
let byte = make_byte(index).map_err(SecretGenerateError::Generate)?;
secret.inner.push(byte);
index += 1;
}
Ok(secret)
}
#[inline]
pub fn try_from_fn_bounded<E>(
len: usize,
maximum: usize,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<Self, SecretGenerateError<E>> {
if len > maximum {
return Err(SecretGenerateError::Build(SecretAllocationError::TooLong {
maximum,
actual: len,
}));
}
Self::try_from_fn(len, make_byte)
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[must_use]
#[inline]
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[inline]
pub fn with_secret<R>(&self, inspect: impl FnOnce(&[u8]) -> R) -> R {
inspect(self.inner.as_slice())
}
#[inline]
pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [u8]) -> R) -> R {
edit(self.inner.as_mut_slice())
}
#[inline]
pub fn extend_from_slice(&mut self, bytes: &[u8]) {
self.grow_for(bytes.len());
self.inner.extend_from_slice(bytes);
}
#[inline]
pub fn replace_from_slice(&mut self, bytes: &[u8]) {
if bytes.len() > self.inner.capacity() {
let new_capacity = next_secret_capacity(self.inner.capacity(), bytes.len());
let mut replacement = Vec::with_capacity(new_capacity);
replacement.extend_from_slice(bytes);
self.clear_secret();
self.inner = replacement;
return;
}
self.clear_secret();
self.inner.extend_from_slice(bytes);
}
#[inline]
pub fn replace_from_vec(&mut self, bytes: Vec<u8>) {
self.clear_secret();
self.inner = bytes;
}
#[inline]
pub fn replace_from_fn(&mut self, len: usize, make_byte: impl FnMut(usize) -> u8) {
let mut replacement = Self::from_fn(len, make_byte);
self.clear_secret();
core::mem::swap(&mut self.inner, &mut replacement.inner);
}
#[inline]
pub fn try_replace_from_fn<E>(
&mut self,
len: usize,
make_byte: impl FnMut(usize) -> Result<u8, E>,
) -> Result<(), SecretGenerateError<E>> {
let mut replacement = Self::try_from_fn(len, make_byte)?;
self.clear_secret();
core::mem::swap(&mut self.inner, &mut replacement.inner);
Ok(())
}
#[inline(never)]
pub fn clear_secret(&mut self) {
sanitize_vec_capacity(&mut self.inner);
}
#[cfg(feature = "multi-pass-clear")]
#[inline(never)]
pub fn clear_secret_multi_pass(&mut self) {
sanitize_vec_capacity_multi_pass(&mut self.inner);
}
#[cfg(feature = "cache-flush")]
#[inline(never)]
pub fn clear_secret_and_flush(
&mut self,
) -> Result<crate::cache_flush::CacheFlushReport, crate::cache_flush::CacheFlushError> {
crate::cache_flush::cache_flush_sanitize_vec(&mut self.inner)
}
#[must_use]
#[inline]
pub fn constant_time_eq(&self, other: &[u8]) -> bool {
constant_time_eq_slices(self.inner.as_slice(), other)
}
#[inline]
pub fn into_cleared(mut self) {
self.clear_secret();
}
#[inline]
pub fn try_into_secret_string(self) -> Result<SecretString, core::str::Utf8Error> {
SecretString::from_secret_vec(self)
}
fn grow_for(&mut self, additional: usize) {
let required = self.inner.len().saturating_add(additional);
if required <= self.inner.capacity() {
return;
}
let new_capacity = next_secret_capacity(self.inner.capacity(), required);
let mut replacement = Vec::with_capacity(new_capacity);
replacement.extend_from_slice(self.inner.as_slice());
self.clear_secret();
self.inner = replacement;
}
}
#[cfg(feature = "alloc")]
impl Drop for SecretVec {
#[inline]
fn drop(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl Default for SecretVec {
#[inline]
fn default() -> Self {
Self::empty()
}
}
#[cfg(feature = "alloc")]
impl SecureSanitize for SecretVec {
#[inline]
fn secure_sanitize(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl fmt::Debug for SecretVec {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SecretVec")
.field("len", &self.inner.len())
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "alloc")]
impl ct::ConstantTimeEq for SecretVec {
#[inline]
fn ct_eq(&self, other: &Self) -> ct::Choice {
ct::eq_public_len(self.inner.as_slice(), other.inner.as_slice())
}
}
#[cfg(feature = "alloc")]
impl ct::ConstantTimeEq<[u8]> for SecretVec {
#[inline]
fn ct_eq(&self, other: &[u8]) -> ct::Choice {
ct::eq_public_len(self.inner.as_slice(), other)
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SecretVecLimitError {
pub maximum: usize,
pub actual: usize,
}
#[cfg(feature = "alloc")]
impl fmt::Display for SecretVecLimitError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"secret length exceeds limit: maximum {} bytes, got {} bytes",
self.maximum, self.actual
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for SecretVecLimitError {}
#[cfg(feature = "alloc")]
pub struct BoundedSecretVec<const MAX: usize> {
pub(crate) inner: SecretVec,
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> BoundedSecretVec<MAX> {
#[must_use]
#[inline]
pub const fn empty() -> Self {
Self {
inner: SecretVec::empty(),
}
}
#[inline]
pub fn from_slice(bytes: &[u8]) -> Result<Self, SecretVecLimitError> {
Self::validate_len(bytes.len())?;
Ok(Self {
inner: SecretVec::from_slice(bytes),
})
}
#[inline]
pub fn from_vec(mut bytes: Vec<u8>) -> Result<Self, SecretVecLimitError> {
if let Err(error) = Self::validate_len(bytes.len()) {
sanitize_vec_capacity(&mut bytes);
return Err(error);
}
Ok(Self {
inner: SecretVec::from_vec(bytes),
})
}
#[inline]
pub fn from_secret_vec(mut secret: SecretVec) -> Result<Self, SecretVecLimitError> {
if let Err(error) = Self::validate_len(secret.len()) {
secret.clear_secret();
return Err(error);
}
Ok(Self { inner: secret })
}
#[must_use]
#[inline]
pub const fn max_len() -> usize {
MAX
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn with_secret<R>(&self, inspect: impl FnOnce(&[u8]) -> R) -> R {
self.inner.with_secret(inspect)
}
#[inline]
pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [u8]) -> R) -> R {
self.inner.with_secret_mut(edit)
}
#[inline]
pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), SecretVecLimitError> {
let actual = self.len().saturating_add(bytes.len());
Self::validate_len(actual)?;
self.inner.extend_from_slice(bytes);
Ok(())
}
#[inline]
pub fn replace_from_slice(&mut self, bytes: &[u8]) -> Result<(), SecretVecLimitError> {
Self::validate_len(bytes.len())?;
self.inner.replace_from_slice(bytes);
Ok(())
}
#[inline(never)]
pub fn clear_secret(&mut self) {
self.inner.clear_secret();
}
#[must_use]
#[inline]
pub fn into_secret_vec(self) -> SecretVec {
self.inner
}
#[inline]
fn validate_len(actual: usize) -> Result<(), SecretVecLimitError> {
if actual > MAX {
Err(SecretVecLimitError {
maximum: MAX,
actual,
})
} else {
Ok(())
}
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> Default for BoundedSecretVec<MAX> {
#[inline]
fn default() -> Self {
Self::empty()
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> From<BoundedSecretVec<MAX>> for SecretVec {
#[inline]
fn from(secret: BoundedSecretVec<MAX>) -> Self {
secret.into_secret_vec()
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> SecureSanitize for BoundedSecretVec<MAX> {
#[inline]
fn secure_sanitize(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> fmt::Debug for BoundedSecretVec<MAX> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("BoundedSecretVec")
.field("len", &self.len())
.field("max_len", &MAX)
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> ct::ConstantTimeEq for BoundedSecretVec<MAX> {
#[inline]
fn ct_eq(&self, other: &Self) -> ct::Choice {
self.inner.ct_eq(&other.inner)
}
}
#[cfg(feature = "alloc")]
pub struct SecretString {
pub(crate) inner: Vec<u8>,
}
#[cfg(feature = "alloc")]
pub const DEFAULT_SECRET_STRING_SERDE_MAX_LEN: usize = 1024 * 1024;
#[cfg(feature = "alloc")]
impl SecretString {
#[must_use]
#[inline]
pub fn new(inner: String) -> Self {
Self {
inner: inner.into_bytes(),
}
}
#[must_use]
#[inline]
pub fn from_string(text: String) -> Self {
Self::new(text)
}
#[inline]
pub fn from_secret_vec(mut secret: SecretVec) -> Result<Self, core::str::Utf8Error> {
if let Err(error) = core::str::from_utf8(secret.inner.as_slice()) {
secret.clear_secret();
return Err(error);
}
Ok(Self {
inner: core::mem::take(&mut secret.inner),
})
}
#[must_use]
#[inline]
pub const fn empty() -> Self {
Self { inner: Vec::new() }
}
#[must_use]
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: Vec::with_capacity(capacity),
}
}
#[inline]
pub fn try_with_capacity(capacity: usize) -> Result<Self, SecretAllocationError> {
let mut inner = Vec::new();
inner
.try_reserve_exact(capacity)
.map_err(SecretAllocationError::Allocation)?;
Ok(Self { inner })
}
#[must_use]
#[inline]
pub fn from_secret_str(text: &str) -> Self {
Self {
inner: Vec::from(text.as_bytes()),
}
}
#[inline]
pub fn try_from_secret_str_bounded(
text: &str,
maximum_bytes: usize,
) -> Result<Self, SecretAllocationError> {
if text.len() > maximum_bytes {
return Err(SecretAllocationError::TooLong {
maximum: maximum_bytes,
actual: text.len(),
});
}
let mut secret = Self::try_with_capacity(text.len())?;
secret.inner.extend_from_slice(text.as_bytes());
Ok(secret)
}
#[must_use]
#[inline]
pub fn from_chars(char_count: usize, mut make_char: impl FnMut(usize) -> char) -> Self {
let capacity = char_count
.checked_mul(MAX_UTF8_CHAR_BYTES)
.expect("secret UTF-8 capacity calculation overflowed");
let mut secret = Self::with_capacity(capacity);
let mut index = 0;
while index < char_count {
secret.push_secret_char(make_char(index));
index += 1;
}
secret
}
#[inline]
pub fn try_from_chars<E>(
char_count: usize,
mut make_char: impl FnMut(usize) -> Result<char, E>,
) -> Result<Self, SecretGenerateError<E>> {
let capacity =
char_count
.checked_mul(MAX_UTF8_CHAR_BYTES)
.ok_or(SecretGenerateError::Build(
SecretAllocationError::CapacityOverflow,
))?;
let mut secret = Self::try_with_capacity(capacity).map_err(SecretGenerateError::Build)?;
let mut index = 0;
while index < char_count {
let character = make_char(index).map_err(SecretGenerateError::Generate)?;
secret.push_secret_char(character);
index += 1;
}
Ok(secret)
}
#[inline]
pub fn try_from_chars_bounded<E>(
char_count: usize,
maximum_bytes: usize,
make_char: impl FnMut(usize) -> Result<char, E>,
) -> Result<Self, SecretGenerateError<E>> {
let capacity =
char_count
.checked_mul(MAX_UTF8_CHAR_BYTES)
.ok_or(SecretGenerateError::Build(
SecretAllocationError::CapacityOverflow,
))?;
if capacity > maximum_bytes {
return Err(SecretGenerateError::Build(SecretAllocationError::TooLong {
maximum: maximum_bytes,
actual: capacity,
}));
}
Self::try_from_chars(char_count, make_char)
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[must_use]
#[inline]
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[inline]
pub fn try_with_secret<R>(
&self,
inspect: impl FnOnce(&str) -> R,
) -> Result<R, core::str::Utf8Error> {
core::str::from_utf8(self.inner.as_slice()).map(inspect)
}
#[inline]
pub fn try_with_secret_mut<R>(
&mut self,
edit: impl FnOnce(&mut str) -> R,
) -> Result<R, core::str::Utf8Error> {
core::str::from_utf8_mut(self.inner.as_mut_slice()).map(edit)
}
#[inline]
pub fn with_secret_bytes<R>(&self, inspect: impl FnOnce(&[u8]) -> R) -> R {
inspect(self.inner.as_slice())
}
#[inline]
pub fn push_str(&mut self, text: &str) {
self.grow_for(text.len());
self.inner.extend_from_slice(text.as_bytes());
}
#[inline]
pub fn replace_from_secret_str(&mut self, text: &str) {
if text.len() > self.inner.capacity() {
let new_capacity = next_secret_capacity(self.inner.capacity(), text.len());
let mut replacement = Vec::with_capacity(new_capacity);
replacement.extend_from_slice(text.as_bytes());
self.clear_secret();
self.inner = replacement;
return;
}
self.clear_secret();
self.inner.extend_from_slice(text.as_bytes());
}
#[inline]
pub fn replace_from_string(&mut self, text: String) {
let replacement = text.into_bytes();
self.clear_secret();
self.inner = replacement;
}
#[inline]
pub fn replace_from_chars(&mut self, char_count: usize, make_char: impl FnMut(usize) -> char) {
let mut replacement = Self::from_chars(char_count, make_char);
self.clear_secret();
core::mem::swap(&mut self.inner, &mut replacement.inner);
}
#[inline]
pub fn try_replace_from_chars<E>(
&mut self,
char_count: usize,
make_char: impl FnMut(usize) -> Result<char, E>,
) -> Result<(), SecretGenerateError<E>> {
let mut replacement = Self::try_from_chars(char_count, make_char)?;
self.clear_secret();
core::mem::swap(&mut self.inner, &mut replacement.inner);
Ok(())
}
#[inline(never)]
pub fn clear_secret(&mut self) {
sanitize_vec_capacity(&mut self.inner);
}
#[cfg(feature = "multi-pass-clear")]
#[inline(never)]
pub fn clear_secret_multi_pass(&mut self) {
sanitize_vec_capacity_multi_pass(&mut self.inner);
}
#[cfg(feature = "cache-flush")]
#[inline(never)]
pub fn clear_secret_and_flush(
&mut self,
) -> Result<crate::cache_flush::CacheFlushReport, crate::cache_flush::CacheFlushError> {
crate::cache_flush::cache_flush_sanitize_vec(&mut self.inner)
}
#[must_use]
#[inline]
pub fn constant_time_eq(&self, other: &str) -> bool {
constant_time_eq_slices(self.inner.as_slice(), other.as_bytes())
}
#[inline]
pub fn into_cleared(mut self) {
self.clear_secret();
}
#[must_use]
#[inline]
pub fn into_secret_vec(mut self) -> SecretVec {
SecretVec::from_vec(core::mem::take(&mut self.inner))
}
fn grow_for(&mut self, additional: usize) {
let required = self.inner.len().saturating_add(additional);
if required <= self.inner.capacity() {
return;
}
let new_capacity = next_secret_capacity(self.inner.capacity(), required);
let mut replacement = Vec::with_capacity(new_capacity);
replacement.extend_from_slice(self.inner.as_slice());
self.clear_secret();
self.inner = replacement;
}
fn push_secret_char(&mut self, character: char) {
let mut encoded = [0; 4];
let text = character.encode_utf8(&mut encoded);
self.inner.extend_from_slice(text.as_bytes());
wipe::bytes(&mut encoded);
}
}
#[cfg(feature = "alloc")]
impl Drop for SecretString {
#[inline]
fn drop(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl Default for SecretString {
#[inline]
fn default() -> Self {
Self::empty()
}
}
#[cfg(feature = "alloc")]
impl SecureSanitize for SecretString {
#[inline]
fn secure_sanitize(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl fmt::Debug for SecretString {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SecretString")
.field("len", &self.inner.len())
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "alloc")]
impl ct::ConstantTimeEq for SecretString {
#[inline]
fn ct_eq(&self, other: &Self) -> ct::Choice {
ct::eq_public_len(self.inner.as_slice(), other.inner.as_slice())
}
}
#[cfg(feature = "alloc")]
impl ct::ConstantTimeEq<str> for SecretString {
#[inline]
fn ct_eq(&self, other: &str) -> ct::Choice {
ct::eq_public_len(self.inner.as_slice(), other.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl TryFrom<SecretVec> for SecretString {
type Error = core::str::Utf8Error;
#[inline]
fn try_from(secret: SecretVec) -> Result<Self, Self::Error> {
Self::from_secret_vec(secret)
}
}
#[cfg(feature = "alloc")]
impl From<SecretString> for SecretVec {
#[inline]
fn from(secret: SecretString) -> Self {
secret.into_secret_vec()
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SecretStringLimitError {
pub maximum: usize,
pub actual: usize,
}
#[cfg(feature = "alloc")]
impl fmt::Display for SecretStringLimitError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"secret text length exceeds limit: maximum {} UTF-8 bytes, got {} bytes",
self.maximum, self.actual
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for SecretStringLimitError {}
#[cfg(feature = "alloc")]
pub struct BoundedSecretString<const MAX: usize> {
pub(crate) inner: SecretString,
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> BoundedSecretString<MAX> {
#[must_use]
#[inline]
pub const fn empty() -> Self {
Self {
inner: SecretString::empty(),
}
}
#[inline]
pub fn from_secret_str(text: &str) -> Result<Self, SecretStringLimitError> {
Self::validate_len(text.len())?;
Ok(Self {
inner: SecretString::from_secret_str(text),
})
}
#[inline]
pub fn from_string(mut text: String) -> Result<Self, SecretStringLimitError> {
if let Err(error) = Self::validate_len(text.len()) {
text.secure_sanitize();
return Err(error);
}
Ok(Self {
inner: SecretString::from_string(text),
})
}
#[inline]
pub fn from_secret_string(mut secret: SecretString) -> Result<Self, SecretStringLimitError> {
if let Err(error) = Self::validate_len(secret.len()) {
secret.clear_secret();
return Err(error);
}
Ok(Self { inner: secret })
}
#[inline]
pub fn from_secret_vec(secret: SecretVec) -> Result<Self, BoundedSecretStringError> {
let text = SecretString::from_secret_vec(secret).map_err(BoundedSecretStringError::Utf8)?;
Self::from_secret_string(text).map_err(BoundedSecretStringError::Limit)
}
#[must_use]
#[inline]
pub const fn max_len() -> usize {
MAX
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[must_use]
#[inline]
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[inline]
pub fn try_with_secret<R>(
&self,
inspect: impl FnOnce(&str) -> R,
) -> Result<R, core::str::Utf8Error> {
self.inner.try_with_secret(inspect)
}
#[inline]
pub fn try_with_secret_mut<R>(
&mut self,
edit: impl FnOnce(&mut str) -> R,
) -> Result<R, core::str::Utf8Error> {
self.inner.try_with_secret_mut(edit)
}
#[inline]
pub fn push_str(&mut self, text: &str) -> Result<(), SecretStringLimitError> {
Self::validate_len(self.len().saturating_add(text.len()))?;
self.inner.push_str(text);
Ok(())
}
#[inline]
pub fn replace_from_secret_str(&mut self, text: &str) -> Result<(), SecretStringLimitError> {
Self::validate_len(text.len())?;
self.inner.replace_from_secret_str(text);
Ok(())
}
#[inline]
pub fn replace_from_string(&mut self, text: String) -> Result<(), SecretStringLimitError> {
let mut replacement = Self::from_string(text)?;
self.clear_secret();
core::mem::swap(&mut self.inner, &mut replacement.inner);
Ok(())
}
#[inline(never)]
pub fn clear_secret(&mut self) {
self.inner.clear_secret();
}
#[must_use]
#[inline]
pub fn constant_time_eq(&self, other: &str) -> bool {
self.inner.constant_time_eq(other)
}
#[must_use]
#[inline]
pub fn into_secret_string(self) -> SecretString {
self.inner
}
#[must_use]
#[inline]
pub fn into_secret_vec(self) -> SecretVec {
self.inner.into_secret_vec()
}
#[inline]
fn validate_len(actual: usize) -> Result<(), SecretStringLimitError> {
if actual > MAX {
Err(SecretStringLimitError {
maximum: MAX,
actual,
})
} else {
Ok(())
}
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BoundedSecretStringError {
Utf8(core::str::Utf8Error),
Limit(SecretStringLimitError),
}
#[cfg(feature = "alloc")]
impl fmt::Display for BoundedSecretStringError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Utf8(error) => error.fmt(formatter),
Self::Limit(error) => error.fmt(formatter),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for BoundedSecretStringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Utf8(error) => Some(error),
Self::Limit(error) => Some(error),
}
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> Default for BoundedSecretString<MAX> {
#[inline]
fn default() -> Self {
Self::empty()
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> SecureSanitize for BoundedSecretString<MAX> {
#[inline]
fn secure_sanitize(&mut self) {
self.clear_secret();
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> fmt::Debug for BoundedSecretString<MAX> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("BoundedSecretString")
.field("len", &self.len())
.field("max_len", &MAX)
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> ct::ConstantTimeEq for BoundedSecretString<MAX> {
#[inline]
fn ct_eq(&self, other: &Self) -> ct::Choice {
self.inner.ct_eq(&other.inner)
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> ct::ConstantTimeEq<str> for BoundedSecretString<MAX> {
#[inline]
fn ct_eq(&self, other: &str) -> ct::Choice {
self.inner.ct_eq(other)
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> From<BoundedSecretString<MAX>> for SecretString {
#[inline]
fn from(secret: BoundedSecretString<MAX>) -> Self {
secret.into_secret_string()
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> TryFrom<SecretString> for BoundedSecretString<MAX> {
type Error = SecretStringLimitError;
#[inline]
fn try_from(secret: SecretString) -> Result<Self, Self::Error> {
Self::from_secret_string(secret)
}
}
#[cfg(feature = "alloc")]
impl<const MAX: usize> TryFrom<SecretVec> for BoundedSecretString<MAX> {
type Error = BoundedSecretStringError;
#[inline]
fn try_from(secret: SecretVec) -> Result<Self, Self::Error> {
Self::from_secret_vec(secret)
}
}
pub struct Secret<T: SecureSanitize> {
inner: T,
}
impl<T: SecureSanitize> Secret<T> {
#[must_use]
#[inline]
pub const fn new(inner: T) -> Self {
Self { inner }
}
#[inline]
pub fn into_cleared(mut self) {
self.inner.secure_sanitize();
}
}
impl<T: StableSharedSecretStorage> Secret<T> {
#[inline]
pub fn with_secret<R>(&self, inspect: impl FnOnce(&T) -> R) -> R {
inspect(&self.inner)
}
}
impl<T: StableMutableSecretStorage> Secret<T> {
#[inline]
pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut T) -> R) -> R {
edit(&mut self.inner)
}
}
impl<T: SecureSanitize> SecureSanitize for Secret<T> {
#[inline]
fn secure_sanitize(&mut self) {
self.inner.secure_sanitize();
}
}
impl<T: StableSharedSecretStorage> StableSharedSecretStorage for Secret<T> {}
impl<T: StableMutableSecretStorage> StableMutableSecretStorage for Secret<T> {}
impl<T: SecureSanitize> Drop for Secret<T> {
#[inline]
fn drop(&mut self) {
self.secure_sanitize();
}
}
impl<T: SecureSanitize + Default> Default for Secret<T> {
#[inline]
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: SecureSanitize> fmt::Debug for Secret<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Secret")
.field("contents", &"<redacted>")
.finish()
}
}
pub struct AllowlistedSecret<T: SecureSanitize, P> {
inner: T,
policy: PhantomData<fn() -> P>,
}
impl<T, P> AllowlistedSecret<T, P>
where
T: SecureSanitize,
P: SecretStoragePolicy<T>,
{
#[must_use]
#[inline]
pub const fn new(inner: T) -> Self {
Self {
inner,
policy: PhantomData,
}
}
#[must_use]
#[inline]
pub const fn policy_rationale() -> &'static str {
P::RATIONALE
}
#[inline]
pub fn into_cleared(mut self) {
self.inner.secure_sanitize();
}
}
impl<T, P> AllowlistedSecret<T, P>
where
T: StableSharedSecretStorage,
P: SecretStoragePolicy<T>,
{
#[inline]
pub fn with_secret<R>(&self, inspect: impl FnOnce(&T) -> R) -> R {
inspect(&self.inner)
}
}
impl<T, P> AllowlistedSecret<T, P>
where
T: StableMutableSecretStorage,
P: SecretStoragePolicy<T>,
{
#[inline]
pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut T) -> R) -> R {
edit(&mut self.inner)
}
}
impl<T: SecureSanitize, P> SecureSanitize for AllowlistedSecret<T, P> {
#[inline]
fn secure_sanitize(&mut self) {
self.inner.secure_sanitize();
}
}
impl<T, P> StableSharedSecretStorage for AllowlistedSecret<T, P>
where
T: StableSharedSecretStorage,
P: SecretStoragePolicy<T>,
{
}
impl<T, P> StableMutableSecretStorage for AllowlistedSecret<T, P>
where
T: StableMutableSecretStorage,
P: SecretStoragePolicy<T>,
{
}
impl<T: SecureSanitize, P> Drop for AllowlistedSecret<T, P> {
#[inline]
fn drop(&mut self) {
self.secure_sanitize();
}
}
impl<T, P> Default for AllowlistedSecret<T, P>
where
T: SecureSanitize + Default,
P: SecretStoragePolicy<T>,
{
#[inline]
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: SecureSanitize, P> fmt::Debug for AllowlistedSecret<T, P> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AllowlistedSecret")
.field("contents", &"<redacted>")
.finish()
}
}
#[allow(unsafe_code)]
mod consume_once {
use super::{fmt, SecureSanitize, StableMutableSecretStorage, StableSharedSecretStorage};
use core::{
cell::UnsafeCell,
sync::atomic::{AtomicBool, Ordering},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AlreadyConsumedError;
impl fmt::Display for AlreadyConsumedError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("consume-once secret already claimed")
}
}
#[cfg(feature = "std")]
impl std::error::Error for AlreadyConsumedError {}
pub struct ConsumeOnceSecret<T: SecureSanitize> {
inner: UnsafeCell<T>,
claimed: AtomicBool,
}
struct ClearOnExit<'a, T: SecureSanitize> {
owner: &'a ConsumeOnceSecret<T>,
}
impl<T: SecureSanitize> Drop for ClearOnExit<'_, T> {
#[inline]
fn drop(&mut self) {
self.owner.clear_inner();
}
}
unsafe impl<T: SecureSanitize + Send> Send for ConsumeOnceSecret<T> {}
unsafe impl<T: SecureSanitize + Send> Sync for ConsumeOnceSecret<T> {}
impl<T: SecureSanitize> ConsumeOnceSecret<T> {
#[must_use]
#[inline]
pub const fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
claimed: AtomicBool::new(false),
}
}
#[inline]
pub fn into_cleared(mut self) {
self.claimed.store(true, Ordering::Release);
self.inner.get_mut().secure_sanitize();
}
#[must_use]
#[inline]
pub fn is_claimed(&self) -> bool {
self.claimed.load(Ordering::Acquire)
}
#[inline]
fn claim(&self) -> Result<ClearOnExit<'_, T>, AlreadyConsumedError> {
if self.claimed.swap(true, Ordering::AcqRel) {
Err(AlreadyConsumedError)
} else {
Ok(ClearOnExit { owner: self })
}
}
#[inline]
fn clear_inner(&self) {
unsafe { (&mut *self.inner.get()).secure_sanitize() };
}
}
impl<T: StableSharedSecretStorage> ConsumeOnceSecret<T> {
#[inline]
pub fn consume<R>(&self, inspect: impl FnOnce(&T) -> R) -> Result<R, AlreadyConsumedError> {
let clear_guard = self.claim()?;
let result = inspect(unsafe { &*self.inner.get() });
drop(clear_guard);
Ok(result)
}
}
impl<T: SecureSanitize> Drop for ConsumeOnceSecret<T> {
#[inline]
fn drop(&mut self) {
self.inner.get_mut().secure_sanitize();
}
}
impl<T: SecureSanitize + Default> Default for ConsumeOnceSecret<T> {
#[inline]
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: SecureSanitize> SecureSanitize for ConsumeOnceSecret<T> {
#[inline]
fn secure_sanitize(&mut self) {
self.claimed.store(true, Ordering::Release);
self.inner.get_mut().secure_sanitize();
}
}
impl<T: StableSharedSecretStorage> StableSharedSecretStorage for ConsumeOnceSecret<T> {}
impl<T: StableMutableSecretStorage> StableMutableSecretStorage for ConsumeOnceSecret<T> {}
impl<T: SecureSanitize> fmt::Debug for ConsumeOnceSecret<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ConsumeOnceSecret")
.field("contents", &"<redacted>")
.finish()
}
}
}
pub use consume_once::{AlreadyConsumedError, ConsumeOnceSecret};