Skip to main content

base64_ng/v2/secret/
owned.rs

1//! Owned fixed and heap secret storage.
2
3use super::{ExposedSecret, ExposedSecretMut};
4use crate::v2::bounded::BufferLengthError;
5
6/// Non-Clone bounded secret bytes with mandatory full-capacity cleanup.
7pub struct SecretArray<const CAP: usize> {
8    bytes: [u8; CAP],
9    len: usize,
10}
11
12impl<const CAP: usize> SecretArray<CAP> {
13    pub(super) fn from_frame(bytes: [u8; CAP], len: usize) -> Result<Self, BufferLengthError> {
14        Self::from_array(bytes, len)
15    }
16
17    /// Takes ownership, checks the visible prefix, and wipes unused capacity.
18    pub fn from_array(mut bytes: [u8; CAP], len: usize) -> Result<Self, BufferLengthError> {
19        if len > CAP {
20            crate::wipe_bytes(&mut bytes);
21            return Err(BufferLengthError::new(len, CAP));
22        }
23        crate::wipe_tail(&mut bytes, len);
24        Ok(Self { bytes, len })
25    }
26
27    /// Creates an explicit borrowed interoperability view.
28    #[must_use]
29    pub fn expose_secret(&self) -> ExposedSecret<'_> {
30        ExposedSecret::new(&self.bytes[..self.len])
31    }
32
33    /// Creates an explicit mutable interoperability view.
34    #[must_use]
35    pub fn expose_secret_mut(&mut self) -> ExposedSecretMut<'_> {
36        ExposedSecretMut {
37            bytes: &mut self.bytes[..self.len],
38        }
39    }
40
41    /// Deliberately converts this secret into ordinary non-wiping storage.
42    #[must_use = "declassification transfers cleanup responsibility to the caller"]
43    pub fn declassify(mut self) -> DeclassifiedArray<CAP> {
44        let bytes = core::mem::replace(&mut self.bytes, [0u8; CAP]);
45        let len = self.len;
46        self.len = 0;
47        DeclassifiedArray { bytes, len }
48    }
49
50    /// Returns the public initialized length.
51    #[must_use]
52    pub const fn len(&self) -> usize {
53        self.len
54    }
55
56    /// Returns whether the initialized prefix is empty.
57    #[must_use]
58    pub const fn is_empty(&self) -> bool {
59        self.len == 0
60    }
61
62    /// Returns the public fixed capacity.
63    #[must_use]
64    pub const fn capacity(&self) -> usize {
65        CAP
66    }
67
68    /// Wipes the complete backing array and resets the visible length.
69    pub fn clear(&mut self) {
70        crate::wipe_bytes(&mut self.bytes);
71        self.len = 0;
72    }
73
74    #[cfg(test)]
75    pub(crate) const fn backing_for_test(&self) -> &[u8; CAP] {
76        &self.bytes
77    }
78
79    #[cfg(kani)]
80    pub(crate) const fn backing_for_proof(&self) -> &[u8; CAP] {
81        &self.bytes
82    }
83}
84
85impl<const CAP: usize> Drop for SecretArray<CAP> {
86    fn drop(&mut self) {
87        self.clear();
88    }
89}
90
91impl<const CAP: usize> core::fmt::Debug for SecretArray<CAP> {
92    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
93        formatter
94            .debug_struct("SecretArray")
95            .field("bytes", &"<redacted>")
96            .field("len", &self.len)
97            .field("capacity", &CAP)
98            .finish()
99    }
100}
101
102impl<const CAP: usize> core::fmt::Display for SecretArray<CAP> {
103    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        formatter.write_str("<redacted secret array>")
105    }
106}
107
108/// Ordinary fixed array created by explicit secret declassification.
109///
110/// This value is `Copy` and deliberately performs no cleanup on drop.
111#[derive(Clone, Copy, Eq, Hash, PartialEq)]
112pub struct DeclassifiedArray<const CAP: usize> {
113    bytes: [u8; CAP],
114    len: usize,
115}
116
117impl<const CAP: usize> DeclassifiedArray<CAP> {
118    /// Returns the ordinary initialized prefix.
119    #[must_use]
120    pub fn as_bytes(&self) -> &[u8] {
121        &self.bytes[..self.len]
122    }
123
124    /// Returns the public initialized length.
125    #[must_use]
126    pub const fn len(&self) -> usize {
127        self.len
128    }
129
130    /// Returns whether the initialized prefix is empty.
131    #[must_use]
132    pub const fn is_empty(&self) -> bool {
133        self.len == 0
134    }
135
136    /// Returns the fixed capacity.
137    #[must_use]
138    pub const fn capacity(&self) -> usize {
139        CAP
140    }
141
142    /// Returns the complete ordinary array and initialized length.
143    #[must_use]
144    pub const fn into_parts(self) -> ([u8; CAP], usize) {
145        (self.bytes, self.len)
146    }
147}
148
149impl<const CAP: usize> AsRef<[u8]> for DeclassifiedArray<CAP> {
150    fn as_ref(&self) -> &[u8] {
151        self.as_bytes()
152    }
153}
154
155impl<const CAP: usize> core::fmt::Debug for DeclassifiedArray<CAP> {
156    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157        formatter
158            .debug_tuple("DeclassifiedArray")
159            .field(&self.as_bytes())
160            .finish()
161    }
162}
163
164/// Owned heap secret bytes with initialized and spare-capacity cleanup.
165#[cfg(feature = "alloc")]
166pub struct SecretVec {
167    bytes: alloc::vec::Vec<u8>,
168}
169
170#[cfg(feature = "alloc")]
171impl SecretVec {
172    pub(super) fn from_frame(mut bytes: alloc::vec::Vec<u8>, len: usize) -> Self {
173        debug_assert!(len <= bytes.len());
174        bytes.truncate(len);
175        crate::wipe_vec_spare_capacity(&mut bytes);
176        Self { bytes }
177    }
178
179    /// Takes ownership and wipes the vector's spare capacity.
180    #[must_use]
181    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
182        crate::wipe_vec_spare_capacity(&mut bytes);
183        Self { bytes }
184    }
185
186    /// Copies caller-owned bytes into secret storage.
187    #[must_use]
188    pub fn from_slice(bytes: &[u8]) -> Self {
189        Self::from_vec(bytes.to_vec())
190    }
191
192    /// Replaces the owned bytes after wiping the displaced allocation.
193    ///
194    /// The replacement's spare capacity is wiped before ownership transfers.
195    /// The previous initialized bytes and spare capacity are wiped before its
196    /// allocation is released. This remains best-effort software cleanup and
197    /// cannot remove historical copies or allocator metadata.
198    pub fn replace_from_vec(&mut self, replacement: alloc::vec::Vec<u8>) {
199        drop(self.replace_and_wipe_displaced(replacement));
200    }
201
202    fn replace_and_wipe_displaced(
203        &mut self,
204        mut replacement: alloc::vec::Vec<u8>,
205    ) -> alloc::vec::Vec<u8> {
206        crate::wipe_vec_spare_capacity(&mut replacement);
207        let mut displaced = core::mem::replace(&mut self.bytes, replacement);
208        crate::wipe_vec_all(&mut displaced);
209        displaced
210    }
211
212    #[cfg(test)]
213    pub(crate) fn replace_for_test(
214        &mut self,
215        replacement: alloc::vec::Vec<u8>,
216    ) -> alloc::vec::Vec<u8> {
217        self.replace_and_wipe_displaced(replacement)
218    }
219
220    /// Creates an explicit borrowed interoperability view.
221    #[must_use]
222    pub fn expose_secret(&self) -> ExposedSecret<'_> {
223        ExposedSecret::new(&self.bytes)
224    }
225
226    /// Creates an explicit mutable interoperability view.
227    #[must_use]
228    pub fn expose_secret_mut(&mut self) -> ExposedSecretMut<'_> {
229        ExposedSecretMut {
230            bytes: &mut self.bytes,
231        }
232    }
233
234    /// Deliberately returns an ordinary vector without crate-managed cleanup.
235    #[must_use = "caller must apply its approved cleanup policy to the returned Vec"]
236    pub fn declassify_into_unprotected_vec(mut self) -> alloc::vec::Vec<u8> {
237        core::mem::take(&mut self.bytes)
238    }
239
240    /// Returns the public initialized length.
241    #[must_use]
242    pub fn len(&self) -> usize {
243        self.bytes.len()
244    }
245
246    /// Returns whether the initialized prefix is empty.
247    #[must_use]
248    pub fn is_empty(&self) -> bool {
249        self.bytes.is_empty()
250    }
251
252    /// Returns the public allocation capacity.
253    #[must_use]
254    pub fn capacity(&self) -> usize {
255        self.bytes.capacity()
256    }
257
258    /// Wipes initialized bytes and spare capacity, then resets the length.
259    pub fn clear(&mut self) {
260        crate::wipe_vec_all(&mut self.bytes);
261        self.bytes.clear();
262    }
263}
264
265#[cfg(feature = "alloc")]
266impl Drop for SecretVec {
267    fn drop(&mut self) {
268        self.clear();
269    }
270}
271
272#[cfg(feature = "alloc")]
273redacted_formatting!(SecretVec, "SecretVec");