use crate::{
DecodeError, EncodeError, STANDARD, constant_time_eq_public_len, wipe_bytes, wipe_tail,
};
#[cfg(feature = "alloc")]
use crate::{wipe_vec_all, wipe_vec_spare_capacity};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
pub struct EncodedBuffer<const CAP: usize> {
bytes: [u8; CAP],
len: usize,
}
pub struct ExposedEncodedArray<const CAP: usize> {
bytes: [u8; CAP],
len: usize,
}
impl<const CAP: usize> ExposedEncodedArray<CAP> {
#[must_use]
pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
assert!(len <= CAP, "visible length exceeds array capacity");
Self { bytes, len }
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn capacity(&self) -> usize {
CAP
}
#[must_use = "caller must zeroize the returned array"]
pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
let len = self.len;
self.len = 0;
(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
}
}
impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
fn drop(&mut self) {
wipe_bytes(&mut self.bytes);
self.len = 0;
}
}
impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("ExposedEncodedArray")
.field("bytes", &"<redacted>")
.field("len", &self.len)
.field("capacity", &CAP)
.finish()
}
}
impl<const CAP: usize> EncodedBuffer<CAP> {
#[must_use]
pub const fn new() -> Self {
Self {
bytes: [0u8; CAP],
len: 0,
}
}
pub(crate) fn as_mut_capacity(&mut self) -> &mut [u8] {
&mut self.bytes
}
pub(crate) fn set_filled(&mut self, written: usize) -> Result<(), EncodeError> {
debug_assert!(
written <= CAP,
"encoder wrote past stack-backed buffer capacity"
);
if written > CAP {
self.clear();
return Err(EncodeError::OutputTooSmall {
required: written,
available: CAP,
});
}
self.len = written;
Ok(())
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn is_full(&self) -> bool {
self.len == CAP
}
#[must_use]
pub const fn capacity(&self) -> usize {
CAP
}
#[must_use]
pub const fn remaining_capacity(&self) -> usize {
CAP - self.len
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
core::str::from_utf8(self.as_bytes())
}
#[must_use]
pub fn as_str(&self) -> &str {
match self.as_utf8() {
Ok(output) => output,
Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
}
}
#[doc(alias = "constant_time_eq")]
#[must_use]
pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
constant_time_eq_public_len(self.as_bytes(), other)
}
#[must_use]
pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
let len = self.len;
self.len = 0;
ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
}
pub fn clear(&mut self) {
wipe_bytes(&mut self.bytes);
self.len = 0;
}
pub fn clear_tail(&mut self) {
wipe_tail(&mut self.bytes, self.len);
}
}
impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
fn clone(&self) -> Self {
let mut output = Self::new();
output.bytes[..self.len].copy_from_slice(self.as_bytes());
output.len = self.len;
output
}
}
impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("EncodedBuffer")
.field("bytes", &"<redacted>")
.field("len", &self.len)
.field("capacity", &CAP)
.finish()
}
}
impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(self.as_str())
}
}
impl<const CAP: usize> Default for EncodedBuffer<CAP> {
fn default() -> Self {
Self::new()
}
}
impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
fn drop(&mut self) {
self.clear();
}
}
impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
type Error = EncodeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
STANDARD.encode_buffer(input)
}
}
impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
type Error = EncodeError;
fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
Self::try_from(&input[..])
}
}
impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
type Error = EncodeError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
Self::try_from(input.as_bytes())
}
}
pub struct DecodedBuffer<const CAP: usize> {
bytes: [u8; CAP],
len: usize,
}
pub struct ExposedDecodedArray<const CAP: usize> {
bytes: [u8; CAP],
len: usize,
}
impl<const CAP: usize> ExposedDecodedArray<CAP> {
#[must_use]
pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
assert!(len <= CAP, "visible length exceeds array capacity");
Self { bytes, len }
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn capacity(&self) -> usize {
CAP
}
#[must_use = "caller must zeroize the returned array"]
pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
let len = self.len;
self.len = 0;
(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
}
}
impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
fn drop(&mut self) {
wipe_bytes(&mut self.bytes);
self.len = 0;
}
}
impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("ExposedDecodedArray")
.field("bytes", &"<redacted>")
.field("len", &self.len)
.field("capacity", &CAP)
.finish()
}
}
impl<const CAP: usize> DecodedBuffer<CAP> {
#[must_use]
pub const fn new() -> Self {
Self {
bytes: [0u8; CAP],
len: 0,
}
}
pub(crate) fn as_mut_capacity(&mut self) -> &mut [u8] {
&mut self.bytes
}
pub(crate) fn set_filled(&mut self, written: usize) -> Result<(), DecodeError> {
debug_assert!(
written <= CAP,
"decoder wrote past stack-backed buffer capacity"
);
if written > CAP {
self.clear();
return Err(DecodeError::OutputTooSmall {
required: written,
available: CAP,
});
}
self.len = written;
Ok(())
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn is_full(&self) -> bool {
self.len == CAP
}
#[must_use]
pub const fn capacity(&self) -> usize {
CAP
}
#[must_use]
pub const fn remaining_capacity(&self) -> usize {
CAP - self.len
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
core::str::from_utf8(self.as_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.as_bytes(), other)
}
#[must_use]
pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
let len = self.len;
self.len = 0;
ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
}
pub fn clear(&mut self) {
wipe_bytes(&mut self.bytes);
self.len = 0;
}
pub fn clear_tail(&mut self) {
wipe_tail(&mut self.bytes, self.len);
}
}
impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
fn clone(&self) -> Self {
let mut output = Self::new();
output.bytes[..self.len].copy_from_slice(self.as_bytes());
output.len = self.len;
output
}
}
impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("DecodedBuffer")
.field("bytes", &"<redacted>")
.field("len", &self.len)
.field("capacity", &CAP)
.finish()
}
}
impl<const CAP: usize> Default for DecodedBuffer<CAP> {
fn default() -> Self {
Self::new()
}
}
impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
fn drop(&mut self) {
self.clear();
}
}
impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
type Error = DecodeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
STANDARD.decode_buffer(input)
}
}
impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
type Error = DecodeError;
fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
Self::try_from(&input[..])
}
}
impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
type Error = DecodeError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
Self::try_from(input.as_bytes())
}
}
impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
type Err = DecodeError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::try_from(input)
}
}
#[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) -> alloc::string::String {
wipe_vec_spare_capacity(&mut self.bytes);
let bytes = core::mem::take(&mut self.bytes);
core::mem::forget(self);
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 = string_from_validated_secret_bytes(bytes);
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);
Ok(ExposedSecretString::from_string(
guard.into_validated_secret_string(),
))
}
#[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")]
impl From<alloc::vec::Vec<u8>> for SecretBuffer {
fn from(bytes: alloc::vec::Vec<u8>) -> Self {
Self::from_vec(bytes)
}
}
#[cfg(feature = "alloc")]
impl From<alloc::string::String> for SecretBuffer {
fn from(text: alloc::string::String) -> Self {
Self::from_vec(text.into_bytes())
}
}
#[cfg(feature = "alloc")]
impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
fn from(buffer: EncodedBuffer<CAP>) -> Self {
Self::from_slice(buffer.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
fn from(buffer: DecodedBuffer<CAP>) -> Self {
Self::from_slice(buffer.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl TryFrom<&[u8]> for SecretBuffer {
type Error = DecodeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
STANDARD.decode_secret(input)
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
type Error = DecodeError;
fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
Self::try_from(&input[..])
}
}
#[cfg(feature = "alloc")]
impl TryFrom<&str> for SecretBuffer {
type Error = DecodeError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
Self::try_from(input.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl core::str::FromStr for SecretBuffer {
type Err = DecodeError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::try_from(input)
}
}
#[cfg(feature = "alloc")]
fn string_from_validated_secret_bytes(bytes: Vec<u8>) -> String {
match String::from_utf8(bytes) {
Ok(string) => string,
Err(error) => {
let mut bytes = error.into_bytes();
wipe_vec_all(&mut bytes);
String::new()
}
}
}