#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt;
use crate::traits::ct;
pub struct SecretBytes<const N: usize>([u8; N]);
impl<const N: usize> SecretBytes<N> {
pub const LENGTH: usize = N;
#[inline]
#[must_use]
pub const fn new(bytes: [u8; N]) -> Self {
Self(bytes)
}
#[inline]
#[must_use]
pub const fn as_bytes(&self) -> &[u8; N] {
&self.0
}
#[inline]
#[must_use]
pub fn expose(mut self) -> [u8; N] {
let bytes = self.0;
ct::zeroize(&mut self.0);
bytes
}
}
impl<const N: usize> From<[u8; N]> for SecretBytes<N> {
#[inline]
fn from(value: [u8; N]) -> Self {
Self::new(value)
}
}
impl<const N: usize> AsRef<[u8]> for SecretBytes<N> {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const N: usize> fmt::Debug for SecretBytes<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SecretBytes(****)")
}
}
impl<const N: usize> Drop for SecretBytes<N> {
fn drop(&mut self) {
ct::zeroize(&mut self.0);
}
}
#[cfg(feature = "alloc")]
pub struct SecretVec(Vec<u8>);
#[cfg(feature = "alloc")]
impl SecretVec {
#[inline]
#[must_use]
#[cfg(any(feature = "rsa", feature = "diag", test))]
pub(crate) const fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline(always)]
pub fn clear(&mut self) {
ct::zeroize(&mut self.0);
}
#[inline]
#[must_use]
pub fn into_unprotected_vec(mut self) -> Vec<u8> {
core::mem::take(&mut self.0)
}
}
#[cfg(feature = "alloc")]
impl AsRef<[u8]> for SecretVec {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
#[cfg(feature = "alloc")]
impl core::ops::Deref for SecretVec {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
self.as_bytes()
}
}
#[cfg(feature = "alloc")]
impl fmt::Debug for SecretVec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SecretVec(****)")
}
}
#[cfg(feature = "alloc")]
impl Drop for SecretVec {
fn drop(&mut self) {
self.clear();
}
}
#[cfg(any(
feature = "aes-gcm",
feature = "aes-gcm-siv",
feature = "chacha20poly1305",
feature = "xchacha20poly1305",
feature = "aegis256",
feature = "ascon-aead",
all(feature = "phc-strings", any(feature = "argon2", feature = "scrypt")),
feature = "ecdsa-p256",
feature = "ecdsa-p384",
feature = "ed25519",
feature = "ml-kem",
feature = "poly1305",
feature = "x25519"
))]
pub(crate) struct ZeroizingBytes<const N: usize>([u8; N]);
#[cfg(any(
feature = "aes-gcm",
feature = "aes-gcm-siv",
feature = "chacha20poly1305",
feature = "xchacha20poly1305",
feature = "aegis256",
feature = "ascon-aead",
all(feature = "phc-strings", any(feature = "argon2", feature = "scrypt")),
feature = "ecdsa-p256",
feature = "ecdsa-p384",
feature = "ed25519",
feature = "ml-kem",
feature = "poly1305",
feature = "x25519"
))]
impl<const N: usize> ZeroizingBytes<N> {
#[inline]
#[cfg(any(feature = "serde-secrets", feature = "ecdsa-p256", feature = "ecdsa-p384"))]
pub(crate) const fn new(bytes: [u8; N]) -> Self {
Self(bytes)
}
#[inline]
pub(crate) const fn zeroed() -> Self {
Self([0u8; N])
}
#[inline]
pub(crate) const fn as_array(&self) -> &[u8; N] {
&self.0
}
#[inline]
pub(crate) fn as_mut_array(&mut self) -> &mut [u8; N] {
&mut self.0
}
}
#[cfg(any(
feature = "aes-gcm",
feature = "aes-gcm-siv",
feature = "chacha20poly1305",
feature = "xchacha20poly1305",
feature = "aegis256",
feature = "ascon-aead",
all(feature = "phc-strings", any(feature = "argon2", feature = "scrypt")),
feature = "ecdsa-p256",
feature = "ecdsa-p384",
feature = "ed25519",
feature = "ml-kem",
feature = "poly1305",
feature = "x25519"
))]
impl<const N: usize> Drop for ZeroizingBytes<N> {
fn drop(&mut self) {
ct::zeroize(&mut self.0);
}
}
#[cfg(feature = "diag")]
#[doc(hidden)]
#[unsafe(no_mangle)]
#[inline(never)]
pub fn diag_zeroize_fixed_stack(input: [u8; 32]) -> u8 {
let secret = SecretBytes::new(input);
core::hint::black_box(secret.as_bytes()[0])
}
#[cfg(feature = "diag")]
#[doc(hidden)]
#[unsafe(no_mangle)]
#[inline(never)]
pub fn diag_zeroize_fixed_move(input: [u8; 32]) -> u8 {
let secret = SecretBytes::new(input);
let mut exposed = secret.expose();
let output = core::hint::black_box(exposed[0]);
ct::zeroize(&mut exposed);
output
}
#[cfg(feature = "diag")]
#[doc(hidden)]
#[unsafe(no_mangle)]
#[inline(never)]
pub fn diag_zeroize_early_return(input: [u8; 32], stop: bool) -> u8 {
let secret = SecretBytes::new(input);
if core::hint::black_box(stop) {
return 0;
}
core::hint::black_box(secret.as_bytes()[0])
}
#[cfg(all(feature = "diag", feature = "alloc"))]
#[doc(hidden)]
#[unsafe(no_mangle)]
#[inline(never)]
pub fn diag_zeroize_variable_heap(input: Vec<u8>) -> usize {
let secret = SecretVec::new(input);
core::hint::black_box(secret.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secret_bytes_debug_masks_contents() {
assert_eq!(alloc::format!("{:?}", SecretBytes::new([0x42; 4])), "SecretBytes(****)");
}
#[cfg(feature = "alloc")]
#[test]
fn secret_vec_requires_explicit_unprotected_extraction() {
let protected = SecretVec::new(alloc::vec![0x42, 0x24]);
assert_eq!(protected.as_bytes(), [0x42, 0x24]);
assert_eq!(alloc::format!("{protected:?}"), "SecretVec(****)");
assert_eq!(protected.into_unprotected_vec(), [0x42, 0x24]);
}
}