Skip to main content

base64_ng/
buffers.rs

1//! Stack-backed and owned buffer wrappers.
2
3use crate::{
4    DecodeError, EncodeError, STANDARD, constant_time_eq_public_len, wipe_bytes, wipe_tail,
5};
6#[cfg(feature = "alloc")]
7use crate::{wipe_vec_all, wipe_vec_spare_capacity};
8#[cfg(feature = "alloc")]
9use alloc::{string::String, vec::Vec};
10
11/// Stack-backed encoded Base64 output.
12///
13/// This type is intended for short values where heap allocation would be
14/// unnecessary but manually sizing and passing a separate output slice is
15/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
16/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
17///
18/// The backing array is cleared when the value is dropped. This is best-effort
19/// data-retention reduction and is not a formal zeroization guarantee.
20///
21/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
22/// runtime JIT may still optimize or retain cleared bytes in ways this crate
23/// cannot control. `wasm32` builds fail closed by default; enable
24/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
25/// this limitation and applies its own memory strategy around stack-backed
26/// buffers.
27pub struct EncodedBuffer<const CAP: usize> {
28    bytes: [u8; CAP],
29    len: usize,
30}
31
32/// Owned stack array extracted from [`EncodedBuffer`].
33///
34/// This wrapper keeps the extracted encoded bytes on the crate's best-effort
35/// drop-time cleanup path. Use
36/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
37/// bare array is unavoidable and the caller will handle cleanup.
38pub struct ExposedEncodedArray<const CAP: usize> {
39    bytes: [u8; CAP],
40    len: usize,
41}
42
43impl<const CAP: usize> ExposedEncodedArray<CAP> {
44    /// Wraps an encoded backing array and visible length.
45    ///
46    /// # Panics
47    ///
48    /// Panics if `len` is greater than `CAP`.
49    #[must_use]
50    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
51        assert!(len <= CAP, "visible length exceeds array capacity");
52        Self { bytes, len }
53    }
54
55    /// Returns the visible encoded bytes.
56    #[must_use]
57    pub fn as_bytes(&self) -> &[u8] {
58        &self.bytes[..self.len]
59    }
60
61    /// Returns the number of visible encoded bytes.
62    #[must_use]
63    pub const fn len(&self) -> usize {
64        self.len
65    }
66
67    /// Returns whether there are no visible encoded bytes.
68    #[must_use]
69    pub const fn is_empty(&self) -> bool {
70        self.len == 0
71    }
72
73    /// Returns the backing array capacity.
74    #[must_use]
75    pub const fn capacity(&self) -> usize {
76        CAP
77    }
78
79    /// Consumes the wrapper and returns a bare array plus visible length.
80    ///
81    /// This is an unprotected escape hatch. The returned array will not be
82    /// cleared by this crate on drop. Callers must clear it with their own
83    /// approved zeroization policy.
84    ///
85    /// # Security
86    ///
87    /// Treat this as a cleanup-boundary API. Failing to clear the returned
88    /// array leaves the encoded bytes in ordinary caller-owned memory until
89    /// overwritten by later stack or heap activity.
90    #[must_use = "caller must zeroize the returned array"]
91    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
92        let len = self.len;
93        self.len = 0;
94        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
95    }
96}
97
98impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
99    fn drop(&mut self) {
100        wipe_bytes(&mut self.bytes);
101        self.len = 0;
102    }
103}
104
105impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
106    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        formatter
108            .debug_struct("ExposedEncodedArray")
109            .field("bytes", &"<redacted>")
110            .field("len", &self.len)
111            .field("capacity", &CAP)
112            .finish()
113    }
114}
115
116impl<const CAP: usize> EncodedBuffer<CAP> {
117    /// Creates an empty encoded buffer.
118    #[must_use]
119    pub const fn new() -> Self {
120        Self {
121            bytes: [0u8; CAP],
122            len: 0,
123        }
124    }
125
126    /// Returns the full backing array as an output slice for crate-internal
127    /// encode paths.
128    pub(crate) fn as_mut_capacity(&mut self) -> &mut [u8] {
129        &mut self.bytes
130    }
131
132    /// Sets the visible length after a crate-internal encode path succeeds.
133    pub(crate) fn set_filled(&mut self, written: usize) -> Result<(), EncodeError> {
134        debug_assert!(
135            written <= CAP,
136            "encoder wrote past stack-backed buffer capacity"
137        );
138        if written > CAP {
139            self.clear();
140            return Err(EncodeError::OutputTooSmall {
141                required: written,
142                available: CAP,
143            });
144        }
145        self.len = written;
146        Ok(())
147    }
148
149    /// Returns the number of visible encoded bytes.
150    #[must_use]
151    pub const fn len(&self) -> usize {
152        self.len
153    }
154
155    /// Returns whether the buffer has no visible encoded bytes.
156    #[must_use]
157    pub const fn is_empty(&self) -> bool {
158        self.len == 0
159    }
160
161    /// Returns whether the visible encoded bytes fill the stack backing array.
162    #[must_use]
163    pub const fn is_full(&self) -> bool {
164        self.len == CAP
165    }
166
167    /// Returns the stack capacity in bytes.
168    #[must_use]
169    pub const fn capacity(&self) -> usize {
170        CAP
171    }
172
173    /// Returns the number of unused bytes in the stack backing array.
174    #[must_use]
175    pub const fn remaining_capacity(&self) -> usize {
176        CAP - self.len
177    }
178
179    /// Returns the visible encoded bytes.
180    #[must_use]
181    pub fn as_bytes(&self) -> &[u8] {
182        &self.bytes[..self.len]
183    }
184
185    /// Returns the visible encoded bytes as UTF-8 text.
186    ///
187    /// Encoded Base64 output is produced as ASCII by this crate, so this
188    /// method should not fail unless an internal invariant has been broken.
189    /// It is provided for callers that prefer a fallible accessor over
190    /// [`Self::as_str`].
191    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
192        core::str::from_utf8(self.as_bytes())
193    }
194
195    /// Returns the visible encoded bytes as UTF-8.
196    ///
197    /// # Panics
198    ///
199    /// Panics only if the crate's internal invariant is broken and the buffer
200    /// contains non-UTF-8 bytes.
201    #[must_use]
202    pub fn as_str(&self) -> &str {
203        match self.as_utf8() {
204            Ok(output) => output,
205            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
206        }
207    }
208
209    /// Compares this encoded output to `other` without short-circuiting on the
210    /// first differing byte.
211    ///
212    /// Length and the final equality result remain public. Different lengths
213    /// return `false` immediately; use this helper only when the compared
214    /// lengths are public protocol facts or have been normalized by the
215    /// caller. For equal-length inputs, this helper scans every byte before
216    /// returning. It is constant-time-oriented best effort, not a formal
217    /// cryptographic constant-time guarantee. This comparison is deliberately
218    /// explicit: redacted buffer types do not implement [`PartialEq`] because
219    /// `==` would make a best-effort helper look like a formal token/MAC
220    /// comparison primitive.
221    ///
222    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
223    /// authentication-secret comparison primitive in high-assurance systems.
224    /// Applications that can admit dependencies should use a reviewed
225    /// constant-time comparison primitive, such as `subtle`, at the protocol
226    /// boundary.
227    #[doc(alias = "constant_time_eq")]
228    #[must_use]
229    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
230        constant_time_eq_public_len(self.as_bytes(), other)
231    }
232
233    /// Consumes the wrapper and returns the backing array plus visible length
234    /// inside a drop-wiping exposed wrapper.
235    ///
236    /// This is an explicit escape hatch for no-alloc interop with APIs that
237    /// require ownership of a fixed array. The returned
238    /// [`ExposedEncodedArray`] remains redacted by formatting and clears its
239    /// backing array on drop.
240    #[must_use]
241    pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
242        let len = self.len;
243        self.len = 0;
244        ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
245    }
246
247    /// Clears the visible bytes and the full backing array.
248    pub fn clear(&mut self) {
249        wipe_bytes(&mut self.bytes);
250        self.len = 0;
251    }
252
253    /// Clears bytes after the visible prefix.
254    pub fn clear_tail(&mut self) {
255        wipe_tail(&mut self.bytes, self.len);
256    }
257}
258
259impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
260    fn as_ref(&self) -> &[u8] {
261        self.as_bytes()
262    }
263}
264
265impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
266    /// Clones the visible encoded bytes into a second stack-backed buffer.
267    ///
268    /// Security note: cloning duplicates the visible bytes in memory. Both the
269    /// original and the clone must be dropped or explicitly cleared before the
270    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
271    /// compiler may also create temporary stack copies while performing the
272    /// copy; those intermediates are outside this crate's cleanup boundary.
273    /// Avoid cloning encoded secret material; use `SecretBuffer` when redacted
274    /// formatting and heap-owned secret handling are required.
275    fn clone(&self) -> Self {
276        let mut output = Self::new();
277        output.bytes[..self.len].copy_from_slice(self.as_bytes());
278        output.len = self.len;
279        output
280    }
281}
282
283impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
284    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
285        formatter
286            .debug_struct("EncodedBuffer")
287            .field("bytes", &"<redacted>")
288            .field("len", &self.len)
289            .field("capacity", &CAP)
290            .finish()
291    }
292}
293
294impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
295    /// Writes the full Base64 text.
296    ///
297    /// Security note: this is intentionally not redacted. Do not use
298    /// `EncodedBuffer` for encoded secrets that may reach logs or error
299    /// messages; use `SecretBuffer` for redacted formatting.
300    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
301        formatter.write_str(self.as_str())
302    }
303}
304
305impl<const CAP: usize> Default for EncodedBuffer<CAP> {
306    fn default() -> Self {
307        Self::new()
308    }
309}
310
311impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
312    fn drop(&mut self) {
313        self.clear();
314    }
315}
316
317impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
318    type Error = EncodeError;
319
320    /// Encodes bytes into strict standard padded Base64 in a stack-backed
321    /// buffer.
322    ///
323    /// Use [`crate::Engine::encode_buffer`] or [`crate::Profile::encode_buffer`] when a
324    /// different alphabet, padding mode, or line-wrapping profile is required.
325    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
326        STANDARD.encode_buffer(input)
327    }
328}
329
330impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
331    type Error = EncodeError;
332
333    /// Encodes a byte array into strict standard padded Base64 in a
334    /// stack-backed buffer.
335    ///
336    /// Use [`crate::Engine::encode_buffer`] or [`crate::Profile::encode_buffer`] when a
337    /// different alphabet, padding mode, or line-wrapping profile is required.
338    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
339        Self::try_from(&input[..])
340    }
341}
342
343impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
344    type Error = EncodeError;
345
346    /// Encodes UTF-8 text bytes into strict standard padded Base64 in a
347    /// stack-backed buffer.
348    ///
349    /// This treats the string as raw input bytes. Use
350    /// [`crate::Engine::encode_buffer`] or [`crate::Profile::encode_buffer`] when a
351    /// different alphabet, padding mode, or line-wrapping profile is required.
352    fn try_from(input: &str) -> Result<Self, Self::Error> {
353        Self::try_from(input.as_bytes())
354    }
355}
356
357/// Stack-backed decoded Base64 output.
358///
359/// This type is intended for short decoded values where heap allocation would
360/// be unnecessary but manually sizing and passing a separate output slice is
361/// noisy. Decoded data may be binary or secret-bearing, so formatting is
362/// redacted and contents are exposed only through explicit byte accessors.
363///
364/// The backing array is cleared when the value is dropped. This is best-effort
365/// data-retention reduction and is not a formal zeroization guarantee.
366///
367/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
368/// runtime JIT may still optimize or retain cleared bytes in ways this crate
369/// cannot control. `wasm32` builds fail closed by default; enable
370/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
371/// this limitation and applies its own memory strategy around stack-backed
372/// buffers.
373pub struct DecodedBuffer<const CAP: usize> {
374    bytes: [u8; CAP],
375    len: usize,
376}
377
378/// Owned stack array extracted from [`DecodedBuffer`].
379///
380/// This wrapper keeps the extracted decoded bytes on the crate's best-effort
381/// drop-time cleanup path. Use
382/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
383/// bare array is unavoidable and the caller will handle cleanup.
384pub struct ExposedDecodedArray<const CAP: usize> {
385    bytes: [u8; CAP],
386    len: usize,
387}
388
389impl<const CAP: usize> ExposedDecodedArray<CAP> {
390    /// Wraps a decoded backing array and visible length.
391    ///
392    /// # Panics
393    ///
394    /// Panics if `len` is greater than `CAP`.
395    #[must_use]
396    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
397        assert!(len <= CAP, "visible length exceeds array capacity");
398        Self { bytes, len }
399    }
400
401    /// Returns the visible decoded bytes.
402    #[must_use]
403    pub fn as_bytes(&self) -> &[u8] {
404        &self.bytes[..self.len]
405    }
406
407    /// Returns the number of visible decoded bytes.
408    #[must_use]
409    pub const fn len(&self) -> usize {
410        self.len
411    }
412
413    /// Returns whether there are no visible decoded bytes.
414    #[must_use]
415    pub const fn is_empty(&self) -> bool {
416        self.len == 0
417    }
418
419    /// Returns the backing array capacity.
420    #[must_use]
421    pub const fn capacity(&self) -> usize {
422        CAP
423    }
424
425    /// Consumes the wrapper and returns a bare array plus visible length.
426    ///
427    /// This is an unprotected escape hatch. The returned array will not be
428    /// cleared by this crate on drop. Callers must clear it with their own
429    /// approved zeroization policy.
430    ///
431    /// # Security
432    ///
433    /// Treat this as a cleanup-boundary API. Failing to clear the returned
434    /// array leaves decoded bytes, which may be secret-bearing, in ordinary
435    /// caller-owned memory until overwritten by later stack or heap activity.
436    #[must_use = "caller must zeroize the returned array"]
437    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
438        let len = self.len;
439        self.len = 0;
440        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
441    }
442}
443
444impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
445    fn drop(&mut self) {
446        wipe_bytes(&mut self.bytes);
447        self.len = 0;
448    }
449}
450
451impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
452    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
453        formatter
454            .debug_struct("ExposedDecodedArray")
455            .field("bytes", &"<redacted>")
456            .field("len", &self.len)
457            .field("capacity", &CAP)
458            .finish()
459    }
460}
461
462impl<const CAP: usize> DecodedBuffer<CAP> {
463    /// Creates an empty decoded buffer.
464    #[must_use]
465    pub const fn new() -> Self {
466        Self {
467            bytes: [0u8; CAP],
468            len: 0,
469        }
470    }
471
472    /// Returns the full backing array as an output slice for crate-internal
473    /// decode paths.
474    pub(crate) fn as_mut_capacity(&mut self) -> &mut [u8] {
475        &mut self.bytes
476    }
477
478    /// Sets the visible length after a crate-internal decode path succeeds.
479    pub(crate) fn set_filled(&mut self, written: usize) -> Result<(), DecodeError> {
480        debug_assert!(
481            written <= CAP,
482            "decoder wrote past stack-backed buffer capacity"
483        );
484        if written > CAP {
485            self.clear();
486            return Err(DecodeError::OutputTooSmall {
487                required: written,
488                available: CAP,
489            });
490        }
491        self.len = written;
492        Ok(())
493    }
494
495    /// Returns the number of visible decoded bytes.
496    #[must_use]
497    pub const fn len(&self) -> usize {
498        self.len
499    }
500
501    /// Returns whether the buffer has no visible decoded bytes.
502    #[must_use]
503    pub const fn is_empty(&self) -> bool {
504        self.len == 0
505    }
506
507    /// Returns whether the visible decoded bytes fill the stack backing array.
508    #[must_use]
509    pub const fn is_full(&self) -> bool {
510        self.len == CAP
511    }
512
513    /// Returns the stack capacity in bytes.
514    #[must_use]
515    pub const fn capacity(&self) -> usize {
516        CAP
517    }
518
519    /// Returns the number of unused bytes in the stack backing array.
520    #[must_use]
521    pub const fn remaining_capacity(&self) -> usize {
522        CAP - self.len
523    }
524
525    /// Returns the visible decoded bytes.
526    #[must_use]
527    pub fn as_bytes(&self) -> &[u8] {
528        &self.bytes[..self.len]
529    }
530
531    /// Returns the visible decoded bytes as UTF-8 text.
532    ///
533    /// Decoded Base64 output is arbitrary bytes, so this method is fallible.
534    /// Use [`Self::as_bytes`] when the decoded payload is binary or when text
535    /// validation belongs to a higher protocol layer.
536    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
537        core::str::from_utf8(self.as_bytes())
538    }
539
540    /// Compares this decoded output to `other` without short-circuiting on the
541    /// first differing byte.
542    ///
543    /// Length and the final equality result remain public. Different lengths
544    /// return `false` immediately; use this helper only when the compared
545    /// lengths are public protocol facts or have been normalized by the
546    /// caller. For equal-length inputs, this helper scans every byte before
547    /// returning. It is constant-time-oriented best effort, not a formal
548    /// cryptographic constant-time guarantee. This comparison is deliberately
549    /// explicit: redacted buffer types do not implement [`PartialEq`] because
550    /// `==` would make a best-effort helper look like a formal token/MAC
551    /// comparison primitive.
552    ///
553    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
554    /// authentication-secret comparison primitive in high-assurance systems.
555    /// Applications that can admit dependencies should use a reviewed
556    /// constant-time comparison primitive, such as `subtle`, at the protocol
557    /// boundary.
558    #[doc(alias = "constant_time_eq")]
559    #[must_use]
560    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
561        constant_time_eq_public_len(self.as_bytes(), other)
562    }
563
564    /// Consumes the wrapper and returns the backing array plus visible length
565    /// inside a drop-wiping exposed wrapper.
566    ///
567    /// This is an explicit escape hatch for no-alloc interop with APIs that
568    /// require ownership of a fixed array. The returned
569    /// [`ExposedDecodedArray`] remains redacted by formatting and clears its
570    /// backing array on drop.
571    #[must_use]
572    pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
573        let len = self.len;
574        self.len = 0;
575        ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
576    }
577
578    /// Clears the visible bytes and the full backing array.
579    pub fn clear(&mut self) {
580        wipe_bytes(&mut self.bytes);
581        self.len = 0;
582    }
583
584    /// Clears bytes after the visible prefix.
585    pub fn clear_tail(&mut self) {
586        wipe_tail(&mut self.bytes, self.len);
587    }
588}
589
590impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
591    fn as_ref(&self) -> &[u8] {
592        self.as_bytes()
593    }
594}
595
596impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
597    /// Clones the visible decoded bytes into a second stack-backed buffer.
598    ///
599    /// Security note: cloning duplicates decoded bytes in memory. Both the
600    /// original and the clone must be dropped or explicitly cleared before the
601    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
602    /// compiler may also create temporary stack copies while performing the
603    /// copy; those intermediates are outside this crate's cleanup boundary. For
604    /// high-assurance applications, avoid cloning decoded key material and use
605    /// `SecretBuffer` for heap-owned secrets without a `Clone` implementation.
606    fn clone(&self) -> Self {
607        let mut output = Self::new();
608        output.bytes[..self.len].copy_from_slice(self.as_bytes());
609        output.len = self.len;
610        output
611    }
612}
613
614impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
615    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
616        formatter
617            .debug_struct("DecodedBuffer")
618            .field("bytes", &"<redacted>")
619            .field("len", &self.len)
620            .field("capacity", &CAP)
621            .finish()
622    }
623}
624
625impl<const CAP: usize> Default for DecodedBuffer<CAP> {
626    fn default() -> Self {
627        Self::new()
628    }
629}
630
631impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
632    fn drop(&mut self) {
633        self.clear();
634    }
635}
636
637impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
638    type Error = DecodeError;
639
640    /// Decodes strict standard padded Base64 into a stack-backed buffer.
641    ///
642    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`] when a
643    /// different alphabet, padding mode, or line-wrapping profile is required.
644    ///
645    /// # Security
646    ///
647    /// This idiomatic conversion uses the strict standard decoder, not the
648    /// constant-time-oriented decoder. It may branch or return early on
649    /// malformed input and reports exact [`DecodeError`] positions. For
650    /// secret-bearing tokens or key material where malformed-input timing
651    /// matters, use [`crate::ct::STANDARD`].`decode_buffer::<CAP>(input)`
652    /// instead.
653    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
654        STANDARD.decode_buffer(input)
655    }
656}
657
658impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
659    type Error = DecodeError;
660
661    /// Decodes a strict standard padded Base64 byte array into a stack-backed
662    /// buffer.
663    ///
664    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`] when a
665    /// different alphabet, padding mode, or line-wrapping profile is required.
666    ///
667    /// # Security
668    ///
669    /// This idiomatic conversion uses the strict standard decoder, not the
670    /// constant-time-oriented decoder. It may branch or return early on
671    /// malformed input and reports exact [`DecodeError`] positions. For
672    /// secret-bearing tokens or key material where malformed-input timing
673    /// matters, use [`crate::ct::STANDARD`].`decode_buffer::<CAP>(input)`
674    /// instead.
675    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
676        Self::try_from(&input[..])
677    }
678}
679
680impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
681    type Error = DecodeError;
682
683    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
684    ///
685    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`] when a
686    /// different alphabet, padding mode, or line-wrapping profile is required.
687    ///
688    /// # Security
689    ///
690    /// This idiomatic conversion uses the strict standard decoder, not the
691    /// constant-time-oriented decoder. It may branch or return early on
692    /// malformed input and reports exact [`DecodeError`] positions. For
693    /// secret-bearing tokens or key material where malformed-input timing
694    /// matters, use [`crate::ct::STANDARD`].`decode_buffer::<CAP>(input)`
695    /// instead.
696    fn try_from(input: &str) -> Result<Self, Self::Error> {
697        Self::try_from(input.as_bytes())
698    }
699}
700
701impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
702    type Err = DecodeError;
703
704    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
705    ///
706    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`] when a
707    /// different alphabet, padding mode, or line-wrapping profile is required.
708    ///
709    /// # Security
710    ///
711    /// This idiomatic conversion uses the strict standard decoder, not the
712    /// constant-time-oriented decoder. It may branch or return early on
713    /// malformed input and reports exact [`DecodeError`] positions. For
714    /// secret-bearing tokens or key material where malformed-input timing
715    /// matters, use [`crate::ct::STANDARD`].`decode_buffer::<CAP>(input)`
716    /// instead.
717    fn from_str(input: &str) -> Result<Self, Self::Err> {
718        Self::try_from(input)
719    }
720}
721
722/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
723///
724/// `SecretBuffer` is available with the `alloc` feature. It is intended for
725/// decoded keys, tokens, and other values that should not be accidentally
726/// logged. The buffer exposes contents only through explicit reveal methods.
727///
728/// Spare vector capacity is cleared when wrapping owned bytes. On drop,
729/// initialized bytes and vector spare capacity are cleared with the crate's
730/// internal best-effort wipe helpers. This is data-retention reduction, not a
731/// formal zeroization guarantee, and it cannot make claims about allocator
732/// behavior or historical copies outside the wrapper.
733///
734/// # Platform Memory Controls
735///
736/// `SecretBuffer` does not lock its allocation into physical memory. The OS
737/// may page its contents to disk, include them in hibernation images, or expose
738/// them through crash dumps. High-assurance deployments must combine
739/// `SecretBuffer` with platform memory-locking where available, encrypted or
740/// disabled swap, crash-dump suppression, and allocator isolation appropriate
741/// for their environment.
742///
743/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
744/// runtime JIT may still optimize or retain cleared bytes in ways this crate
745/// cannot control. `wasm32` builds fail closed by default; enable
746/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
747/// this limitation and applies its own memory strategy around owned secret
748/// buffers.
749#[cfg(feature = "alloc")]
750pub struct SecretBuffer {
751    bytes: alloc::vec::Vec<u8>,
752}
753
754/// Owned secret bytes extracted from [`SecretBuffer`].
755///
756/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
757/// at construction time, and best-effort full wipe on drop after a
758/// [`SecretBuffer`] is consumed for owned interop. Use
759/// [`Self::into_exposed_unprotected_vec_caller_must_zeroize`] only when a raw
760/// `Vec<u8>` is unavoidable and the caller will handle cleanup.
761#[cfg(feature = "alloc")]
762pub struct ExposedSecretVec {
763    bytes: alloc::vec::Vec<u8>,
764}
765
766#[cfg(feature = "alloc")]
767impl ExposedSecretVec {
768    /// Wraps an owned vector as exposed secret material.
769    #[must_use]
770    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
771        wipe_vec_spare_capacity(&mut bytes);
772        Self { bytes }
773    }
774
775    /// Returns the number of initialized secret bytes.
776    #[must_use]
777    pub fn len(&self) -> usize {
778        self.bytes.len()
779    }
780
781    /// Returns whether the buffer contains no initialized secret bytes.
782    #[must_use]
783    pub fn is_empty(&self) -> bool {
784        self.bytes.is_empty()
785    }
786
787    /// Reveals the secret bytes.
788    ///
789    /// This method is intentionally named to make secret access explicit at the
790    /// call site.
791    #[must_use]
792    pub fn expose_secret(&self) -> &[u8] {
793        &self.bytes
794    }
795
796    /// Reveals the secret bytes mutably.
797    ///
798    /// This method is intentionally named to make secret access explicit at the
799    /// call site.
800    #[must_use]
801    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
802        &mut self.bytes
803    }
804
805    /// Consumes the wrapper and returns a raw `Vec<u8>`.
806    ///
807    /// This is an unprotected escape hatch. The returned vector is no longer
808    /// redacted by formatting and will not be cleared by this crate on drop.
809    /// Callers must clear it with their own approved zeroization policy.
810    #[must_use = "caller must zeroize the returned Vec"]
811    pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
812        core::mem::take(&mut self.bytes)
813    }
814}
815
816#[cfg(feature = "alloc")]
817impl core::fmt::Debug for ExposedSecretVec {
818    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
819        formatter
820            .debug_struct("ExposedSecretVec")
821            .field("bytes", &"<redacted>")
822            .field("len", &self.len())
823            .finish()
824    }
825}
826
827#[cfg(feature = "alloc")]
828impl core::fmt::Display for ExposedSecretVec {
829    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
830        formatter.write_str("<redacted>")
831    }
832}
833
834#[cfg(feature = "alloc")]
835impl Drop for ExposedSecretVec {
836    fn drop(&mut self) {
837        wipe_vec_all(&mut self.bytes);
838    }
839}
840
841#[cfg(feature = "alloc")]
842struct WipeVecGuard {
843    bytes: alloc::vec::Vec<u8>,
844}
845
846#[cfg(feature = "alloc")]
847impl WipeVecGuard {
848    fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
849        Self { bytes }
850    }
851
852    fn into_validated_secret_string(mut self) -> alloc::string::String {
853        wipe_vec_spare_capacity(&mut self.bytes);
854        let bytes = core::mem::take(&mut self.bytes);
855        core::mem::forget(self);
856        string_from_validated_secret_bytes(bytes)
857    }
858}
859
860#[cfg(feature = "alloc")]
861impl Drop for WipeVecGuard {
862    fn drop(&mut self) {
863        wipe_vec_all(&mut self.bytes);
864    }
865}
866
867#[cfg(feature = "alloc")]
868impl AsRef<[u8]> for ExposedSecretVec {
869    fn as_ref(&self) -> &[u8] {
870        self.expose_secret()
871    }
872}
873
874#[cfg(feature = "alloc")]
875impl AsMut<[u8]> for ExposedSecretVec {
876    fn as_mut(&mut self) -> &mut [u8] {
877        self.expose_secret_mut()
878    }
879}
880
881/// Owned secret UTF-8 text extracted from [`SecretBuffer`].
882///
883/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
884/// at construction time, and best-effort full wipe on drop after a
885/// [`SecretBuffer`] is consumed for string interop. Use
886/// [`Self::into_exposed_unprotected_string_caller_must_zeroize`] only when a
887/// raw `String` is unavoidable and the caller will handle cleanup.
888#[cfg(feature = "alloc")]
889pub struct ExposedSecretString {
890    text: alloc::string::String,
891}
892
893#[cfg(feature = "alloc")]
894impl ExposedSecretString {
895    /// Wraps an owned UTF-8 string as exposed secret text.
896    #[must_use]
897    pub fn from_string(text: alloc::string::String) -> Self {
898        let mut bytes = text.into_bytes();
899        wipe_vec_spare_capacity(&mut bytes);
900        let text = string_from_validated_secret_bytes(bytes);
901        Self { text }
902    }
903
904    /// Returns the length of the secret text in bytes.
905    #[must_use]
906    pub fn len(&self) -> usize {
907        self.text.len()
908    }
909
910    /// Returns whether the secret text is empty.
911    #[must_use]
912    pub fn is_empty(&self) -> bool {
913        self.text.is_empty()
914    }
915
916    /// Reveals the secret text.
917    ///
918    /// This method is intentionally named to make secret access explicit at
919    /// the call site.
920    #[must_use]
921    pub fn expose_secret(&self) -> &str {
922        &self.text
923    }
924
925    /// Reveals the secret text as bytes.
926    ///
927    /// This method is intentionally named to make secret access explicit at
928    /// the call site.
929    #[must_use]
930    pub fn expose_secret_bytes(&self) -> &[u8] {
931        self.text.as_bytes()
932    }
933
934    /// Consumes the wrapper and returns a raw `String`.
935    ///
936    /// This is an unprotected escape hatch. The returned string is no longer
937    /// redacted by formatting and will not be cleared by this crate on drop.
938    /// Callers must clear it with their own approved zeroization policy.
939    #[must_use = "caller must zeroize the returned String"]
940    pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
941        core::mem::take(&mut self.text)
942    }
943}
944
945#[cfg(feature = "alloc")]
946impl core::fmt::Debug for ExposedSecretString {
947    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
948        formatter
949            .debug_struct("ExposedSecretString")
950            .field("text", &"<redacted>")
951            .field("len", &self.len())
952            .finish()
953    }
954}
955
956#[cfg(feature = "alloc")]
957impl core::fmt::Display for ExposedSecretString {
958    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
959        formatter.write_str("<redacted>")
960    }
961}
962
963#[cfg(feature = "alloc")]
964impl Drop for ExposedSecretString {
965    fn drop(&mut self) {
966        let mut bytes = core::mem::take(&mut self.text).into_bytes();
967        wipe_vec_all(&mut bytes);
968    }
969}
970
971#[cfg(feature = "alloc")]
972impl AsRef<str> for ExposedSecretString {
973    fn as_ref(&self) -> &str {
974        self.expose_secret()
975    }
976}
977
978#[cfg(feature = "alloc")]
979impl SecretBuffer {
980    /// Wraps an existing vector as sensitive material.
981    #[must_use]
982    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
983        wipe_vec_spare_capacity(&mut bytes);
984        Self { bytes }
985    }
986
987    /// Copies a slice into an owned sensitive buffer.
988    #[must_use]
989    pub fn from_slice(bytes: &[u8]) -> Self {
990        Self::from_vec(bytes.to_vec())
991    }
992
993    /// Returns the number of initialized secret bytes.
994    #[must_use]
995    pub fn len(&self) -> usize {
996        self.bytes.len()
997    }
998
999    /// Returns whether the buffer contains no initialized secret bytes.
1000    #[must_use]
1001    pub fn is_empty(&self) -> bool {
1002        self.bytes.is_empty()
1003    }
1004
1005    /// Reveals the secret bytes.
1006    ///
1007    /// This method is intentionally named to make secret access explicit at the
1008    /// call site.
1009    #[must_use]
1010    pub fn expose_secret(&self) -> &[u8] {
1011        &self.bytes
1012    }
1013
1014    /// Reveals the secret bytes as UTF-8 text.
1015    ///
1016    /// This method is intentionally named to make secret access explicit at the
1017    /// call site. Secret material may be arbitrary binary data, so this method
1018    /// is fallible.
1019    pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1020        core::str::from_utf8(self.expose_secret())
1021    }
1022
1023    /// Reveals the secret bytes mutably.
1024    ///
1025    /// This method is intentionally named to make secret access explicit at the
1026    /// call site.
1027    #[must_use]
1028    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1029        &mut self.bytes
1030    }
1031
1032    /// Consumes the wrapper and returns owned secret bytes.
1033    ///
1034    /// This is an explicit escape hatch for interop with APIs that require an
1035    /// owned vector-like value. The returned [`ExposedSecretVec`] remains
1036    /// redacted by formatting and clears its vector on drop.
1037    #[must_use]
1038    pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
1039        ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
1040    }
1041
1042    /// Consumes the wrapper and returns the owned secret bytes as UTF-8 text.
1043    ///
1044    /// This is an explicit escape hatch for interop with APIs that require an
1045    /// owned string-like value. The returned [`ExposedSecretString`] remains
1046    /// redacted by formatting and clears its heap allocation on drop.
1047    ///
1048    /// If the secret bytes are not valid UTF-8, the original redacted wrapper
1049    /// is returned unchanged.
1050    #[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
1051    pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
1052        if core::str::from_utf8(self.expose_secret()).is_err() {
1053            return Err(self);
1054        }
1055
1056        // Keep the bytes behind a wiping guard until the final infallible
1057        // ownership transfer into `String`.
1058        let mut exposed = self.into_exposed_vec();
1059        let guard = WipeVecGuard::from_vec(core::mem::take(&mut exposed.bytes));
1060        drop(exposed);
1061        Ok(ExposedSecretString::from_string(
1062            guard.into_validated_secret_string(),
1063        ))
1064    }
1065
1066    /// Compares this secret to `other` without short-circuiting on the first
1067    /// differing byte.
1068    ///
1069    /// Length and the final equality result remain public. Different lengths
1070    /// return `false` immediately; use this helper only when the compared
1071    /// lengths are public protocol facts or have been normalized by the
1072    /// caller. For equal-length inputs, this helper scans every byte before
1073    /// returning. It is constant-time-oriented best effort, not a formal
1074    /// cryptographic constant-time guarantee. This comparison is deliberately
1075    /// explicit: redacted buffer types do not implement [`PartialEq`] because
1076    /// `==` would make a best-effort helper look like a formal token/MAC
1077    /// comparison primitive.
1078    ///
1079    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
1080    /// authentication-secret comparison primitive in high-assurance systems.
1081    /// Applications that can admit dependencies should use a reviewed
1082    /// constant-time comparison primitive, such as `subtle`, at the protocol
1083    /// boundary.
1084    #[doc(alias = "constant_time_eq")]
1085    #[must_use]
1086    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1087        constant_time_eq_public_len(self.expose_secret(), other)
1088    }
1089
1090    /// Clears the initialized bytes and makes the buffer empty.
1091    pub fn clear(&mut self) {
1092        wipe_vec_all(&mut self.bytes);
1093        self.bytes.clear();
1094    }
1095}
1096
1097#[cfg(feature = "alloc")]
1098impl core::fmt::Debug for SecretBuffer {
1099    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1100        formatter
1101            .debug_struct("SecretBuffer")
1102            .field("bytes", &"<redacted>")
1103            .field("len", &self.len())
1104            .finish()
1105    }
1106}
1107
1108#[cfg(feature = "alloc")]
1109impl core::fmt::Display for SecretBuffer {
1110    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1111        formatter.write_str("<redacted>")
1112    }
1113}
1114
1115#[cfg(feature = "alloc")]
1116impl Drop for SecretBuffer {
1117    fn drop(&mut self) {
1118        wipe_vec_all(&mut self.bytes);
1119    }
1120}
1121
1122#[cfg(feature = "alloc")]
1123impl From<alloc::vec::Vec<u8>> for SecretBuffer {
1124    /// Wraps an owned vector as sensitive material.
1125    ///
1126    /// Spare capacity is cleared immediately before the vector is stored.
1127    /// Use [`SecretBuffer::from_slice`] when the source data is borrowed.
1128    fn from(bytes: alloc::vec::Vec<u8>) -> Self {
1129        Self::from_vec(bytes)
1130    }
1131}
1132
1133#[cfg(feature = "alloc")]
1134impl From<alloc::string::String> for SecretBuffer {
1135    /// Wraps an owned UTF-8 string as sensitive material.
1136    ///
1137    /// The string is consumed without copying its initialized bytes. Spare
1138    /// vector capacity is cleared immediately before the bytes are stored.
1139    fn from(text: alloc::string::String) -> Self {
1140        Self::from_vec(text.into_bytes())
1141    }
1142}
1143
1144#[cfg(feature = "alloc")]
1145impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
1146    /// Copies visible encoded bytes from a stack-backed buffer into an owned
1147    /// redacted buffer.
1148    ///
1149    /// The consumed stack-backed buffer clears its backing array when it is
1150    /// dropped at the end of the conversion.
1151    fn from(buffer: EncodedBuffer<CAP>) -> Self {
1152        Self::from_slice(buffer.as_bytes())
1153    }
1154}
1155
1156#[cfg(feature = "alloc")]
1157impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
1158    /// Copies visible decoded bytes from a stack-backed buffer into an owned
1159    /// redacted buffer.
1160    ///
1161    /// The consumed stack-backed buffer clears its backing array when it is
1162    /// dropped at the end of the conversion.
1163    fn from(buffer: DecodedBuffer<CAP>) -> Self {
1164        Self::from_slice(buffer.as_bytes())
1165    }
1166}
1167
1168#[cfg(feature = "alloc")]
1169impl TryFrom<&[u8]> for SecretBuffer {
1170    type Error = DecodeError;
1171
1172    /// Decodes strict standard padded Base64 into a redacted owned buffer.
1173    ///
1174    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`] when a
1175    /// different alphabet, padding mode, or line-wrapping profile is required.
1176    ///
1177    /// # Security
1178    ///
1179    /// This idiomatic conversion uses the strict standard decoder, not the
1180    /// constant-time-oriented decoder. It may branch or return early on
1181    /// malformed input and reports exact [`DecodeError`] positions. For
1182    /// secret-bearing tokens or key material where malformed-input timing
1183    /// matters, use [`crate::ct::STANDARD`].`decode_buffer(input)` or
1184    /// [`crate::ct::STANDARD`].`decode_slice_staged_clear_tail(...)` and then
1185    /// wrap the successful output in `SecretBuffer`.
1186    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1187        STANDARD.decode_secret(input)
1188    }
1189}
1190
1191#[cfg(feature = "alloc")]
1192impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
1193    type Error = DecodeError;
1194
1195    /// Decodes a strict standard padded Base64 byte array into a redacted
1196    /// owned buffer.
1197    ///
1198    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`] when a
1199    /// different alphabet, padding mode, or line-wrapping profile is required.
1200    ///
1201    /// # Security
1202    ///
1203    /// This idiomatic conversion uses the strict standard decoder, not the
1204    /// constant-time-oriented decoder. It may branch or return early on
1205    /// malformed input and reports exact [`DecodeError`] positions. For
1206    /// secret-bearing tokens or key material where malformed-input timing
1207    /// matters, use [`crate::ct::STANDARD`].`decode_buffer(input)` or
1208    /// [`crate::ct::STANDARD`].`decode_slice_staged_clear_tail(...)` and then
1209    /// wrap the successful output in `SecretBuffer`.
1210    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1211        Self::try_from(&input[..])
1212    }
1213}
1214
1215#[cfg(feature = "alloc")]
1216impl TryFrom<&str> for SecretBuffer {
1217    type Error = DecodeError;
1218
1219    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
1220    ///
1221    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`] when a
1222    /// different alphabet, padding mode, or line-wrapping profile is required.
1223    ///
1224    /// # Security
1225    ///
1226    /// This idiomatic conversion uses the strict standard decoder, not the
1227    /// constant-time-oriented decoder. It may branch or return early on
1228    /// malformed input and reports exact [`DecodeError`] positions. For
1229    /// secret-bearing tokens or key material where malformed-input timing
1230    /// matters, use [`crate::ct::STANDARD`].`decode_buffer(input)` or
1231    /// [`crate::ct::STANDARD`].`decode_slice_staged_clear_tail(...)` and then
1232    /// wrap the successful output in `SecretBuffer`.
1233    fn try_from(input: &str) -> Result<Self, Self::Error> {
1234        Self::try_from(input.as_bytes())
1235    }
1236}
1237
1238#[cfg(feature = "alloc")]
1239impl core::str::FromStr for SecretBuffer {
1240    type Err = DecodeError;
1241
1242    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
1243    ///
1244    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`] when a
1245    /// different alphabet, padding mode, or line-wrapping profile is required.
1246    ///
1247    /// # Security
1248    ///
1249    /// This idiomatic conversion uses the strict standard decoder, not the
1250    /// constant-time-oriented decoder. It may branch or return early on
1251    /// malformed input and reports exact [`DecodeError`] positions. For
1252    /// secret-bearing tokens or key material where malformed-input timing
1253    /// matters, use [`crate::ct::STANDARD`].`decode_buffer(input)` or
1254    /// [`crate::ct::STANDARD`].`decode_slice_staged_clear_tail(...)` and then
1255    /// wrap the successful output in `SecretBuffer`.
1256    fn from_str(input: &str) -> Result<Self, Self::Err> {
1257        Self::try_from(input)
1258    }
1259}
1260
1261#[cfg(feature = "alloc")]
1262fn string_from_validated_secret_bytes(bytes: Vec<u8>) -> String {
1263    match String::from_utf8(bytes) {
1264        Ok(string) => string,
1265        Err(error) => {
1266            let mut bytes = error.into_bytes();
1267            wipe_vec_all(&mut bytes);
1268            String::new()
1269        }
1270    }
1271}