#![forbid(unsafe_code)]
use core::str::FromStr;
use core::{any, fmt};
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::String, vec::Vec};
use crate::der::{self, Decode, Encode, FixedTag};
use crate::zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecretError {
Unavailable,
}
crate::impl_error_display!(unconditional SecretError {
Unavailable => "Secret value is unavailable",
});
pub struct Secret<S: Zeroize + ?Sized> {
inner: Option<Box<S>>,
}
impl<S: Zeroize + ?Sized> Secret<S> {
pub fn new(boxed: Box<S>) -> Self {
Self { inner: Some(boxed) }
}
pub fn with<R>(&self, f: impl FnOnce(&S) -> R) -> Result<R, SecretError> {
match self.inner.as_ref() {
Some(inner) => Ok(f(inner.as_ref())),
None => Err(SecretError::Unavailable),
}
}
}
impl<S: Zeroize + ?Sized> Zeroize for Secret<S> {
fn zeroize(&mut self) {
if let Some(inner) = self.inner.as_mut() {
inner.as_mut().zeroize();
}
}
}
impl<S: Zeroize + ?Sized> Drop for Secret<S> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<S: Zeroize + ?Sized> ZeroizeOnDrop for Secret<S> {}
impl<S: Zeroize + ?Sized> fmt::Debug for Secret<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Secret<{}>([REDACTED])", any::type_name::<S>())
}
}
impl<S> From<S> for Secret<S>
where
S: Zeroize + Encode + for<'a> Decode<'a>,
{
fn from(src: S) -> Self {
Secret { inner: Some(Box::new(src)) }
}
}
impl<S: Zeroize + ?Sized> From<Box<S>> for Secret<S> {
fn from(b: Box<S>) -> Self {
Secret::new(b)
}
}
impl<S> FixedTag for Secret<S>
where
S: Zeroize + FixedTag + ?Sized,
{
const TAG: der::Tag = S::TAG;
}
pub type SecretSlice<T> = Secret<[T]>;
impl<T> From<Vec<T>> for SecretSlice<T>
where
T: Zeroize,
[T]: Zeroize,
{
fn from(v: Vec<T>) -> Self {
Secret::from(v.into_boxed_slice())
}
}
pub type SecretString = Secret<str>;
impl From<String> for SecretString {
fn from(s: String) -> Self {
Secret::from(s.into_boxed_str())
}
}
impl From<&str> for SecretString {
fn from(s: &str) -> Self {
Secret::from(String::from(s))
}
}
impl FromStr for SecretString {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(SecretString::from(s))
}
}
pub trait ToInsecure {
type Raw;
fn to_insecure(self) -> Result<Self::Raw, SecretError>;
}
impl<S: Zeroize> ToInsecure for Secret<S> {
type Raw = S;
fn to_insecure(self) -> Result<S, SecretError> {
let mut this = self;
match this.inner.take() {
Some(inner_box) => Ok(*inner_box),
None => Err(SecretError::Unavailable),
}
}
}
impl<T> ToInsecure for Secret<[T]>
where
T: Zeroize,
[T]: Zeroize,
{
type Raw = Box<[T]>;
fn to_insecure(self) -> Result<Box<[T]>, SecretError> {
let mut this = self;
match this.inner.take() {
Some(inner) => Ok(inner),
None => Err(SecretError::Unavailable),
}
}
}
impl ToInsecure for Secret<str> {
type Raw = Box<str>;
fn to_insecure(self) -> Result<Box<str>, SecretError> {
let mut this = self;
match this.inner.take() {
Some(inner) => Ok(inner),
None => Err(SecretError::Unavailable),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_secret_string_from_str() -> Result<(), Box<dyn std::error::Error>> {
let s = SecretString::from_str("test")?;
assert_eq!(s.to_insecure()?, "test".into());
Ok(())
}
#[test]
fn test_to_insecure_sized() -> Result<(), Box<dyn std::error::Error>> {
let s: Secret<[u8; 2]> = Secret::from([1u8, 2u8]);
let raw = s.to_insecure()?;
assert_eq!(raw, [1, 2]);
Ok(())
}
#[test]
fn test_to_insecure_dsts() -> Result<(), Box<dyn std::error::Error>> {
let s: SecretString = SecretString::from("abc");
let raw: Box<str> = s.to_insecure()?;
assert_eq!(&*raw, "abc");
let s2: SecretSlice<u8> = Vec::from([9u8, 8u8, 7u8]).into();
let raw2: Box<[u8]> = s2.to_insecure()?;
assert_eq!(&*raw2, &[9, 8, 7]);
Ok(())
}
#[test]
fn test_with_immutable_access() -> Result<(), Box<dyn std::error::Error>> {
let s: SecretString = SecretString::from("abcdef");
let len = s.with(|inner| inner.len())?;
assert_eq!(len, 6);
let raw = s.to_insecure()?;
assert_eq!(&*raw, "abcdef");
Ok(())
}
}