Skip to main content

base64_ng/v2/secret/
encoders.rs

1//! Wiping owners around the fixed-work secret encoder state.
2
3#[cfg(feature = "alloc")]
4use super::SecretVec;
5use super::{SecretArray, SecretInput, SecretOutput};
6use crate::v2::{
7    Base64, Codec, Progress,
8    secret_encoder::{SecretEncodeError, SecretEncoderState, require_disjoint},
9};
10
11/// Stack-backed incremental secret encoder.
12///
13/// `CAP` is encoded output capacity. The public maximum input length is bound
14/// separately when the encoder is constructed.
15pub struct SecretArrayEncoder<const CAP: usize> {
16    state: SecretEncoderState,
17    output: [u8; CAP],
18}
19
20impl<const CAP: usize> SecretArrayEncoder<CAP> {
21    const CAPACITY_ASSERT: () = enforce_stack_capacity::<CAP>();
22
23    /// Creates an empty bounded encoder over wiping stack storage.
24    pub fn new<S: Codec>(
25        codec: &Base64<S>,
26        maximum_input_len: usize,
27    ) -> Result<Self, SecretEncodeError> {
28        const { enforce_stack_capacity::<CAP>() }
29        let () = Self::CAPACITY_ASSERT;
30        Ok(Self {
31            state: SecretEncoderState::new(codec.settings(), maximum_input_len, CAP)?,
32            output: [0; CAP],
33        })
34    }
35
36    /// Encodes one classified chunk into private secret output storage.
37    pub fn update(&mut self, input: &SecretInput<'_>) -> Result<Progress, SecretEncodeError> {
38        if let Err(error) = require_disjoint(input.classified_bytes(), &self.output) {
39            self.state.latch_external_failure();
40            self.fail_storage();
41            return Err(error);
42        }
43        match self
44            .state
45            .update(input.classified_bytes(), &mut self.output)
46        {
47            Ok(progress) => Ok(progress),
48            Err(error) => {
49                self.fail_storage();
50                Err(error)
51            }
52        }
53    }
54
55    /// Completes encoding and returns redacted wiping storage.
56    pub fn finish(mut self) -> Result<SecretArray<CAP>, SecretEncodeError> {
57        let written = match self.state.finish(&mut self.output) {
58            Ok(written) => written,
59            Err(error) => {
60                self.fail_storage();
61                return Err(error);
62            }
63        };
64        let output = core::mem::replace(&mut self.output, [0; CAP]);
65        SecretArray::from_frame(output, written).map_err(|error| SecretEncodeError::OutputFull {
66            required: error.length(),
67            available: error.capacity(),
68        })
69    }
70
71    /// Encodes one complete classified input into a secret array.
72    pub fn encode<S: Codec>(
73        codec: &Base64<S>,
74        input: &SecretInput<'_>,
75    ) -> Result<SecretArray<CAP>, SecretEncodeError> {
76        let mut encoder = Self::new(codec, input.len())?;
77        encoder.update(input)?;
78        encoder.finish()
79    }
80
81    /// Returns public encoder metadata without exposing output bytes.
82    #[must_use]
83    pub const fn state(&self) -> &SecretEncoderState {
84        &self.state
85    }
86
87    fn fail_storage(&mut self) {
88        crate::wipe_bytes(&mut self.output);
89    }
90
91    #[cfg(test)]
92    pub(crate) const fn storage_for_test(&self) -> &[u8; CAP] {
93        &self.output
94    }
95}
96
97#[allow(clippy::manual_assert)]
98const fn enforce_stack_capacity<const CAP: usize>() {
99    if CAP > crate::v2::secret_encoder::MAX_SECRET_STACK_ENCODED {
100        panic!("SecretArrayEncoder encoded capacity exceeds 1368-byte stack limit");
101    }
102}
103
104impl<const CAP: usize> Drop for SecretArrayEncoder<CAP> {
105    fn drop(&mut self) {
106        self.fail_storage();
107    }
108}
109
110impl<const CAP: usize> core::fmt::Debug for SecretArrayEncoder<CAP> {
111    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112        formatter
113            .debug_struct("SecretArrayEncoder")
114            .field("output", &"<redacted>")
115            .field("state", &self.state)
116            .finish_non_exhaustive()
117    }
118}
119
120/// Incremental secret encoder over caller-provided protected output storage.
121pub struct SecretEncoder<'a> {
122    state: SecretEncoderState,
123    output: Option<&'a mut [u8]>,
124}
125
126impl<'a> SecretEncoder<'a> {
127    /// Binds one complete protected output range before accepting input.
128    pub fn new<S: Codec>(
129        codec: &Base64<S>,
130        maximum_input_len: usize,
131        output: &'a mut [u8],
132    ) -> Result<Self, SecretEncodeError> {
133        let state = SecretEncoderState::new(codec.settings(), maximum_input_len, output.len())?;
134        crate::wipe_bytes(output);
135        Ok(Self {
136            state,
137            output: Some(output),
138        })
139    }
140
141    /// Encodes one classified chunk into the protected output range.
142    pub fn update(&mut self, input: &SecretInput<'_>) -> Result<Progress, SecretEncodeError> {
143        let output = self
144            .output
145            .as_deref_mut()
146            .ok_or(SecretEncodeError::Failed)?;
147        if let Err(error) = require_disjoint(input.classified_bytes(), output) {
148            self.state.latch_external_failure();
149            crate::wipe_bytes(output);
150            return Err(error);
151        }
152        match self.state.update(input.classified_bytes(), output) {
153            Ok(progress) => Ok(progress),
154            Err(error) => {
155                crate::wipe_bytes(output);
156                Err(error)
157            }
158        }
159    }
160
161    /// Completes encoding and returns a wiping borrowed output guard.
162    pub fn finish(mut self) -> Result<SecretOutput<'a>, SecretEncodeError> {
163        let Some(output) = self.output.take() else {
164            return Err(SecretEncodeError::Failed);
165        };
166        let written = match self.state.finish(output) {
167            Ok(written) => written,
168            Err(error) => {
169                crate::wipe_bytes(output);
170                return Err(error);
171            }
172        };
173        let available = output.len();
174        SecretOutput::from_initialized(output, written).map_err(|_| SecretEncodeError::OutputFull {
175            required: written,
176            available,
177        })
178    }
179
180    /// Returns public encoder metadata without exposing output bytes.
181    #[must_use]
182    pub const fn state(&self) -> &SecretEncoderState {
183        &self.state
184    }
185
186    fn fail_storage(&mut self) {
187        if let Some(output) = self.output.as_deref_mut() {
188            crate::wipe_bytes(output);
189        }
190    }
191}
192
193impl Drop for SecretEncoder<'_> {
194    fn drop(&mut self) {
195        self.fail_storage();
196    }
197}
198
199impl core::fmt::Debug for SecretEncoder<'_> {
200    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
201        formatter
202            .debug_struct("SecretEncoder")
203            .field("output", &"<redacted>")
204            .field("state", &self.state)
205            .finish_non_exhaustive()
206    }
207}
208
209/// Preallocated heap-backed incremental secret encoder.
210#[cfg(feature = "alloc")]
211pub struct SecretVecEncoder {
212    state: SecretEncoderState,
213    output: alloc::vec::Vec<u8>,
214}
215
216#[cfg(feature = "alloc")]
217impl SecretVecEncoder {
218    /// Preallocates the complete encoded output before accepting input.
219    pub fn new<S: Codec>(
220        codec: &Base64<S>,
221        maximum_input_len: usize,
222    ) -> Result<Self, SecretEncodeError> {
223        let padded = codec.settings().encode_padding() == crate::v2::EncodePadding::Padded;
224        let required = crate::checked_encoded_len(maximum_input_len, padded)
225            .ok_or(SecretEncodeError::LengthOverflow)?;
226        let output = allocate_zeroed(required)?;
227        Ok(Self {
228            state: SecretEncoderState::new(codec.settings(), maximum_input_len, required)?,
229            output,
230        })
231    }
232
233    /// Encodes one classified chunk into preallocated secret storage.
234    pub fn update(&mut self, input: &SecretInput<'_>) -> Result<Progress, SecretEncodeError> {
235        if let Err(error) = require_disjoint(input.classified_bytes(), &self.output) {
236            self.state.latch_external_failure();
237            self.fail_storage();
238            return Err(error);
239        }
240        match self
241            .state
242            .update(input.classified_bytes(), &mut self.output)
243        {
244            Ok(progress) => Ok(progress),
245            Err(error) => {
246                self.fail_storage();
247                Err(error)
248            }
249        }
250    }
251
252    /// Completes encoding and returns redacted heap storage.
253    pub fn finish(mut self) -> Result<SecretVec, SecretEncodeError> {
254        let written = match self.state.finish(&mut self.output) {
255            Ok(written) => written,
256            Err(error) => {
257                self.fail_storage();
258                return Err(error);
259            }
260        };
261        let output = core::mem::take(&mut self.output);
262        Ok(SecretVec::from_frame(output, written))
263    }
264
265    /// Encodes one complete classified input into a secret vector.
266    pub fn encode<S: Codec>(
267        codec: &Base64<S>,
268        input: &SecretInput<'_>,
269    ) -> Result<SecretVec, SecretEncodeError> {
270        let mut encoder = Self::new(codec, input.len())?;
271        encoder.update(input)?;
272        encoder.finish()
273    }
274
275    /// Returns public encoder metadata without exposing output bytes.
276    #[must_use]
277    pub const fn state(&self) -> &SecretEncoderState {
278        &self.state
279    }
280
281    #[cfg(test)]
282    pub(crate) fn allocation_snapshot(&self) -> (*const u8, usize) {
283        (self.output.as_ptr(), self.output.capacity())
284    }
285
286    fn fail_storage(&mut self) {
287        crate::wipe_bytes(&mut self.output);
288    }
289}
290
291#[cfg(feature = "alloc")]
292impl Drop for SecretVecEncoder {
293    fn drop(&mut self) {
294        self.fail_storage();
295    }
296}
297
298#[cfg(feature = "alloc")]
299impl core::fmt::Debug for SecretVecEncoder {
300    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
301        formatter
302            .debug_struct("SecretVecEncoder")
303            .field("output", &"<redacted>")
304            .field("state", &self.state)
305            .finish_non_exhaustive()
306    }
307}
308
309#[cfg(feature = "alloc")]
310fn allocate_zeroed(capacity: usize) -> Result<alloc::vec::Vec<u8>, SecretEncodeError> {
311    let mut bytes = alloc::vec::Vec::new();
312    bytes
313        .try_reserve_exact(capacity)
314        .map_err(|_| SecretEncodeError::AllocationFailed)?;
315    bytes.resize(capacity, 0);
316    Ok(bytes)
317}
318
319impl<S: Codec> Base64<S> {
320    /// Encodes classified input into a bounded wiping array.
321    pub fn encode_secret_array<const CAP: usize>(
322        &self,
323        input: &SecretInput<'_>,
324    ) -> Result<SecretArray<CAP>, SecretEncodeError> {
325        SecretArrayEncoder::encode(self, input)
326    }
327
328    /// Encodes classified input into a wiping borrowed output guard.
329    pub fn encode_secret_into<'a>(
330        &self,
331        input: &SecretInput<'_>,
332        output: &'a mut [u8],
333    ) -> Result<SecretOutput<'a>, SecretEncodeError> {
334        require_disjoint(input.classified_bytes(), output)?;
335        let mut encoder = SecretEncoder::new(self, input.len(), output)?;
336        encoder.update(input)?;
337        encoder.finish()
338    }
339
340    /// Encodes classified input into preallocated wiping heap storage.
341    #[cfg(feature = "alloc")]
342    pub fn encode_secret_vec(
343        &self,
344        input: &SecretInput<'_>,
345    ) -> Result<SecretVec, SecretEncodeError> {
346        SecretVecEncoder::encode(self, input)
347    }
348}