Skip to main content

cloud_sdk_reqwest/shared/
auth.rs

1use core::fmt;
2
3use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
4use reqwest::header::HeaderValue;
5#[cfg(test)]
6use std::sync::{
7    Arc,
8    atomic::{AtomicUsize, Ordering},
9};
10use std::vec::Vec;
11
12use super::sensitive_header_value;
13#[cfg(test)]
14use super::sensitive_header_value_with_probe;
15/// Maximum bearer-token length accepted by the adapter.
16pub const MAX_BEARER_TOKEN_BYTES: usize = 4096;
17
18const BEARER_PREFIX: &[u8] = b"Bearer ";
19
20/// Bearer-token validation error.
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub enum BearerTokenError {
23    /// Tokens must not be empty.
24    Empty,
25    /// Tokens exceed [`MAX_BEARER_TOKEN_BYTES`].
26    TooLong,
27    /// Tokens contain bytes outside the RFC bearer-token alphabet or invalid
28    /// non-trailing padding.
29    InvalidByte,
30    /// Allocation failed while taking owned secret storage.
31    AllocationFailed,
32}
33
34impl_static_error!(BearerTokenError,
35    Self::Empty => "bearer token is empty",
36    Self::TooLong => "bearer token exceeds the length limit",
37    Self::InvalidByte => "bearer token contains an invalid byte",
38    Self::AllocationFailed => "bearer-token allocation failed",
39);
40
41/// Owned bearer authorization value with redacted diagnostics and volatile
42/// cleanup of the adapter-owned bytes.
43pub struct BearerToken {
44    authorization: Vec<u8>,
45    #[cfg(test)]
46    drop_probe: Option<Arc<AtomicUsize>>,
47    #[cfg(test)]
48    header_drop_probe: Option<Arc<AtomicUsize>>,
49}
50
51impl BearerToken {
52    /// Validates and copies a bearer token into adapter-owned secret storage.
53    pub fn new(token: &str) -> Result<Self, BearerTokenError> {
54        Self::from_bytes(token.as_bytes())
55    }
56
57    /// Consumes a mutable bearer-token source and clears the complete source
58    /// buffer on success or failure.
59    pub fn from_mut_bytes(token: &mut [u8]) -> Result<Self, BearerTokenError> {
60        let result = Self::from_bytes(token);
61        sanitize_bytes(token);
62        result
63    }
64
65    /// Consumes guarded bearer-token storage, which clears its complete source
66    /// buffer when this function returns on success or failure.
67    pub fn from_secret_buffer(token: SecretBuffer<'_>) -> Result<Self, BearerTokenError> {
68        Self::from_bytes(token.as_slice())
69    }
70
71    fn from_bytes(token: &[u8]) -> Result<Self, BearerTokenError> {
72        if token.is_empty() {
73            return Err(BearerTokenError::Empty);
74        }
75        if token.len() > MAX_BEARER_TOKEN_BYTES {
76            return Err(BearerTokenError::TooLong);
77        }
78        let mut saw_token_byte = false;
79        let mut padding = false;
80        for byte in token.iter().copied() {
81            match byte {
82                b'=' if saw_token_byte => padding = true,
83                b'=' => return Err(BearerTokenError::InvalidByte),
84                _ if padding || !is_bearer_byte(byte) => {
85                    return Err(BearerTokenError::InvalidByte);
86                }
87                _ => saw_token_byte = true,
88            }
89        }
90
91        let capacity = BEARER_PREFIX
92            .len()
93            .checked_add(token.len())
94            .ok_or(BearerTokenError::TooLong)?;
95        let mut authorization = Vec::new();
96        authorization
97            .try_reserve_exact(capacity)
98            .map_err(|_| BearerTokenError::AllocationFailed)?;
99        authorization.extend_from_slice(BEARER_PREFIX);
100        authorization.extend_from_slice(token);
101        Ok(Self {
102            authorization,
103            #[cfg(test)]
104            drop_probe: None,
105            #[cfg(test)]
106            header_drop_probe: None,
107        })
108    }
109
110    pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
111        sensitive_header_value(&self.authorization)
112    }
113
114    #[cfg(test)]
115    pub(crate) fn header_value_with_drop_probe(&self) -> Result<HeaderValue, ()> {
116        sensitive_header_value_with_probe(&self.authorization, self.header_drop_probe.clone())
117    }
118
119    #[cfg(test)]
120    pub(crate) fn owned_bytes(&self) -> &[u8] {
121        &self.authorization
122    }
123
124    #[cfg(test)]
125    pub(crate) fn with_drop_probe(
126        token: &str,
127        probe: Arc<AtomicUsize>,
128    ) -> Result<Self, BearerTokenError> {
129        let mut value = Self::new(token)?;
130        value.drop_probe = Some(probe);
131        Ok(value)
132    }
133
134    #[cfg(test)]
135    pub(crate) fn with_header_drop_probe(
136        token: &str,
137        probe: Arc<AtomicUsize>,
138    ) -> Result<Self, BearerTokenError> {
139        let mut value = Self::new(token)?;
140        value.header_drop_probe = Some(probe);
141        Ok(value)
142    }
143}
144
145impl Drop for BearerToken {
146    fn drop(&mut self) {
147        sanitize_bytes(&mut self.authorization);
148        #[cfg(test)]
149        if let Some(probe) = &self.drop_probe {
150            probe.fetch_add(1, Ordering::SeqCst);
151        }
152    }
153}
154
155impl fmt::Debug for BearerToken {
156    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
157        formatter.write_str("BearerToken([redacted])")
158    }
159}
160
161const fn is_bearer_byte(byte: u8) -> bool {
162    byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')
163}