Skip to main content

ic_core/
zeroize.rs

1//! Secret erasure.
2//!
3//! [`Zeroizing`] wipes its contents on drop using volatile writes plus a
4//! compiler fence, so the erasure survives optimization. This is the mechanism
5//! behind the FIPS 140-3 zeroisation requirement for CSPs held in memory.
6
7use core::sync::atomic::{compiler_fence, Ordering};
8
9/// Types whose in-memory representation can be securely erased.
10pub trait Zeroize {
11    /// Overwrite `self` with zeroes using non-elidable volatile writes.
12    fn zeroize(&mut self);
13}
14
15impl Zeroize for [u8] {
16    fn zeroize(&mut self) {
17        for byte in self.iter_mut() {
18            // SAFETY: `byte` is a valid, aligned, uniquely-borrowed `u8`.
19            unsafe { core::ptr::write_volatile(byte, 0) };
20        }
21        compiler_fence(Ordering::SeqCst);
22    }
23}
24
25impl Zeroize for [u32] {
26    fn zeroize(&mut self) {
27        for w in self.iter_mut() {
28            // SAFETY: `w` is a valid, aligned, uniquely-borrowed `u32`.
29            unsafe { core::ptr::write_volatile(w, 0) };
30        }
31        compiler_fence(Ordering::SeqCst);
32    }
33}
34
35impl Zeroize for [u64] {
36    fn zeroize(&mut self) {
37        for w in self.iter_mut() {
38            // SAFETY: `w` is a valid, aligned, uniquely-borrowed `u64`.
39            unsafe { core::ptr::write_volatile(w, 0) };
40        }
41        compiler_fence(Ordering::SeqCst);
42    }
43}
44
45impl<const N: usize> Zeroize for [u8; N] {
46    fn zeroize(&mut self) {
47        self.as_mut_slice().zeroize();
48    }
49}
50
51impl<const N: usize> Zeroize for [u32; N] {
52    fn zeroize(&mut self) {
53        self.as_mut_slice().zeroize();
54    }
55}
56
57impl<const N: usize> Zeroize for [u64; N] {
58    fn zeroize(&mut self) {
59        self.as_mut_slice().zeroize();
60    }
61}
62
63/// A wrapper that zeroizes its contents when dropped.
64///
65/// ```
66/// use ic_core::Zeroizing;
67/// let mut key = Zeroizing::new([0u8; 32]);
68/// key[0] = 0x42;
69/// assert_eq!(key[0], 0x42);
70/// // `key` is wiped when it leaves scope.
71/// ```
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct Zeroizing<T: Zeroize>(T);
74
75impl<T: Zeroize> Zeroizing<T> {
76    /// Wrap a value so it is erased on drop.
77    pub const fn new(value: T) -> Self {
78        Self(value)
79    }
80
81    /// Borrow the protected value.
82    pub fn get(&self) -> &T {
83        &self.0
84    }
85
86    /// Mutably borrow the protected value.
87    pub fn get_mut(&mut self) -> &mut T {
88        &mut self.0
89    }
90}
91
92impl<T: Zeroize> core::ops::Deref for Zeroizing<T> {
93    type Target = T;
94    fn deref(&self) -> &T {
95        &self.0
96    }
97}
98
99impl<T: Zeroize> core::ops::DerefMut for Zeroizing<T> {
100    fn deref_mut(&mut self) -> &mut T {
101        &mut self.0
102    }
103}
104
105impl<T: Zeroize> Drop for Zeroizing<T> {
106    fn drop(&mut self) {
107        self.0.zeroize();
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn slices_are_wiped() {
117        let mut buf = [1u8, 2, 3, 4];
118        buf.zeroize();
119        assert_eq!(buf, [0, 0, 0, 0]);
120    }
121
122    #[test]
123    fn zeroizing_derefs() {
124        let mut z = Zeroizing::new([7u8; 8]);
125        assert_eq!(z[0], 7);
126        z[0] = 9;
127        assert_eq!(z.get()[0], 9);
128    }
129
130    #[test]
131    fn word_slices_are_wiped() {
132        let mut w = [0xDEAD_BEEFu32; 4];
133        w.zeroize();
134        assert_eq!(w, [0u32; 4]);
135        let mut q = [0xDEAD_BEEF_CAFE_F00Du64; 2];
136        q.zeroize();
137        assert_eq!(q, [0u64; 2]);
138    }
139}