Skip to main content

base64_ng/v2/secret/
exposure.rs

1//! Borrowed secret input, output, exposure, and declassification views.
2
3use super::super::bounded::BufferLengthError;
4
5/// Explicit borrowed interoperability view of secret bytes.
6///
7/// Construct this value only through an `expose_secret` method. Formatting
8/// remains redacted, but the explicit [`Self::as_bytes`] method and standard
9/// slice coercion traits make deliberate interoperability convenient.
10pub struct ExposedSecret<'a> {
11    bytes: &'a [u8],
12}
13
14impl<'a> ExposedSecret<'a> {
15    pub(super) const fn new(bytes: &'a [u8]) -> Self {
16        Self { bytes }
17    }
18
19    /// Returns the deliberately exposed bytes.
20    #[must_use]
21    pub const fn as_bytes(&self) -> &[u8] {
22        self.bytes
23    }
24
25    /// Returns the public byte length.
26    #[must_use]
27    pub const fn len(&self) -> usize {
28        self.bytes.len()
29    }
30
31    /// Returns whether the exposed view is empty.
32    #[must_use]
33    pub const fn is_empty(&self) -> bool {
34        self.bytes.is_empty()
35    }
36}
37
38impl AsRef<[u8]> for ExposedSecret<'_> {
39    fn as_ref(&self) -> &[u8] {
40        self.bytes
41    }
42}
43
44impl core::ops::Deref for ExposedSecret<'_> {
45    type Target = [u8];
46
47    fn deref(&self) -> &Self::Target {
48        self.bytes
49    }
50}
51
52redacted_formatting!(ExposedSecret<'_>, "ExposedSecret");
53
54/// Explicit mutable interoperability view of secret bytes.
55///
56/// The originating secret owner retains cleanup responsibility.
57pub struct ExposedSecretMut<'a> {
58    pub(super) bytes: &'a mut [u8],
59}
60
61impl ExposedSecretMut<'_> {
62    /// Returns the deliberately exposed bytes.
63    #[must_use]
64    pub fn as_bytes(&self) -> &[u8] {
65        self.bytes
66    }
67
68    /// Returns the deliberately exposed mutable bytes.
69    #[must_use]
70    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
71        self.bytes
72    }
73
74    /// Returns the public byte length.
75    #[must_use]
76    pub fn len(&self) -> usize {
77        self.bytes.len()
78    }
79
80    /// Returns whether the exposed view is empty.
81    #[must_use]
82    pub fn is_empty(&self) -> bool {
83        self.bytes.is_empty()
84    }
85}
86
87impl AsRef<[u8]> for ExposedSecretMut<'_> {
88    fn as_ref(&self) -> &[u8] {
89        self.bytes
90    }
91}
92
93impl AsMut<[u8]> for ExposedSecretMut<'_> {
94    fn as_mut(&mut self) -> &mut [u8] {
95        self.bytes
96    }
97}
98
99impl core::ops::Deref for ExposedSecretMut<'_> {
100    type Target = [u8];
101
102    fn deref(&self) -> &Self::Target {
103        self.bytes
104    }
105}
106
107impl core::ops::DerefMut for ExposedSecretMut<'_> {
108    fn deref_mut(&mut self) -> &mut Self::Target {
109        self.bytes
110    }
111}
112
113redacted_formatting!(ExposedSecretMut<'_>, "ExposedSecretMut");
114
115/// Non-Clone borrowed input classified as secret-bearing.
116///
117/// This wrapper does not own or wipe the borrowed bytes. It prevents implicit
118/// passage into ordinary codecs; callers must deliberately call
119/// [`Self::expose_secret`] to obtain an interoperability view.
120pub struct SecretInput<'a> {
121    bytes: &'a [u8],
122}
123
124impl<'a> SecretInput<'a> {
125    /// Classifies caller-owned bytes as secret input.
126    #[must_use]
127    pub const fn new(bytes: &'a [u8]) -> Self {
128        Self { bytes }
129    }
130
131    /// Creates an explicit borrowed interoperability view.
132    #[must_use]
133    pub const fn expose_secret(&self) -> ExposedSecret<'_> {
134        ExposedSecret::new(self.bytes)
135    }
136
137    /// Returns the public input length without exposing bytes.
138    #[must_use]
139    pub const fn len(&self) -> usize {
140        self.bytes.len()
141    }
142
143    /// Returns whether the input is empty without exposing bytes.
144    #[must_use]
145    pub const fn is_empty(&self) -> bool {
146        self.bytes.is_empty()
147    }
148
149    pub(crate) const fn classified_bytes(&self) -> &[u8] {
150        self.bytes
151    }
152}
153
154redacted_formatting!(SecretInput<'_>, "SecretInput");
155
156/// Borrowed secret output with full-range cleanup on drop.
157///
158/// Construction wipes unused tail bytes. Drop wipes the complete borrowed
159/// range. A consuming [`Self::declassify`] call deliberately transfers the
160/// bytes into an ordinary non-wiping view.
161pub struct SecretOutput<'a> {
162    storage: &'a mut [u8],
163    len: usize,
164}
165
166impl<'a> SecretOutput<'a> {
167    /// Wraps one initialized prefix and assumes cleanup responsibility.
168    ///
169    /// Invalid lengths wipe the complete range before returning an error.
170    pub fn from_initialized(storage: &'a mut [u8], len: usize) -> Result<Self, BufferLengthError> {
171        let capacity = storage.len();
172        if len > capacity {
173            crate::wipe_bytes(storage);
174            return Err(BufferLengthError::new(len, capacity));
175        }
176        crate::wipe_tail(storage, len);
177        Ok(Self { storage, len })
178    }
179
180    /// Creates an empty output guard after wiping the complete range.
181    #[must_use]
182    pub fn empty(storage: &'a mut [u8]) -> Self {
183        crate::wipe_bytes(storage);
184        Self { storage, len: 0 }
185    }
186
187    /// Creates an explicit borrowed interoperability view.
188    #[must_use]
189    pub fn expose_secret(&self) -> ExposedSecret<'_> {
190        ExposedSecret::new(&self.storage[..self.len])
191    }
192
193    /// Creates an explicit mutable interoperability view.
194    #[must_use]
195    pub fn expose_secret_mut(&mut self) -> ExposedSecretMut<'_> {
196        ExposedSecretMut {
197            bytes: &mut self.storage[..self.len],
198        }
199    }
200
201    /// Deliberately transfers the initialized prefix into ordinary storage.
202    ///
203    /// The returned view does not wipe on drop. Its tail remains zeroed, and
204    /// the caller becomes responsible for any later cleanup requirement.
205    #[must_use = "declassification transfers cleanup responsibility to the caller"]
206    pub fn declassify(mut self) -> DeclassifiedOutput<'a> {
207        let storage = core::mem::take(&mut self.storage);
208        let len = self.len;
209        self.len = 0;
210        DeclassifiedOutput { storage, len }
211    }
212
213    /// Returns the public initialized length.
214    #[must_use]
215    pub const fn len(&self) -> usize {
216        self.len
217    }
218
219    /// Returns the public borrowed capacity.
220    #[must_use]
221    pub fn capacity(&self) -> usize {
222        self.storage.len()
223    }
224
225    /// Returns whether the initialized prefix is empty.
226    #[must_use]
227    pub const fn is_empty(&self) -> bool {
228        self.len == 0
229    }
230
231    /// Wipes the complete range and resets the initialized length.
232    pub fn clear(&mut self) {
233        crate::wipe_bytes(self.storage);
234        self.len = 0;
235    }
236}
237
238impl Drop for SecretOutput<'_> {
239    fn drop(&mut self) {
240        self.clear();
241    }
242}
243
244redacted_formatting!(SecretOutput<'_>, "SecretOutput");
245
246/// Ordinary borrowed output created by explicit declassification.
247///
248/// This value deliberately performs no cleanup on drop.
249pub struct DeclassifiedOutput<'a> {
250    storage: &'a mut [u8],
251    len: usize,
252}
253
254impl<'a> DeclassifiedOutput<'a> {
255    /// Returns the ordinary initialized prefix.
256    #[must_use]
257    pub fn as_bytes(&self) -> &[u8] {
258        &self.storage[..self.len]
259    }
260
261    /// Returns the ordinary initialized prefix mutably.
262    #[must_use]
263    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
264        &mut self.storage[..self.len]
265    }
266
267    /// Returns the public initialized length.
268    #[must_use]
269    pub const fn len(&self) -> usize {
270        self.len
271    }
272
273    /// Returns whether the initialized prefix is empty.
274    #[must_use]
275    pub const fn is_empty(&self) -> bool {
276        self.len == 0
277    }
278
279    /// Returns the complete caller-owned range and initialized length.
280    #[must_use]
281    pub fn into_parts(self) -> (&'a mut [u8], usize) {
282        (self.storage, self.len)
283    }
284}
285
286impl AsRef<[u8]> for DeclassifiedOutput<'_> {
287    fn as_ref(&self) -> &[u8] {
288        self.as_bytes()
289    }
290}
291
292impl AsMut<[u8]> for DeclassifiedOutput<'_> {
293    fn as_mut(&mut self) -> &mut [u8] {
294        self.as_bytes_mut()
295    }
296}