secure_gate/traits/decoding/base64_url.rs
1//! URL-safe Base64 decoding trait.
2//!
3//! This trait provides secure, explicit decoding of base64url-encoded strings
4//! (URL-safe alphabet, no padding) to byte vectors. It is designed for handling
5//! untrusted input in cryptographic contexts, such as decoding encoded keys or tokens.
6//!
7//! Requires the `encoding-base64` feature.
8//!
9//! # Security Notes
10//!
11//! - **Treat all input as untrusted**: validate base64url strings upstream before
12//! wrapping in secrets. Invalid input may indicate tampering or injection attempts.
13//! - **Heap allocation**: Returns `Vec<u8>` โ wrap in [`Fixed`](crate::Fixed) or
14//! [`Dynamic`](crate::Dynamic) to store as a secret.
15//! - **Strict validation**: URL-safe alphabet, no padding, per RFC 4648 ยง5. Invalid input fails immediately.
16//! - **URL-safe alphabet**: Uses `-` and `_` instead of `+` and `/`.
17//!
18//! # Example
19//!
20//! ```rust
21//! # #[cfg(feature = "encoding-base64")]
22//! use secure_gate::{FromBase64UrlStr, Fixed};
23//! # #[cfg(feature = "encoding-base64")]
24//! {
25//! // "AQIDBA" decodes to [1, 2, 3, 4]
26//! let bytes = "AQIDBA".try_from_base64url().unwrap();
27//! assert_eq!(bytes, vec![1, 2, 3, 4]);
28//!
29//! // Wrap result in a secret immediately
30//! let secret: Fixed<[u8; 3]> = Fixed::try_from_base64url("AQID").unwrap();
31//!
32//! // Error on invalid input
33//! assert!("!!!".try_from_base64url().is_err());
34//! }
35//! ```
36#[cfg(feature = "encoding-base64")]
37use ::base64 as base64_crate;
38
39#[cfg(feature = "encoding-base64")]
40use base64_crate::engine::general_purpose::URL_SAFE_NO_PAD;
41
42#[cfg(feature = "encoding-base64")]
43use base64_crate::Engine;
44
45#[cfg(feature = "encoding-base64")]
46use crate::error::Base64Error;
47
48/// Extension trait for decoding URL-safe base64 strings into byte vectors.
49///
50/// *Requires feature `encoding-base64`.*
51///
52/// Blanket-implemented for all `AsRef<str>` types. Uses the RFC 4648 URL-safe
53/// alphabet without `=` padding. Treat all input as untrusted; validate lengths
54/// and content upstream before wrapping decoded bytes in secrets.
55#[cfg(feature = "encoding-base64")]
56pub trait FromBase64UrlStr {
57 /// Decodes a URL-safe base64 string (no padding) into a byte vector.
58 ///
59 /// # Errors
60 ///
61 /// - [`Base64Error::InvalidBase64`] โ invalid characters or unexpected padding.
62 ///
63 /// # Examples
64 ///
65 /// ```rust
66 /// use secure_gate::FromBase64UrlStr;
67 ///
68 /// // "AQIDBA" decodes to [1, 2, 3, 4]
69 /// let bytes = "AQIDBA".try_from_base64url()?;
70 /// assert_eq!(bytes, [1, 2, 3, 4]);
71 ///
72 /// assert!("!!!".try_from_base64url().is_err()); // invalid chars
73 /// # Ok::<(), secure_gate::Base64Error>(())
74 /// ```
75 fn try_from_base64url(&self) -> Result<Vec<u8>, Base64Error>;
76}
77
78// Blanket impl to cover any AsRef<str> (e.g., &str, String, etc.)
79#[cfg(feature = "encoding-base64")]
80impl<T: AsRef<str> + ?Sized> FromBase64UrlStr for T {
81 fn try_from_base64url(&self) -> Result<Vec<u8>, Base64Error> {
82 URL_SAFE_NO_PAD
83 .decode(self.as_ref())
84 .map_err(|_| Base64Error::InvalidBase64)
85 }
86}