Skip to main content

sanitization_arrayvec/
lib.rs

1#![no_std]
2#![deny(unsafe_code)]
3
4//! `arrayvec` integration wrappers for `sanitization`.
5//!
6//! This crate deliberately uses wrapper types instead of trait impls for
7//! external types. Rust's orphan rules prevent implementing
8//! `sanitization::SecureSanitize` directly for `arrayvec::ArrayVec` here.
9
10use arrayvec::{ArrayVec, CapacityError};
11use core::fmt;
12use sanitization::SecureSanitize;
13
14#[cfg(test)]
15extern crate std;
16
17/// Error returned when [`SecretArrayVec::push_or_sanitize`] rejects and clears
18/// an element because the vector is full.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub struct SanitizedCapacityError;
21
22impl fmt::Display for SanitizedCapacityError {
23    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24        formatter.write_str("secret array vector is full; rejected element was sanitized")
25    }
26}
27
28impl core::error::Error for SanitizedCapacityError {}
29
30/// Clear-on-drop wrapper around [`ArrayVec`].
31///
32/// Live elements are sanitized before the vector is cleared. Spare uninitialized
33/// storage is not treated as secret material because it has never held a `T`.
34pub struct SecretArrayVec<T: SecureSanitize, const CAP: usize> {
35    inner: ArrayVec<T, CAP>,
36}
37
38impl<T: SecureSanitize, const CAP: usize> SecretArrayVec<T, CAP> {
39    /// Create an empty secret array vector.
40    #[must_use]
41    #[inline]
42    pub const fn new() -> Self {
43        Self {
44            inner: ArrayVec::new_const(),
45        }
46    }
47
48    /// Wrap an existing [`ArrayVec`].
49    #[must_use]
50    #[inline]
51    pub const fn from_arrayvec(inner: ArrayVec<T, CAP>) -> Self {
52        Self { inner }
53    }
54
55    /// Number of initialized elements.
56    #[must_use]
57    #[inline]
58    pub fn len(&self) -> usize {
59        self.inner.len()
60    }
61
62    /// Maximum number of elements.
63    #[must_use]
64    #[inline]
65    pub const fn capacity(&self) -> usize {
66        CAP
67    }
68
69    /// Returns true when there are no initialized elements.
70    #[must_use]
71    #[inline]
72    pub fn is_empty(&self) -> bool {
73        self.inner.is_empty()
74    }
75
76    /// Push one sanitizable element.
77    ///
78    /// If the vector is full, [`CapacityError`] returns the original element
79    /// unchanged, matching `arrayvec` semantics. Callers remain responsible for
80    /// sanitizing or securely reusing that rejected value. Use
81    /// [`SecretArrayVec::push_or_sanitize`] when rejection must consume and
82    /// clear the element instead.
83    #[inline]
84    pub fn push(&mut self, value: T) -> Result<(), CapacityError<T>> {
85        self.inner.try_push(value)
86    }
87
88    /// Push an element, consuming and sanitizing it if the vector is full.
89    ///
90    /// The error intentionally carries no `T`, preventing callers from
91    /// mistaking a sanitized value for the original secret.
92    #[inline]
93    pub fn push_or_sanitize(&mut self, value: T) -> Result<(), SanitizedCapacityError> {
94        match self.inner.try_push(value) {
95            Ok(()) => Ok(()),
96            Err(error) => {
97                let mut rejected = error.element();
98                rejected.secure_sanitize();
99                Err(SanitizedCapacityError)
100            }
101        }
102    }
103
104    /// Borrow initialized elements.
105    #[must_use]
106    #[inline]
107    pub fn as_slice(&self) -> &[T] {
108        self.inner.as_slice()
109    }
110
111    /// Mutably borrow initialized elements.
112    #[must_use]
113    #[inline]
114    pub fn as_mut_slice(&mut self) -> &mut [T] {
115        self.inner.as_mut_slice()
116    }
117
118    /// Run a closure with read-only access to initialized elements.
119    #[inline]
120    pub fn with_secret<R>(&self, inspect: impl FnOnce(&[T]) -> R) -> R {
121        inspect(self.as_slice())
122    }
123
124    /// Run a closure with mutable access to initialized elements.
125    #[inline]
126    pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [T]) -> R) -> R {
127        edit(self.as_mut_slice())
128    }
129
130    /// Sanitize all live elements and clear the vector.
131    #[inline]
132    pub fn clear_secret(&mut self) {
133        self.inner.as_mut_slice().secure_sanitize();
134        self.inner.clear();
135    }
136
137    /// Consume after first sanitizing all live elements.
138    #[inline]
139    pub fn into_cleared(mut self) {
140        self.clear_secret();
141    }
142}
143
144impl<T: SecureSanitize, const CAP: usize> Default for SecretArrayVec<T, CAP> {
145    #[inline]
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl<T: SecureSanitize, const CAP: usize> SecureSanitize for SecretArrayVec<T, CAP> {
152    #[inline]
153    fn secure_sanitize(&mut self) {
154        self.clear_secret();
155    }
156}
157
158impl<T: SecureSanitize, const CAP: usize> Drop for SecretArrayVec<T, CAP> {
159    #[inline]
160    fn drop(&mut self) {
161        self.clear_secret();
162    }
163}
164
165impl<T: SecureSanitize, const CAP: usize> fmt::Debug for SecretArrayVec<T, CAP> {
166    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
167        formatter
168            .debug_struct("SecretArrayVec")
169            .field("len", &self.len())
170            .field("capacity", &CAP)
171            .field("contents", &"<redacted>")
172            .finish()
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use sanitization::SecretBytes;
180
181    #[test]
182    fn arrayvec_wrapper_clears_live_elements() {
183        let mut secrets = SecretArrayVec::<SecretBytes<4>, 2>::new();
184
185        secrets.push(SecretBytes::from_array([1, 2, 3, 4])).unwrap();
186        secrets.push(SecretBytes::from_array([5, 6, 7, 8])).unwrap();
187
188        assert_eq!(secrets.len(), 2);
189        assert!(secrets.with_secret(|items| items[0].constant_time_eq(&[1, 2, 3, 4])));
190
191        secrets.clear_secret();
192
193        assert!(secrets.is_empty());
194    }
195
196    #[test]
197    fn arrayvec_wrapper_debug_is_redacted() {
198        let mut secrets = SecretArrayVec::<SecretBytes<4>, 2>::new();
199        secrets.push(SecretBytes::from_array([1, 2, 3, 4])).unwrap();
200
201        let rendered = std::format!("{secrets:?}");
202
203        assert!(rendered.contains("redacted"));
204        assert!(!rendered.contains("1, 2, 3, 4"));
205    }
206
207    #[test]
208    fn arrayvec_wrapper_push_returns_original_rejected_element() {
209        let mut secrets = SecretArrayVec::<[u8; 4], 0>::new();
210
211        let error = secrets.push([1, 2, 3, 4]).unwrap_err();
212
213        assert_eq!(error.element(), [1, 2, 3, 4]);
214    }
215
216    #[test]
217    fn arrayvec_wrapper_can_sanitize_rejected_elements() {
218        use core::sync::atomic::{AtomicBool, Ordering};
219        use std::sync::Arc;
220
221        struct Probe(Arc<AtomicBool>);
222
223        impl SecureSanitize for Probe {
224            fn secure_sanitize(&mut self) {
225                self.0.store(true, Ordering::Release);
226            }
227        }
228
229        let sanitized = Arc::new(AtomicBool::new(false));
230        let mut secrets = SecretArrayVec::<Probe, 0>::new();
231
232        assert_eq!(
233            secrets.push_or_sanitize(Probe(Arc::clone(&sanitized))),
234            Err(SanitizedCapacityError)
235        );
236        assert!(sanitized.load(Ordering::Acquire));
237    }
238}