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