use crate::{constant_time_eq_public_len, wipe_vec_all, wipe_vec_spare_capacity};
use alloc::{string::String, vec::Vec};
#[cfg(feature = "alloc")]
pub struct SecretBuffer {
bytes: alloc::vec::Vec<u8>,
}
#[cfg(feature = "alloc")]
pub struct ExposedSecretVec {
bytes: alloc::vec::Vec<u8>,
}
#[cfg(feature = "alloc")]
impl ExposedSecretVec {
#[must_use]
pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
wipe_vec_spare_capacity(&mut bytes);
Self { bytes }
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn expose_secret(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn expose_secret_mut(&mut self) -> &mut [u8] {
&mut self.bytes
}
#[must_use = "caller must zeroize the returned Vec"]
pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
core::mem::take(&mut self.bytes)
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Debug for ExposedSecretVec {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("ExposedSecretVec")
.field("bytes", &"<redacted>")
.field("len", &self.len())
.finish()
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Display for ExposedSecretVec {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("<redacted>")
}
}
#[cfg(feature = "alloc")]
impl Drop for ExposedSecretVec {
fn drop(&mut self) {
wipe_vec_all(&mut self.bytes);
}
}
#[cfg(feature = "alloc")]
struct WipeVecGuard {
bytes: alloc::vec::Vec<u8>,
}
#[cfg(feature = "alloc")]
impl WipeVecGuard {
fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
Self { bytes }
}
fn into_validated_secret_string(
mut self,
) -> Result<alloc::string::String, alloc::vec::Vec<u8>> {
wipe_vec_spare_capacity(&mut self.bytes);
let bytes = core::mem::take(&mut self.bytes);
string_from_validated_secret_bytes(bytes)
}
}
#[cfg(feature = "alloc")]
impl Drop for WipeVecGuard {
fn drop(&mut self) {
wipe_vec_all(&mut self.bytes);
}
}
#[cfg(feature = "alloc")]
impl AsRef<[u8]> for ExposedSecretVec {
fn as_ref(&self) -> &[u8] {
self.expose_secret()
}
}
#[cfg(feature = "alloc")]
impl AsMut<[u8]> for ExposedSecretVec {
fn as_mut(&mut self) -> &mut [u8] {
self.expose_secret_mut()
}
}
#[cfg(feature = "alloc")]
pub struct ExposedSecretString {
text: alloc::string::String,
}
#[cfg(feature = "alloc")]
impl ExposedSecretString {
#[must_use]
pub fn from_string(text: alloc::string::String) -> Self {
let mut bytes = text.into_bytes();
wipe_vec_spare_capacity(&mut bytes);
let text = match string_from_validated_secret_bytes(bytes) {
Ok(text) => text,
Err(mut bytes) => {
wipe_vec_all(&mut bytes);
alloc::string::String::new()
}
};
Self { text }
}
#[must_use]
pub fn len(&self) -> usize {
self.text.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
#[must_use]
pub fn expose_secret(&self) -> &str {
&self.text
}
#[must_use]
pub fn expose_secret_bytes(&self) -> &[u8] {
self.text.as_bytes()
}
#[must_use = "caller must zeroize the returned String"]
pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
core::mem::take(&mut self.text)
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Debug for ExposedSecretString {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("ExposedSecretString")
.field("text", &"<redacted>")
.field("len", &self.len())
.finish()
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Display for ExposedSecretString {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("<redacted>")
}
}
#[cfg(feature = "alloc")]
impl Drop for ExposedSecretString {
fn drop(&mut self) {
let mut bytes = core::mem::take(&mut self.text).into_bytes();
wipe_vec_all(&mut bytes);
}
}
#[cfg(feature = "alloc")]
impl AsRef<str> for ExposedSecretString {
fn as_ref(&self) -> &str {
self.expose_secret()
}
}
#[cfg(feature = "alloc")]
impl SecretBuffer {
#[must_use]
pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
wipe_vec_spare_capacity(&mut bytes);
Self { bytes }
}
#[must_use]
pub fn from_slice(bytes: &[u8]) -> Self {
Self::from_vec(bytes.to_vec())
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn expose_secret(&self) -> &[u8] {
&self.bytes
}
pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
core::str::from_utf8(self.expose_secret())
}
#[must_use]
pub fn expose_secret_mut(&mut self) -> &mut [u8] {
&mut self.bytes
}
#[must_use]
pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
}
#[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
if core::str::from_utf8(self.expose_secret()).is_err() {
return Err(self);
}
let mut exposed = self.into_exposed_vec();
let guard = WipeVecGuard::from_vec(core::mem::take(&mut exposed.bytes));
drop(exposed);
match guard.into_validated_secret_string() {
Ok(text) => Ok(ExposedSecretString::from_string(text)),
Err(bytes) => Err(SecretBuffer::from_vec(bytes)),
}
}
#[doc(alias = "constant_time_eq")]
#[must_use]
pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
constant_time_eq_public_len(self.expose_secret(), other)
}
pub fn clear(&mut self) {
wipe_vec_all(&mut self.bytes);
self.bytes.clear();
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Debug for SecretBuffer {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("SecretBuffer")
.field("bytes", &"<redacted>")
.field("len", &self.len())
.finish()
}
}
#[cfg(feature = "alloc")]
impl core::fmt::Display for SecretBuffer {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("<redacted>")
}
}
#[cfg(feature = "alloc")]
impl Drop for SecretBuffer {
fn drop(&mut self) {
wipe_vec_all(&mut self.bytes);
}
}
#[cfg(feature = "alloc")]
fn string_from_validated_secret_bytes(bytes: Vec<u8>) -> Result<String, Vec<u8>> {
String::from_utf8(bytes).map_err(alloc::string::FromUtf8Error::into_bytes)
}