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-style behavior, caller-owned output
11//! buffers, and an audited scalar fallback. The `1.3.x` 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//!
16//! # Examples
17//!
18//! Encode and decode with caller-owned buffers:
19//!
20//! ```
21//! use base64_ng::{STANDARD, checked_encoded_len};
22//!
23//! let input = b"hello";
24//! const ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {
25//! Some(len) => len,
26//! None => panic!("encoded length overflow"),
27//! };
28//! let mut encoded = [0u8; ENCODED_CAPACITY];
29//! let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
30//! assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
31//!
32//! let mut decoded = [0u8; 5];
33//! let decoded_len = STANDARD.decode_slice(&encoded, &mut decoded).unwrap();
34//! assert_eq!(&decoded[..decoded_len], input);
35//! ```
36//!
37//! Use the URL-safe no-padding engine:
38//!
39//! ```
40//! use base64_ng::URL_SAFE_NO_PAD;
41//!
42//! let mut encoded = [0u8; 3];
43//! let encoded_len = URL_SAFE_NO_PAD.encode_slice(b"\xfb\xff", &mut encoded).unwrap();
44//! assert_eq!(&encoded[..encoded_len], b"-_8");
45//! ```
46//!
47//! # Sensitive Decode Policy
48//!
49//! The default engines such as [`STANDARD`] and [`URL_SAFE_NO_PAD`] are strict
50//! scalar encoders/decoders with localized diagnostics. They are not
51//! constant-time token validators or key-material decoders: strict decode and
52//! validation may branch or return early based on malformed input, and strict
53//! [`DecodeError`] values can include input-derived bytes and indexes. Do not
54//! log strict decode errors verbatim for secret-bearing input; log
55//! [`DecodeError::kind`] instead. Use [`ct::STANDARD`],
56//! [`crate::ct::URL_SAFE_NO_PAD`], or [`Engine::ct_decoder`] for secret-bearing
57//! payloads where decode timing posture matters more than exact error indexes.
58//!
59//! Recommended heap-owning pattern for secret-bearing standard Base64:
60//!
61//! ```
62//! # #[cfg(feature = "alloc")]
63//! # {
64//! use base64_ng::ct;
65//!
66//! let expected = b"session-key";
67//! let decoded = ct::STANDARD.decode_secret(b"c2Vzc2lvbi1rZXk=").unwrap();
68//!
69//! assert!(decoded.constant_time_eq_public_len(expected));
70//! # }
71//! ```
72//!
73//! For shared-memory, enclave-adjacent, HSM-style, or multi-principal
74//! deployments where even transient writes into caller-owned output are
75//! unacceptable, use [`ct::CtEngine::decode_slice_staged_clear_tail`] with a
76//! private staging buffer.
77//! CT behavior is best-effort and build-profile specific. Link-Time
78//! Optimization can change generated code shape across crate boundaries, so
79//! high-assurance deployments must rerun the dudect and generated-assembly
80//! evidence scripts for their exact compiler, target, feature set, and release
81//! profile before treating CT decode as acceptable.
82//!
83//! # Zeroization Caveat
84//!
85//! Cleanup APIs and redacted buffers use dependency-free best-effort wiping:
86//! byte-wise volatile zero writes followed by an architecture-gated inline
87//! assembly barrier plus a hardware store-ordering fence where stable Rust
88//! supports it, and a compiler fence on all targets. This resists common
89//! compiler dead-store elimination and orders the issued zero stores on native
90//! supported architectures, but it is not a formal zeroization guarantee and
91//! cannot clear historical copies, registers, cache lines, write buffers, swap,
92//! hibernation images, core dumps, cold-boot remanence, or OS-level memory
93//! snapshots.
94//! High-assurance applications should apply their own approved zeroization
95//! policy to caller-owned buffers at the protocol boundary. Architectures
96//! without a native wipe barrier fail closed by default unless
97//! `allow-compiler-fence-only-wipe` is enabled after platform review. On
98//! `wasm32`, the wipe barrier is compiler-fence-only and cannot constrain
99//! downstream wasm runtime JITs. For that reason, `wasm32` builds fail closed
100//! by default. Enable `allow-wasm32-best-effort-wipe` only when the deployment
101//! explicitly accepts compiler-fence-only cleanup and applies its own memory
102//! strategy.
103
104#[cfg(feature = "alloc")]
105extern crate alloc;
106
107#[cfg(all(target_arch = "wasm32", not(feature = "allow-wasm32-best-effort-wipe")))]
108compile_error!(
109 "base64-ng: wasm32 builds use a compiler-fence-only wipe barrier that cannot \
110 constrain downstream wasm runtime JITs. Enable \
111 `allow-wasm32-best-effort-wipe` to accept this limitation and use \
112 caller-owned, platform-approved zeroization for high-assurance wasm deployments."
113);
114
115#[cfg(all(base64_ng_require_high_assurance, feature = "simd"))]
116compile_error!(
117 "base64-ng: base64_ng_require_high_assurance and the `simd` feature are \
118 mutually exclusive. Disable `simd` or remove the high-assurance build cfg."
119);
120
121#[cfg(all(
122 not(miri),
123 not(feature = "allow-compiler-fence-only-wipe"),
124 not(any(
125 target_arch = "aarch64",
126 target_arch = "arm",
127 target_arch = "riscv32",
128 target_arch = "riscv64",
129 target_arch = "wasm32",
130 target_arch = "x86",
131 target_arch = "x86_64",
132 ))
133))]
134compile_error!(
135 "base64-ng: this architecture has no native hardware wipe barrier in \
136 base64-ng. Enable `allow-compiler-fence-only-wipe` only after reviewing \
137 docs/UNSAFE.md and applying platform-approved memory hygiene controls."
138);
139
140mod alphabet;
141mod buffers;
142mod cleanup;
143pub mod ct;
144mod decode_backend;
145mod encode_backend;
146mod engine;
147mod errors;
148mod length;
149mod profiles;
150mod scalar;
151mod scalar_encode_in_place;
152mod wrap;
153
154pub use alphabet::{
155 Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
156 validate_alphabet,
157};
158pub(crate) use alphabet::{encode_base64_value, encode_base64_value_runtime};
159pub use buffers::{DecodedBuffer, EncodedBuffer, ExposedDecodedArray, ExposedEncodedArray};
160#[cfg(feature = "alloc")]
161pub use buffers::{ExposedSecretString, ExposedSecretVec, SecretBuffer};
162pub(crate) use cleanup::{wipe_bytes, wipe_tail};
163#[cfg(feature = "alloc")]
164pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
165pub(crate) use ct::{
166 constant_time_eq_fixed_width_array, constant_time_eq_public_len, ct_mask_eq_u8, ct_mask_lt_u8,
167};
168#[cfg(test)]
169pub(crate) use ct::{ct_padded_final_quantum, report_ct_error};
170pub use engine::Engine;
171pub use errors::{DecodeError, DecodeErrorKind, EncodeError};
172pub use length::{
173 LineEnding, LineWrap, checked_encoded_len, checked_wrapped_encoded_len, decoded_capacity,
174 decoded_len, encoded_len, wrapped_encoded_len,
175};
176pub(crate) use length::{decoded_len_padded, decoded_len_unpadded};
177pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
178#[cfg(any(test, kani))]
179pub(crate) use scalar::decode_chunk;
180#[cfg(kani)]
181pub(crate) use scalar::{decode_byte, decode_tail_unpadded};
182pub(crate) use scalar::{read_quad, validate_chunk, validate_decode, validate_tail_unpadded};
183pub(crate) use wrap::{
184 compact_wrapped_input, is_legacy_whitespace, validate_legacy_decode, validate_wrapped_decode,
185 write_wrapped_byte, write_wrapped_bytes,
186};
187
188#[cfg(feature = "simd")]
189mod simd;
190
191/// Runtime backend reporting for security-sensitive deployments.
192///
193/// This module exposes backend posture so callers can log, assert, or audit
194/// whether execution is scalar-only, using an admitted encode backend, or
195/// merely detecting future SIMD candidates.
196pub mod runtime;
197
198#[cfg(feature = "stream")]
199pub mod stream;
200
201/// Best-effort dependency-free wipe for caller-owned byte slices.
202///
203/// This is the same hardened cleanup primitive used internally by the core
204/// crate: byte-wise volatile zero writes followed by the crate's
205/// architecture-gated wipe barrier and a compiler fence. It is exposed so
206/// companion crates and integrations can reuse the audited cleanup boundary
207/// without duplicating unsafe code.
208///
209/// This is not a formal zeroization guarantee. It cannot clear historical
210/// copies, registers, caches, swap, hibernation images, core dumps, or
211/// OS-level memory snapshots. High-assurance applications still need their own
212/// platform-approved memory hygiene controls.
213pub fn secure_wipe(bytes: &mut [u8]) {
214 cleanup::wipe_bytes(bytes);
215}
216
217/// Standard Base64 engine with padding.
218///
219/// This default strict engine is not a constant-time token validator or
220/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
221/// matching constant-time-oriented decoder when timing posture matters.
222#[doc(alias = "ct")]
223#[doc(alias = "constant_time")]
224#[doc(alias = "sensitive")]
225pub const STANDARD: Engine<Standard, true> = Engine::new();
226
227/// Standard Base64 engine without padding.
228///
229/// This default strict engine is not a constant-time token validator or
230/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
231/// for the matching constant-time-oriented decoder when timing posture
232/// matters.
233#[doc(alias = "ct")]
234#[doc(alias = "constant_time")]
235#[doc(alias = "sensitive")]
236pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
237
238/// URL-safe Base64 engine with padding.
239///
240/// This default strict engine is not a constant-time token validator or
241/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
242/// matching constant-time-oriented decoder when timing posture matters.
243#[doc(alias = "ct")]
244#[doc(alias = "constant_time")]
245#[doc(alias = "sensitive")]
246pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
247
248/// URL-safe Base64 engine without padding.
249///
250/// This default strict engine is not a constant-time token validator or
251/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
252/// for the matching constant-time-oriented decoder when timing posture
253/// matters.
254#[doc(alias = "ct")]
255#[doc(alias = "constant_time")]
256#[doc(alias = "sensitive")]
257pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
258
259/// bcrypt-style Base64 engine without padding.
260///
261/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
262/// It does not parse complete bcrypt password-hash strings. This default strict
263/// engine is not a constant-time token validator or key-material decoder; use
264/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
265/// timing posture matters.
266#[doc(alias = "ct")]
267#[doc(alias = "constant_time")]
268#[doc(alias = "sensitive")]
269pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
270
271/// Unix `crypt(3)`-style Base64 engine without padding.
272///
273/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
274/// packing. It does not parse complete password-hash strings. This default
275/// strict engine is not a constant-time token validator or key-material
276/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
277/// decoder when timing posture matters.
278#[doc(alias = "ct")]
279#[doc(alias = "constant_time")]
280#[doc(alias = "sensitive")]
281pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
282
283/// Encodes `input` as strict standard padded Base64.
284///
285/// This is a convenience wrapper around [`Engine::encode_string`] on
286/// [`STANDARD`] for callers migrating from simpler Base64 APIs. It requires
287/// the `alloc` feature because it returns an owned string.
288///
289/// # Examples
290///
291/// ```
292/// assert_eq!(base64_ng::encode(b"hello").unwrap(), "aGVsbG8=");
293/// ```
294#[cfg(feature = "alloc")]
295pub fn encode(input: &[u8]) -> Result<alloc::string::String, EncodeError> {
296 STANDARD.encode_string(input)
297}
298
299/// Encodes `input` as strict standard padded Base64.
300///
301/// This is a convenience wrapper around [`Engine::encode_string_infallible`] on
302/// [`STANDARD`] for ordinary byte-to-Base64 paths where encoding failure would
303/// indicate an internal length/allocation invariant failure rather than invalid
304/// input.
305///
306/// Prefer [`encode`] when handling untrusted length metadata, constrained
307/// allocation environments, or code paths that must return a recoverable error
308/// instead of panicking.
309///
310/// # Panics
311///
312/// Panics if [`encode`] returns an error. This includes encoded length
313/// overflow; on 32-bit targets, inputs larger than roughly 1.5 GiB can
314/// overflow the encoded length. For attacker-controlled or externally sized
315/// buffers, use [`encode`], which returns a recoverable
316/// [`EncodeError::LengthOverflow`].
317///
318/// # Examples
319///
320/// ```
321/// assert_eq!(base64_ng::encode_infallible(b"hello"), "aGVsbG8=");
322/// ```
323#[cfg(feature = "alloc")]
324#[must_use]
325pub fn encode_infallible(input: &[u8]) -> alloc::string::String {
326 STANDARD.encode_string_infallible(input)
327}
328
329/// Decodes strict standard padded Base64 into an owned byte vector.
330///
331/// This is a convenience wrapper around [`Engine::decode_vec`] on
332/// [`STANDARD`].
333/// It uses the normal strict decoder, not the [`crate::ct`] module, and may
334/// branch or return early on malformed input. For secret-bearing payloads where
335/// malformed-input timing matters, use
336/// [`crate::ct::CtEngine::decode_secret`] through [`crate::ct::STANDARD`]
337/// instead.
338///
339/// # Examples
340///
341/// ```
342/// assert_eq!(base64_ng::decode("aGVsbG8=").unwrap(), b"hello");
343/// ```
344#[cfg(feature = "alloc")]
345#[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
346pub fn decode(input: impl AsRef<[u8]>) -> Result<alloc::vec::Vec<u8>, DecodeError> {
347 STANDARD.decode_vec(input.as_ref())
348}
349
350/// Compares two fixed-width byte arrays without a length-mismatch branch.
351///
352/// Use this helper when the value length itself should not be represented as a
353/// timing-distinct branch in the comparison API. The array length `N` is a
354/// compile-time public type fact, and the helper scans exactly `N` bytes before
355/// returning. The final equality result remains public. This is still a
356/// dependency-free, constant-time-oriented best-effort helper, not a formally
357/// verified cryptographic comparison primitive.
358///
359/// # Examples
360///
361/// ```
362/// use base64_ng::constant_time_eq_fixed_width;
363///
364/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
365/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
366/// ```
367#[must_use]
368pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
369 constant_time_eq_fixed_width_array(left, right)
370}
371
372/// Compares two byte slices with a public length-mismatch branch.
373///
374/// Equal-length inputs are scanned fully before returning. Different lengths
375/// return `false` immediately because length is treated as public. This is a
376/// dependency-free, constant-time-oriented best-effort helper, not a formally
377/// verified cryptographic MAC, password, or bearer-token comparison primitive.
378///
379/// # Security
380///
381/// This helper is intended to avoid ordinary early-exit equality on values
382/// whose length is public. It is not a formal constant-time guarantee and
383/// should not be the sole primitive admitted at MAC, password, or bearer-token
384/// protocol boundaries in high-assurance systems. Use a reviewed comparison
385/// primitive at that boundary when your dependency policy allows one.
386///
387/// # Examples
388///
389/// ```
390/// assert!(base64_ng::constant_time_eq(b"token", b"token"));
391/// assert!(!base64_ng::constant_time_eq(b"token", b"Token"));
392/// assert!(!base64_ng::constant_time_eq(b"token", b"token2"));
393/// ```
394#[must_use]
395pub fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
396 constant_time_eq_public_len(left, right)
397}
398
399/// Clears caller-owned bytes with this crate's best-effort cleanup primitive.
400///
401/// This helper exposes the same dependency-free cleanup path used by
402/// `base64-ng` stack-backed buffers: byte-wise volatile zero writes followed by
403/// the target-specific wipe barrier documented in the crate-level
404/// zeroization caveat. It is intended for companion crates and applications
405/// that need a small reviewed cleanup primitive without pulling cleanup logic
406/// into generated code.
407///
408/// # Security
409///
410/// This is data-retention reduction, not a formal zeroization guarantee. It
411/// cannot clear historical copies, registers, cache lines, swap, hibernation
412/// images, core dumps, or platform snapshots. High-assurance deployments
413/// should pair it with their approved platform memory controls.
414pub fn clear_bytes(bytes: &mut [u8]) {
415 wipe_bytes(bytes);
416}
417
418#[cfg(kani)]
419mod kani_proofs;
420
421#[cfg(test)]
422mod decode_surface_tests;
423#[cfg(test)]
424mod encode_surface_tests;
425#[cfg(test)]
426mod non_standard_surface_tests;
427#[cfg(test)]
428mod tests;