base64_ng/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(clippy::all)]
5#![deny(clippy::pedantic)]
6#![allow(clippy::missing_errors_doc)]
7
8//! `base64-ng` is a `no_std`-first Base64 encoder and decoder.
9//!
10//! The core API provides strict RFC 4648 behavior, caller-owned output
11//! buffers, and an audited scalar fallback. The `2.0` line admits selected
12//! SIMD encode and strict decode acceleration for standard-family alphabets.
13//! Any accelerated backend must match the scalar module byte-for-byte and pass
14//! the documented admission evidence before dispatch can select it.
15//! [`STANDARD`] and [`URL_SAFE`] require canonical padding; the explicitly
16//! named [`STANDARD_NO_PAD`] and [`URL_SAFE_NO_PAD`] engines reject padding.
17//! Strict decode rejects whitespace, mixed alphabets, malformed input, and
18//! non-canonical unused trailing bits. Legacy whitespace and wrapped line
19//! handling remain separately named opt-in policies.
20//!
21//! # 2.0 API
22//!
23//! The 2.0 surface is available through [`Base64`] and the four
24//! explicitly named `STRICT_*` presets. Its one-shot slice methods validate
25//! and size completely before writing, so every returned error leaves the
26//! destination unchanged. The retained 1.x API remains available as the
27//! compatibility surface documented in the migration guide. Exact const
28//! transforms and bounded ordinary storage are available through the same
29//! validated codec values. The optional `secrets` capability publishes
30//! separate redacted storage and bounded constant-time-oriented codecs under
31//! `base64_ng::secret`.
32//! Finite-buffer in-place transforms use explicit input lengths, while secret
33//! in-place decode requires byte-disjoint private staging.
34//! Allocation-free display, exact counted sinks, rollback-capable append, and
35//! synthesized encoded chunk iteration share the same validated codec values.
36//! Exact WHATWG decoding is separately named [`web::FORGIVING`]. Expert
37//! padding-indifferent and noncanonical-bit policies live under [`compat`]
38//! and never become strict or secret defaults. Accurately scoped body and
39//! alphabet presets include [`MIME_BODY_STRICT`], [`PEM_BODY_LF`],
40//! [`BCRYPT_ALPHABET_NO_PAD`], and [`PBKDF2_ALPHABET_NO_PAD`]. The sole generic
41//! legacy transport-whitespace policy is [`legacy::ASCII_WHITESPACE`].
42//!
43//! # Examples
44//!
45//! Encode and decode with caller-owned buffers:
46//!
47//! ```
48//! use base64_ng::{STANDARD, checked_encoded_len};
49//!
50//! let input = b"hello";
51//! const ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {
52//! Some(len) => len,
53//! None => panic!("encoded length overflow"),
54//! };
55//! let mut encoded = [0u8; ENCODED_CAPACITY];
56//! let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
57//! assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
58//!
59//! let mut decoded = [0u8; 5];
60//! let decoded_len = STANDARD.decode_slice(&encoded, &mut decoded).unwrap();
61//! assert_eq!(&decoded[..decoded_len], input);
62//! ```
63//!
64//! Use the URL-safe no-padding engine:
65//!
66//! ```
67//! use base64_ng::URL_SAFE_NO_PAD;
68//!
69//! let mut encoded = [0u8; 3];
70//! let encoded_len = URL_SAFE_NO_PAD.encode_slice(b"\xfb\xff", &mut encoded).unwrap();
71//! assert_eq!(&encoded[..encoded_len], b"-_8");
72//! ```
73//!
74//! # Sensitive Decode Policy
75//!
76//! The default engines such as [`STANDARD`] and [`URL_SAFE_NO_PAD`] are strict
77//! scalar encoders/decoders with localized diagnostics. They are not
78//! constant-time token validators or key-material decoders: strict decode and
79//! validation may branch or return early based on malformed input, and strict
80//! [`DecodeError`] values can include input-derived bytes and indexes. Do not
81//! log strict decode errors verbatim for secret-bearing input; log
82//! [`DecodeError::kind`] instead. Use [`ct::STANDARD`],
83//! [`crate::ct::URL_SAFE_NO_PAD`], or [`Engine::ct_decoder`] for secret-bearing
84//! payloads where decode timing posture matters more than exact error indexes.
85//!
86//! Recommended heap-owning pattern for secret-bearing standard Base64:
87//!
88//! ```
89//! # #[cfg(feature = "alloc")]
90//! # {
91//! use base64_ng::ct;
92//!
93//! let expected = b"session-key";
94//! let decoded = ct::STANDARD.decode_secret(b"c2Vzc2lvbi1rZXk=").unwrap();
95//!
96//! assert!(decoded.constant_time_eq_public_len(expected));
97//! # }
98//! ```
99//!
100//! For shared-memory, enclave-adjacent, HSM-style, or multi-principal
101//! deployments where even transient writes into caller-owned output are
102//! unacceptable, use [`ct::CtEngine::decode_slice_staged_clear_tail`] with a
103//! private staging buffer.
104//! CT behavior is best-effort and build-profile specific. Link-Time
105//! Optimization can change generated code shape across crate boundaries, so
106//! high-assurance deployments must rerun the dudect and generated-assembly
107//! evidence scripts for their exact compiler, target, feature set, and release
108//! profile before treating CT decode as acceptable.
109//!
110//! # Zeroization Caveat
111//!
112//! Cleanup APIs and redacted buffers use dependency-free best-effort wiping:
113//! byte-wise volatile zero writes followed by an architecture-gated inline
114//! assembly barrier plus a hardware store-ordering fence where stable Rust
115//! supports it, and a compiler fence on all targets. This resists common
116//! compiler dead-store elimination and orders the issued zero stores on native
117//! supported architectures, but it is not a formal zeroization guarantee and
118//! cannot clear historical copies, registers, cache lines, write buffers, swap,
119//! hibernation images, core dumps, cold-boot remanence, or OS-level memory
120//! snapshots.
121//! High-assurance applications should apply their own approved zeroization
122//! policy to caller-owned buffers at the protocol boundary. Ordinary
123//! public-data codecs do not require a wipe-policy opt-in. When the `secrets`
124//! capability is enabled, architectures without a native wipe barrier fail
125//! closed unless `allow-compiler-fence-only-wipe` is enabled after platform
126//! review. On `wasm32`, secret cleanup is compiler-fence-only and cannot
127//! constrain downstream runtime JITs, so `secrets` builds require the explicit
128//! `allow-wasm32-best-effort-wipe` acceptance feature.
129
130#[cfg(feature = "alloc")]
131extern crate alloc;
132
133mod alphabet;
134mod buffers;
135mod build_policy;
136mod cleanup;
137pub mod ct;
138mod decode_backend;
139mod encode_backend;
140mod engine;
141mod errors;
142mod length;
143pub mod prelude;
144mod profiles;
145mod scalar;
146mod scalar_encode_in_place;
147mod v2;
148
149#[cfg(feature = "alloc")]
150pub use v2::Base64String;
151#[cfg(feature = "secrets")]
152pub use v2::assurance;
153#[cfg(feature = "secrets")]
154pub use v2::secret;
155pub use v2::{
156 AssuranceClass, Atomicity, BCRYPT_ALPHABET_NO_PAD, BINHEX_ALPHABET, BackendClass, BackendFault,
157 BackendHealthSnapshot, BackendHealthState, BackendInitializationReport, Base64, BodyCodec,
158 BodyLineEnding, BodyWrap, BodyWrapError, BufferLengthError, CRYPT_ALPHABET_NO_PAD, Codec,
159 CodecBuilder, CodecBuilderError, CodecSettings, ConstTransformError, CountedSink,
160 CountedWriteError, DecodePadding, DecodedArray, DecoderState, EncodePadding, EncodedArray,
161 EncodedChunk, EncodedChunks, EncodedDisplay, EncoderState, Failure, FormatWriteError,
162 IMAP_MUTF7_ALPHABET_NO_PAD, InPlaceError, InputError, InputErrorKind, MIME_BODY_STRICT,
163 OneShotError, OperationError, OutputFull, PBKDF2_ALPHABET_NO_PAD, PEM_BODY_CRLF, PEM_BODY_LF,
164 Progress, ProtocolScope, RuntimeSpec, STRICT_STANDARD_PADDED, STRICT_STANDARD_UNPADDED,
165 STRICT_URL_SAFE_PADDED, STRICT_URL_SAFE_UNPADDED, Status, Step, StrictStandardPadded,
166 StrictStandardUnpadded, StrictUrlSafePadded, StrictUrlSafeUnpadded, TerminalError,
167 TrailingBits, ValidatedAlphabet, ValidatedAlphabetError, initialize_backends,
168};
169pub use v2::{compat, legacy, web};
170mod wrap;
171
172pub use alphabet::{
173 Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
174 validate_alphabet,
175};
176pub(crate) use alphabet::{RuntimeAlphabetMapperFor, encode_base64_value};
177pub use buffers::{DecodedBuffer, EncodedBuffer, ExposedDecodedArray, ExposedEncodedArray};
178#[cfg(feature = "alloc")]
179pub use buffers::{ExposedSecretString, ExposedSecretVec, SecretBuffer};
180pub(crate) use cleanup::{wipe_bytes, wipe_tail};
181#[cfg(feature = "alloc")]
182pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
183pub(crate) use ct::{
184 constant_time_eq_fixed_width_array, constant_time_eq_public_len, ct_mask_eq_u8, ct_mask_lt_u8,
185};
186#[cfg(feature = "secrets")]
187pub(crate) use ct::{ct_accumulate_u8, ct_error_gate_barrier, ct_mask_nonzero_u8};
188#[cfg(test)]
189pub(crate) use ct::{ct_padded_final_quantum, report_ct_error};
190pub use engine::Engine;
191pub use errors::{DecodeError, DecodeErrorKind, EncodeError};
192pub use length::{
193 LineEnding, LineWrap, checked_encoded_len, checked_wrapped_encoded_len, decoded_capacity,
194 decoded_len, encoded_len, wrapped_encoded_len,
195};
196pub(crate) use length::{decoded_len_padded, decoded_len_unpadded};
197pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
198#[cfg(any(test, kani))]
199pub(crate) use scalar::decode_chunk;
200#[cfg(kani)]
201pub(crate) use scalar::{decode_byte, decode_tail_unpadded};
202pub(crate) use scalar::{read_quad, validate_chunk, validate_decode, validate_tail_unpadded};
203pub(crate) use wrap::{
204 compact_wrapped_input, is_legacy_whitespace, validate_legacy_decode, validate_wrapped_decode,
205 write_wrapped_byte, write_wrapped_bytes,
206};
207
208#[cfg(all(base64_ng_perf_evidence, feature = "std"))]
209#[doc(hidden)]
210pub mod perf_evidence;
211
212#[cfg(feature = "simd")]
213mod simd;
214#[cfg(feature = "simd")]
215pub use simd::StaticBackendToken;
216
217/// Runtime backend reporting for security-sensitive deployments.
218///
219/// This module exposes backend posture so callers can log, assert, or audit
220/// whether execution is scalar-only, using an admitted encode backend, or
221/// merely detecting future SIMD candidates.
222pub mod runtime;
223
224#[cfg(feature = "stream")]
225pub mod stream;
226
227/// Best-effort dependency-free wipe for caller-owned byte slices.
228///
229/// This is the same hardened cleanup primitive used internally by the core
230/// crate: byte-wise volatile zero writes followed by the crate's
231/// architecture-gated wipe barrier and a compiler fence. It is exposed so
232/// companion crates and integrations can reuse the audited cleanup boundary
233/// without duplicating unsafe code.
234///
235/// This is not a formal zeroization guarantee. It cannot clear historical
236/// copies, registers, caches, swap, hibernation images, core dumps, or
237/// OS-level memory snapshots. High-assurance applications still need their own
238/// platform-approved memory hygiene controls.
239pub fn secure_wipe(bytes: &mut [u8]) {
240 cleanup::wipe_bytes(bytes);
241}
242
243/// Standard Base64 engine with padding.
244///
245/// This default strict engine is not a constant-time token validator or
246/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
247/// matching constant-time-oriented decoder when timing posture matters.
248#[doc(alias = "ct")]
249#[doc(alias = "constant_time")]
250#[doc(alias = "sensitive")]
251pub const STANDARD: Engine<Standard, true> = Engine::new();
252
253/// Standard Base64 engine without padding.
254///
255/// This default strict engine is not a constant-time token validator or
256/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
257/// for the matching constant-time-oriented decoder when timing posture
258/// matters.
259#[doc(alias = "ct")]
260#[doc(alias = "constant_time")]
261#[doc(alias = "sensitive")]
262pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
263
264/// URL-safe Base64 engine with padding.
265///
266/// This default strict engine is not a constant-time token validator or
267/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
268/// matching constant-time-oriented decoder when timing posture matters.
269#[doc(alias = "ct")]
270#[doc(alias = "constant_time")]
271#[doc(alias = "sensitive")]
272pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
273
274/// URL-safe Base64 engine without padding.
275///
276/// This default strict engine is not a constant-time token validator or
277/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
278/// for the matching constant-time-oriented decoder when timing posture
279/// matters.
280#[doc(alias = "ct")]
281#[doc(alias = "constant_time")]
282#[doc(alias = "sensitive")]
283pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
284
285/// bcrypt-style Base64 engine without padding.
286///
287/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
288/// It does not parse complete bcrypt password-hash strings. This default strict
289/// engine is not a constant-time token validator or key-material decoder; use
290/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
291/// timing posture matters.
292#[doc(alias = "ct")]
293#[doc(alias = "constant_time")]
294#[doc(alias = "sensitive")]
295pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
296
297/// Unix `crypt(3)`-style Base64 engine without padding.
298///
299/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
300/// packing. It does not parse complete password-hash strings. This default
301/// strict engine is not a constant-time token validator or key-material
302/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
303/// decoder when timing posture matters.
304#[doc(alias = "ct")]
305#[doc(alias = "constant_time")]
306#[doc(alias = "sensitive")]
307pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
308
309/// Encodes `input` as strict standard padded Base64.
310///
311/// This is a convenience wrapper around [`Engine::encode_string`] on
312/// [`STANDARD`] for callers migrating from simpler Base64 APIs. It requires
313/// the `alloc` feature because it returns an owned string.
314///
315/// # Examples
316///
317/// ```
318/// assert_eq!(base64_ng::encode(b"hello").unwrap(), "aGVsbG8=");
319/// ```
320#[cfg(feature = "alloc")]
321pub fn encode(input: &[u8]) -> Result<alloc::string::String, EncodeError> {
322 STANDARD.encode_string(input)
323}
324
325/// Encodes `input` as strict standard padded Base64.
326///
327/// This is a convenience wrapper around [`Engine::encode_string_infallible`] on
328/// [`STANDARD`] for ordinary byte-to-Base64 paths where encoding failure would
329/// indicate an internal length/allocation invariant failure rather than invalid
330/// input.
331///
332/// Prefer [`encode`] when handling untrusted length metadata, constrained
333/// allocation environments, or code paths that must return a recoverable error
334/// instead of panicking.
335///
336/// # Panics
337///
338/// Panics if [`encode`] returns an error. This includes encoded length
339/// overflow; on 32-bit targets, inputs larger than roughly 1.5 GiB can
340/// overflow the encoded length. For attacker-controlled or externally sized
341/// buffers, use [`encode`], which returns a recoverable
342/// [`EncodeError::LengthOverflow`].
343///
344/// # Examples
345///
346/// ```
347/// assert_eq!(base64_ng::encode_infallible(b"hello"), "aGVsbG8=");
348/// ```
349#[cfg(feature = "alloc")]
350#[must_use]
351pub fn encode_infallible(input: &[u8]) -> alloc::string::String {
352 STANDARD.encode_string_infallible(input)
353}
354
355/// Decodes strict standard padded Base64 into an owned byte vector.
356///
357/// This is a convenience wrapper around [`Engine::decode_vec`] on
358/// [`STANDARD`].
359/// It uses the normal strict decoder, not the [`crate::ct`] module, and may
360/// branch or return early on malformed input. For secret-bearing payloads where
361/// malformed-input timing matters, use
362/// [`crate::ct::CtEngine::decode_secret`] through [`crate::ct::STANDARD`]
363/// instead.
364///
365/// # Examples
366///
367/// ```
368/// assert_eq!(base64_ng::decode("aGVsbG8=").unwrap(), b"hello");
369/// ```
370#[cfg(feature = "alloc")]
371#[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
372pub fn decode(input: impl AsRef<[u8]>) -> Result<alloc::vec::Vec<u8>, DecodeError> {
373 STANDARD.decode_vec(input.as_ref())
374}
375
376/// Compares two fixed-width byte arrays without a length-mismatch branch.
377///
378/// Use this helper when the value length itself should not be represented as a
379/// timing-distinct branch in the comparison API. The array length `N` is a
380/// compile-time public type fact, and the helper scans exactly `N` bytes before
381/// returning. The final equality result remains public. This is still a
382/// dependency-free, constant-time-oriented best-effort helper, not a formally
383/// verified cryptographic comparison primitive.
384///
385/// # Examples
386///
387/// ```
388/// use base64_ng::constant_time_eq_fixed_width;
389///
390/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
391/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
392/// ```
393#[must_use]
394pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
395 constant_time_eq_fixed_width_array(left, right)
396}
397
398/// Compares two byte slices with a public length-mismatch branch.
399///
400/// Equal-length inputs are scanned fully before returning. Different lengths
401/// return `false` immediately because length is treated as public. This is a
402/// dependency-free, constant-time-oriented best-effort helper, not a formally
403/// verified cryptographic MAC, password, or bearer-token comparison primitive.
404///
405/// # Security
406///
407/// This helper is intended to avoid ordinary early-exit equality on values
408/// whose length is public. It is not a formal constant-time guarantee and
409/// should not be the sole primitive admitted at MAC, password, or bearer-token
410/// protocol boundaries in high-assurance systems. Use a reviewed comparison
411/// primitive at that boundary when your dependency policy allows one.
412///
413/// # Examples
414///
415/// ```
416/// assert!(base64_ng::constant_time_eq(b"token", b"token"));
417/// assert!(!base64_ng::constant_time_eq(b"token", b"Token"));
418/// assert!(!base64_ng::constant_time_eq(b"token", b"token2"));
419/// ```
420#[must_use]
421pub fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
422 constant_time_eq_public_len(left, right)
423}
424
425/// Clears caller-owned bytes with this crate's best-effort cleanup primitive.
426///
427/// This helper exposes the same dependency-free cleanup path used by
428/// `base64-ng` stack-backed buffers: byte-wise volatile zero writes followed by
429/// the target-specific wipe barrier documented in the crate-level
430/// zeroization caveat. It is intended for companion crates and applications
431/// that need a small reviewed cleanup primitive without pulling cleanup logic
432/// into generated code.
433///
434/// # Security
435///
436/// This is data-retention reduction, not a formal zeroization guarantee. It
437/// cannot clear historical copies, registers, cache lines, swap, hibernation
438/// images, core dumps, or platform snapshots. High-assurance deployments
439/// should pair it with their approved platform memory controls.
440pub fn clear_bytes(bytes: &mut [u8]) {
441 wipe_bytes(bytes);
442}
443
444#[cfg(all(kani, feature = "secrets", base64_ng_kani_advanced))]
445mod kani_assurance_proofs;
446#[cfg(kani)]
447mod kani_in_place_proofs;
448#[cfg(kani)]
449mod kani_proofs;
450#[cfg(all(kani, feature = "secrets"))]
451mod kani_secret_encode_proofs;
452#[cfg(all(kani, feature = "secrets"))]
453mod kani_secret_proofs;
454#[cfg(kani)]
455mod kani_simd_model_proofs;
456#[cfg(kani)]
457mod kani_v2_core_proofs;
458
459#[cfg(test)]
460mod decode_surface_tests;
461#[cfg(test)]
462mod encode_surface_tests;
463#[cfg(test)]
464mod non_standard_surface_tests;
465#[cfg(test)]
466mod tests;