1#![no_std]
2#![deny(unsafe_code)]
3#![deny(unsafe_op_in_unsafe_fn)]
4
5use arrayvec::{ArrayVec, CapacityError};
12use core::fmt;
13use sanitization::SecureSanitize;
14
15#[cfg(test)]
16extern crate std;
17
18mod backing_wipe {
19 use arrayvec::ArrayVec;
20
21 pub(super) fn wipe_spare<T, const CAP: usize>(inner: &mut ArrayVec<T, CAP>) {
22 let spare = inner.spare_capacity_mut();
23 sanitization::wipe::maybe_uninit(spare);
24 }
25
26 #[cfg(test)]
27 #[allow(unsafe_code)]
28 pub(super) unsafe fn spare_is_zero_after_wipe<T, const CAP: usize>(
29 inner: &mut ArrayVec<T, CAP>,
30 ) -> bool {
31 let spare = inner.spare_capacity_mut();
32 let byte_len = core::mem::size_of_val(spare);
33 if byte_len == 0 {
34 return true;
35 }
36
37 let bytes = unsafe { core::slice::from_raw_parts(spare.as_ptr().cast::<u8>(), byte_len) };
41 bytes.iter().all(|byte| *byte == 0)
42 }
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub struct SanitizedCapacityError;
49
50impl fmt::Display for SanitizedCapacityError {
51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52 formatter.write_str("secret array vector is full; rejected element was sanitized")
53 }
54}
55
56impl core::error::Error for SanitizedCapacityError {}
57
58pub struct SecretArrayVec<T: SecureSanitize, const CAP: usize> {
65 inner: ArrayVec<T, CAP>,
66}
67
68impl<T: SecureSanitize, const CAP: usize> SecretArrayVec<T, CAP> {
69 #[must_use]
71 #[inline]
72 pub const fn new() -> Self {
73 Self {
74 inner: ArrayVec::new_const(),
75 }
76 }
77
78 #[must_use]
83 #[inline]
84 pub fn from_arrayvec(inner: ArrayVec<T, CAP>) -> Self {
85 let mut secret = Self { inner };
86 secret.wipe_spare();
87 secret
88 }
89
90 #[must_use]
92 #[inline]
93 pub fn len(&self) -> usize {
94 self.inner.len()
95 }
96
97 #[must_use]
99 #[inline]
100 pub const fn capacity(&self) -> usize {
101 CAP
102 }
103
104 #[must_use]
106 #[inline]
107 pub fn is_empty(&self) -> bool {
108 self.inner.is_empty()
109 }
110
111 #[inline]
119 pub fn push(&mut self, value: T) -> Result<(), CapacityError<T>> {
120 self.inner.try_push(value)
121 }
122
123 #[inline]
128 pub fn push_or_sanitize(&mut self, value: T) -> Result<(), SanitizedCapacityError> {
129 match self.inner.try_push(value) {
130 Ok(()) => Ok(()),
131 Err(error) => {
132 let mut rejected = error.element();
133 rejected.secure_sanitize();
134 Err(SanitizedCapacityError)
135 }
136 }
137 }
138
139 #[inline]
145 pub fn pop(&mut self) -> Option<T> {
146 let value = self.inner.pop();
147 self.wipe_spare();
148 value
149 }
150
151 #[inline]
157 pub fn truncate(&mut self, new_len: usize) {
158 let len = self.inner.len();
159 if new_len < len {
160 self.inner.as_mut_slice()[new_len..].secure_sanitize();
161 self.inner.truncate(new_len);
162 }
163 self.wipe_spare();
164 }
165
166 #[must_use]
168 #[inline]
169 pub fn as_slice(&self) -> &[T] {
170 self.inner.as_slice()
171 }
172
173 #[must_use]
175 #[inline]
176 pub fn as_mut_slice(&mut self) -> &mut [T] {
177 self.inner.as_mut_slice()
178 }
179
180 #[inline]
182 pub fn with_secret<R>(&self, inspect: impl FnOnce(&[T]) -> R) -> R {
183 inspect(self.as_slice())
184 }
185
186 #[inline]
188 pub fn with_secret_mut<R>(&mut self, edit: impl FnOnce(&mut [T]) -> R) -> R {
189 edit(self.as_mut_slice())
190 }
191
192 #[inline]
194 pub fn clear_secret(&mut self) {
195 self.inner.as_mut_slice().secure_sanitize();
196 self.inner.clear();
197 self.wipe_spare();
198 }
199
200 #[inline]
202 pub fn into_cleared(mut self) {
203 self.clear_secret();
204 }
205
206 #[inline]
207 fn wipe_spare(&mut self) {
208 backing_wipe::wipe_spare(&mut self.inner);
209 }
210}
211
212impl<T: SecureSanitize, const CAP: usize> Default for SecretArrayVec<T, CAP> {
213 #[inline]
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219impl<T: SecureSanitize, const CAP: usize> SecureSanitize for SecretArrayVec<T, CAP> {
220 #[inline]
221 fn secure_sanitize(&mut self) {
222 self.clear_secret();
223 }
224}
225
226impl<T: SecureSanitize, const CAP: usize> Drop for SecretArrayVec<T, CAP> {
227 #[inline]
228 fn drop(&mut self) {
229 self.clear_secret();
230 }
231}
232
233impl<T: SecureSanitize, const CAP: usize> fmt::Debug for SecretArrayVec<T, CAP> {
234 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
235 formatter
236 .debug_struct("SecretArrayVec")
237 .field("len", &self.len())
238 .field("capacity", &CAP)
239 .field("contents", &"<redacted>")
240 .finish()
241 }
242}
243
244#[cfg(test)]
245#[allow(unsafe_code)]
246mod tests {
247 use super::*;
248 use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
249 use sanitization::SecretBytes;
250 use std::sync::Arc;
251
252 fn assert_backing_is_zero<T: SecureSanitize, const CAP: usize>(
253 secrets: &mut SecretArrayVec<T, CAP>,
254 ) {
255 assert!(unsafe { backing_wipe::spare_is_zero_after_wipe(&mut secrets.inner) });
259 }
260
261 #[test]
262 fn empty_arrayvec_wipes_never_initialized_spare() {
263 let mut secrets = SecretArrayVec::<u8, 1>::new();
264
265 secrets.clear_secret();
266
267 assert_backing_is_zero(&mut secrets);
268 }
269
270 #[test]
271 fn arrayvec_wrapper_clears_live_elements() {
272 let mut secrets = SecretArrayVec::<SecretBytes<4>, 2>::new();
273
274 secrets.push(SecretBytes::from_array([1, 2, 3, 4])).unwrap();
275 secrets.push(SecretBytes::from_array([5, 6, 7, 8])).unwrap();
276
277 assert_eq!(secrets.len(), 2);
278 assert!(secrets.with_secret(|items| items[0].constant_time_eq(&[1, 2, 3, 4])));
279
280 secrets.clear_secret();
281
282 assert!(secrets.is_empty());
283 assert_backing_is_zero(&mut secrets);
284 }
285
286 #[test]
287 fn arrayvec_wrapper_debug_is_redacted() {
288 let mut secrets = SecretArrayVec::<SecretBytes<4>, 2>::new();
289 secrets.push(SecretBytes::from_array([1, 2, 3, 4])).unwrap();
290
291 let rendered = std::format!("{secrets:?}");
292
293 assert!(rendered.contains("redacted"));
294 assert!(!rendered.contains("1, 2, 3, 4"));
295 }
296
297 #[test]
298 fn arrayvec_wrapper_push_returns_original_rejected_element() {
299 let mut secrets = SecretArrayVec::<[u8; 4], 0>::new();
300
301 let error = secrets.push([1, 2, 3, 4]).unwrap_err();
302
303 assert_eq!(error.element(), [1, 2, 3, 4]);
304 }
305
306 #[test]
307 fn arrayvec_wrapper_can_sanitize_rejected_elements() {
308 struct Probe(Arc<AtomicBool>);
309
310 impl SecureSanitize for Probe {
311 fn secure_sanitize(&mut self) {
312 self.0.store(true, Ordering::Release);
313 }
314 }
315
316 let sanitized = Arc::new(AtomicBool::new(false));
317 let mut secrets = SecretArrayVec::<Probe, 0>::new();
318
319 assert_eq!(
320 secrets.push_or_sanitize(Probe(Arc::clone(&sanitized))),
321 Err(SanitizedCapacityError)
322 );
323 assert!(sanitized.load(Ordering::Acquire));
324 }
325
326 #[test]
327 fn arrayvec_wrapper_pop_clears_the_historical_slot() {
328 let mut secrets = SecretArrayVec::<[u8; 4], 2>::new();
329 secrets.push([0xA5; 4]).unwrap();
330
331 let mut removed = secrets.pop().unwrap();
332
333 assert_eq!(removed, [0xA5; 4]);
334 assert!(secrets.is_empty());
335 assert_backing_is_zero(&mut secrets);
336 removed.secure_sanitize();
337 }
338
339 #[test]
340 fn arrayvec_wrapper_truncate_sanitizes_before_drop_and_wipes_spare() {
341 struct Probe {
342 bytes: [u8; 4],
343 sanitized: Arc<AtomicBool>,
344 dropped_zeroed: Arc<AtomicBool>,
345 }
346
347 impl SecureSanitize for Probe {
348 fn secure_sanitize(&mut self) {
349 self.bytes.secure_sanitize();
350 self.sanitized.store(true, Ordering::Release);
351 }
352 }
353
354 impl Drop for Probe {
355 fn drop(&mut self) {
356 self.dropped_zeroed
357 .store(self.bytes == [0; 4], Ordering::Release);
358 }
359 }
360
361 let sanitized = Arc::new(AtomicBool::new(false));
362 let dropped_zeroed = Arc::new(AtomicBool::new(false));
363 let mut secrets = SecretArrayVec::<Probe, 2>::new();
364 secrets
365 .push(Probe {
366 bytes: [9; 4],
367 sanitized: Arc::clone(&sanitized),
368 dropped_zeroed: Arc::clone(&dropped_zeroed),
369 })
370 .unwrap();
371
372 secrets.truncate(0);
373
374 assert!(sanitized.load(Ordering::Acquire));
375 assert!(dropped_zeroed.load(Ordering::Acquire));
376 assert_backing_is_zero(&mut secrets);
377 }
378
379 #[test]
380 fn arrayvec_wrapper_clears_historical_spare_when_wrapping() {
381 let mut inner = ArrayVec::<[u8; 4], 2>::new();
382 inner.push([0xC3; 4]);
383 let mut removed = inner.pop().unwrap();
384
385 let mut secrets = SecretArrayVec::from_arrayvec(inner);
386
387 assert_backing_is_zero(&mut secrets);
388 removed.secure_sanitize();
389 }
390
391 #[test]
392 fn arrayvec_wrapper_clears_complete_backing_after_reuse() {
393 let mut secrets = SecretArrayVec::<[u8; 4], 3>::new();
394 secrets.push([1; 4]).unwrap();
395 secrets.push([2; 4]).unwrap();
396 let mut removed = secrets.pop().unwrap();
397 secrets.push([3; 4]).unwrap();
398
399 secrets.clear_secret();
400
401 assert!(secrets.is_empty());
402 assert_backing_is_zero(&mut secrets);
403 removed.secure_sanitize();
404 }
405
406 #[test]
407 fn arrayvec_wrapper_handles_zero_sized_drop_types() {
408 struct ZeroSized;
409
410 static SANITIZED: AtomicUsize = AtomicUsize::new(0);
411 static DROPPED: AtomicUsize = AtomicUsize::new(0);
412
413 impl SecureSanitize for ZeroSized {
414 fn secure_sanitize(&mut self) {
415 SANITIZED.fetch_add(1, Ordering::SeqCst);
416 }
417 }
418
419 impl Drop for ZeroSized {
420 fn drop(&mut self) {
421 DROPPED.fetch_add(1, Ordering::SeqCst);
422 }
423 }
424
425 SANITIZED.store(0, Ordering::SeqCst);
426 DROPPED.store(0, Ordering::SeqCst);
427 let mut secrets = SecretArrayVec::<ZeroSized, 4>::new();
428 secrets.push(ZeroSized).unwrap();
429 secrets.push(ZeroSized).unwrap();
430
431 secrets.clear_secret();
432
433 assert_eq!(SANITIZED.load(Ordering::SeqCst), 2);
434 assert_eq!(DROPPED.load(Ordering::SeqCst), 2);
435 assert_backing_is_zero(&mut secrets);
436 }
437}