kdk_zeroize/sensitive.rs
1use core::fmt;
2use core::marker::PhantomData;
3use core::sync::atomic;
4
5/// Disciplines yourself. This trait does NOT enforce:
6/// - `Drop` wipes the storage on out-of-scope.
7/// - Redacting `fmt::Debug` impl (never derive).
8/// - No `Clone` / no `Copy`.
9pub trait Sensitive {
10 /// The type whose bytes are the secret material.
11 type Inner: ?Sized;
12
13 /// Explicit-secret-access getter. Mirrors the inherent
14 /// `expose_secret` every sensitive type provides.
15 fn expose_secret(&self) -> &Self::Inner;
16}
17
18/// Read-only sensitive types (computed/derived values like a wrapped
19/// `bip39::Mnemonic`) impl only [`Sensitive`], not this trait —
20/// generic code that requires mutation takes `<S: SensitiveMut>` and
21/// will refuse those at compile time.
22pub trait SensitiveMut: Sensitive {
23 /// Mutable explicit-secret-access getter.
24 fn expose_secret_mut(&mut self) -> &mut Self::Inner;
25}
26
27/// Caller must ensure that after this function returns, **no code
28/// reads `*value` as a `T`**.
29///
30/// # Safety
31///
32/// Zeroing the bytes may produce an invalid `T` (niches, validity invariants
33/// on inner fields, etc.). Typically used inside `Drop`, where Rust's drop
34/// semantics already guarantee no further reads.
35///
36/// WARN: authorship is from [@luisschwab](https://github.com/luisschwab)
37/// while reading a go discord's topic. It's improvised and need auction.
38pub unsafe fn wipe_in_place_mut<T>(value: &mut T) {
39 let ptr = (value as *mut T).cast::<u8>();
40 let len = core::mem::size_of::<T>();
41 for i in 0..len {
42 unsafe {
43 core::ptr::write_volatile(ptr.add(i), 0);
44 }
45 atomic::compiler_fence(atomic::Ordering::SeqCst);
46 }
47}
48
49/// `N` is the byte length and `O` is a chosen zero-sized type that lets the
50/// type system track where the bytes came from (BIP39 seed, dice entropy, AES
51/// key, decrypted KEF payload, etc...).
52///
53/// # Example
54///
55/// ```
56/// use kdk_zeroize::SensitiveBytes;
57///
58/// // A caller-chosen marker type.
59/// pub enum AesKey {}
60///
61/// type AesKey256 = SensitiveBytes<32, AesKey>;
62///
63///
64/// let key = AesKey256::new([0u8; 32]);
65/// ```
66pub struct SensitiveBytes<const N: usize, O> {
67 bytes: [u8; N],
68 _origin: PhantomData<O>,
69}
70
71impl<const N: usize, O> SensitiveBytes<N, O> {
72 /// Wrap a `[u8; N]` as origin-tagged sensitive bytes.
73 pub const fn new(bytes: [u8; N]) -> Self {
74 Self {
75 bytes,
76 _origin: PhantomData,
77 }
78 }
79
80 /// Same bytes as `Sensitive::expose_secret`; the looser `&[u8]`
81 /// return is fine for non-cryptographic reads (e.g. equality
82 /// checks in tests).
83 pub fn as_slice(&self) -> &[u8] {
84 &self.bytes
85 }
86}
87
88impl<const N: usize, O> Drop for SensitiveBytes<N, O> {
89 fn drop(&mut self) {
90 unsafe { wipe_in_place_mut(self) };
91 }
92}
93
94impl<const N: usize, O> fmt::Debug for SensitiveBytes<N, O> {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.write_str("SensitiveBytes(REDACTED)")
97 }
98}
99
100// Mirrors the redacted `Debug` so `{}` is also safe; raw `[u8; N]` has
101// no `Display` impl at all, so this is an additive safety surface.
102impl<const N: usize, O> fmt::Display for SensitiveBytes<N, O> {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str("SensitiveBytes(REDACTED)")
105 }
106}
107
108impl<const N: usize, O> Sensitive for SensitiveBytes<N, O> {
109 type Inner = [u8; N];
110
111 fn expose_secret(&self) -> &[u8; N] {
112 &self.bytes
113 }
114}
115
116impl<const N: usize, O> SensitiveMut for SensitiveBytes<N, O> {
117 fn expose_secret_mut(&mut self) -> &mut [u8; N] {
118 &mut self.bytes
119 }
120}