Skip to main content

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