#![no_std]
#![deny(unsafe_code)]
use arrayvec::{ArrayVec, CapacityError};
use core::fmt;
use sanitization::SecureSanitize;
#[cfg(test)]
extern crate std;
pub struct SecretArrayVec<T: SecureSanitize, const CAP: usize> {
inner: ArrayVec<T, CAP>,
}
impl<T: SecureSanitize, const CAP: usize> SecretArrayVec<T, CAP> {
#[must_use]
#[inline]
pub const fn new() -> Self {
Self {
inner: ArrayVec::new_const(),
}
}
#[must_use]
#[inline]
pub const fn from_arrayvec(inner: ArrayVec<T, CAP>) -> Self {
Self { inner }
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
#[inline]
pub const fn capacity(&self) -> usize {
CAP
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn push(&mut self, value: T) -> Result<(), CapacityError<T>> {
self.inner.try_push(value)
}
#[must_use]
#[inline]
pub fn as_slice(&self) -> &[T] {
self.inner.as_slice()
}
#[must_use]
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T] {
self.inner.as_mut_slice()
}
#[inline]
pub fn with_secret<R>(&self, inspect: impl FnOnce(&[T]) -> R) -> R {
inspect(self.as_slice())
}
#[inline]
pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [T]) -> R) -> R {
edit(self.as_mut_slice())
}
#[inline]
pub fn clear_secret(&mut self) {
self.inner.as_mut_slice().secure_sanitize();
self.inner.clear();
}
#[inline]
pub fn into_cleared(mut self) {
self.clear_secret();
}
}
impl<T: SecureSanitize, const CAP: usize> Default for SecretArrayVec<T, CAP> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T: SecureSanitize, const CAP: usize> SecureSanitize for SecretArrayVec<T, CAP> {
#[inline]
fn secure_sanitize(&mut self) {
self.clear_secret();
}
}
impl<T: SecureSanitize, const CAP: usize> Drop for SecretArrayVec<T, CAP> {
#[inline]
fn drop(&mut self) {
self.clear_secret();
}
}
impl<T: SecureSanitize, const CAP: usize> fmt::Debug for SecretArrayVec<T, CAP> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SecretArrayVec")
.field("len", &self.len())
.field("capacity", &CAP)
.field("contents", &"<redacted>")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use sanitization::SecretBytes;
#[test]
fn arrayvec_wrapper_clears_live_elements() {
let mut secrets = SecretArrayVec::<SecretBytes<4>, 2>::new();
secrets.push(SecretBytes::from_array([1, 2, 3, 4])).unwrap();
secrets.push(SecretBytes::from_array([5, 6, 7, 8])).unwrap();
assert_eq!(secrets.len(), 2);
assert!(secrets.with_secret(|items| items[0].constant_time_eq(&[1, 2, 3, 4])));
secrets.clear_secret();
assert!(secrets.is_empty());
}
#[test]
fn arrayvec_wrapper_debug_is_redacted() {
let mut secrets = SecretArrayVec::<SecretBytes<4>, 2>::new();
secrets.push(SecretBytes::from_array([1, 2, 3, 4])).unwrap();
let rendered = std::format!("{secrets:?}");
assert!(rendered.contains("redacted"));
assert!(!rendered.contains("1, 2, 3, 4"));
}
}