Skip to main content

pito_sync/
secret.rs

1use std::fmt;
2
3pub struct Secret(Vec<u8>);
4
5impl Secret {
6    pub fn new(value: impl Into<String>) -> Self {
7        Secret(value.into().into_bytes())
8    }
9
10    pub fn expose(&self) -> &str {
11        std::str::from_utf8(&self.0).unwrap_or_default()
12    }
13
14    fn wipe(&mut self) {
15        for byte in self.0.iter_mut() {
16            *byte = 0;
17        }
18        for slot in self.0.spare_capacity_mut() {
19            slot.write(0);
20        }
21        std::hint::black_box(&mut self.0);
22    }
23}
24
25impl fmt::Debug for Secret {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        f.write_str("Secret(hidden)")
28    }
29}
30
31impl Drop for Secret {
32    fn drop(&mut self) {
33        self.wipe();
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn a_wiped_secret_is_all_zeroes_up_to_its_capacity() {
43        let mut value = String::with_capacity(64);
44        value.push_str("s3cr3t-value-0042");
45        let mut secret = Secret::new(value);
46        assert_eq!(secret.expose(), "s3cr3t-value-0042");
47        let capacity = secret.0.capacity();
48        secret.wipe();
49        assert_eq!(secret.0.len(), 17);
50        assert_eq!(secret.0.capacity(), capacity);
51        assert!(secret.0.iter().all(|&b| b == 0));
52        assert!(!secret.expose().contains("s3cr3t"));
53    }
54}