Skip to main content

cloud_sdk_reqwest/shared/
auth.rs

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