cloud_sdk_reqwest/shared/
basic.rs1use core::fmt;
2use std::vec::Vec;
3
4use aws_lc_rs::rand::{SecureRandom, SystemRandom};
5use base64_ng::{STANDARD, checked_encoded_len};
6use cloud_sdk::authentication::{CREDENTIAL_BINDING_BYTES, CredentialBinding};
7use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
8use reqwest::header::HeaderValue;
9
10use super::{BasicCredentialScope, sensitive_header_value};
11
12pub const MAX_BASIC_USERNAME_BYTES: usize = 256;
14pub const MAX_BASIC_PASSWORD_BYTES: usize = 2048;
16pub const MAX_BASIC_AUTHORIZATION_BYTES: usize = 4096;
18
19const BASIC_PREFIX: &[u8] = b"Basic ";
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum BasicUsernameError {
24 Empty,
26 TooLong,
28 InvalidByte,
30 AllocationFailed,
32}
33
34impl_static_error!(BasicUsernameError,
35 Self::Empty => "Basic username is empty",
36 Self::TooLong => "Basic username exceeds the length limit",
37 Self::InvalidByte => "Basic username contains an invalid byte",
38 Self::AllocationFailed => "Basic username allocation failed",
39);
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum BasicPasswordError {
44 Empty,
46 TooLong,
48 InvalidByte,
50 AllocationFailed,
52}
53
54impl_static_error!(BasicPasswordError,
55 Self::Empty => "Basic password is empty",
56 Self::TooLong => "Basic password exceeds the length limit",
57 Self::InvalidByte => "Basic password contains an invalid byte",
58 Self::AllocationFailed => "Basic password allocation failed",
59);
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum BasicCredentialError {
64 UsernameRejected(BasicUsernameError),
66 PasswordRejected(BasicPasswordError),
68 AuthorizationTooLong,
70 AllocationFailed,
72 EncodingFailed,
74 BindingGenerationFailed,
76}
77
78impl fmt::Display for BasicCredentialError {
79 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
80 formatter.write_str(match self {
81 Self::UsernameRejected(_) => "Basic username was rejected",
82 Self::PasswordRejected(_) => "Basic password was rejected",
83 Self::AuthorizationTooLong => "Basic authorization exceeds the length limit",
84 Self::AllocationFailed => "Basic authorization allocation failed",
85 Self::EncodingFailed => "Basic authorization encoding failed",
86 Self::BindingGenerationFailed => "Basic credential binding generation failed",
87 })
88 }
89}
90
91impl core::error::Error for BasicCredentialError {
92 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
93 match self {
94 Self::UsernameRejected(error) => Some(error),
95 Self::PasswordRejected(error) => Some(error),
96 Self::AuthorizationTooLong
97 | Self::AllocationFailed
98 | Self::EncodingFailed
99 | Self::BindingGenerationFailed => None,
100 }
101 }
102}
103
104pub struct BasicUsername(SecretBytes);
106
107impl BasicUsername {
108 pub fn new(value: &str) -> Result<Self, BasicUsernameError> {
110 Self::from_bytes(value.as_bytes())
111 }
112
113 pub fn from_mut_bytes(value: &mut [u8]) -> Result<Self, BasicUsernameError> {
115 let result = Self::from_bytes(value);
116 sanitize_bytes(value);
117 result
118 }
119
120 pub fn from_secret_buffer(value: SecretBuffer<'_>) -> Result<Self, BasicUsernameError> {
122 Self::from_bytes(value.as_slice())
123 }
124
125 fn from_bytes(value: &[u8]) -> Result<Self, BasicUsernameError> {
126 validate_username(value)?;
127 SecretBytes::copy_from(value)
128 .map(Self)
129 .map_err(|()| BasicUsernameError::AllocationFailed)
130 }
131}
132
133impl fmt::Debug for BasicUsername {
134 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
135 formatter.write_str("BasicUsername([redacted])")
136 }
137}
138
139pub struct BasicPassword(SecretBytes);
141
142impl BasicPassword {
143 pub fn new(value: &str) -> Result<Self, BasicPasswordError> {
145 Self::from_bytes(value.as_bytes())
146 }
147
148 pub fn from_mut_bytes(value: &mut [u8]) -> Result<Self, BasicPasswordError> {
150 let result = Self::from_bytes(value);
151 sanitize_bytes(value);
152 result
153 }
154
155 pub fn from_secret_buffer(value: SecretBuffer<'_>) -> Result<Self, BasicPasswordError> {
157 Self::from_bytes(value.as_slice())
158 }
159
160 fn from_bytes(value: &[u8]) -> Result<Self, BasicPasswordError> {
161 validate_password(value)?;
162 SecretBytes::copy_from(value)
163 .map(Self)
164 .map_err(|()| BasicPasswordError::AllocationFailed)
165 }
166}
167
168impl fmt::Debug for BasicPassword {
169 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170 formatter.write_str("BasicPassword([redacted])")
171 }
172}
173
174pub struct BasicCredential {
176 authorization: SecretBytes,
177 pub(crate) scope: BasicCredentialScope,
178 binding: CredentialBinding,
179}
180
181impl BasicCredential {
182 pub fn new(
184 username: BasicUsername,
185 password: BasicPassword,
186 scope: BasicCredentialScope,
187 ) -> Result<Self, BasicCredentialError> {
188 let mut user_pass = SecretBytes::with_capacity(
189 username
190 .0
191 .len()
192 .checked_add(1)
193 .and_then(|len| len.checked_add(password.0.len()))
194 .ok_or(BasicCredentialError::AuthorizationTooLong)?,
195 )
196 .map_err(|()| BasicCredentialError::AllocationFailed)?;
197 user_pass.extend(username.0.as_ref())?;
198 user_pass.push(b':')?;
199 user_pass.extend(password.0.as_ref())?;
200 let encoded_len = checked_encoded_len(user_pass.len(), true)
201 .ok_or(BasicCredentialError::AuthorizationTooLong)?;
202 let total_len = BASIC_PREFIX
203 .len()
204 .checked_add(encoded_len)
205 .ok_or(BasicCredentialError::AuthorizationTooLong)?;
206 if total_len > MAX_BASIC_AUTHORIZATION_BYTES {
207 return Err(BasicCredentialError::AuthorizationTooLong);
208 }
209 let mut authorization = SecretBytes::with_capacity(total_len)
210 .map_err(|()| BasicCredentialError::AllocationFailed)?;
211 authorization.extend(BASIC_PREFIX)?;
212 authorization.resize(total_len)?;
213 let destination = authorization
214 .as_mut()
215 .get_mut(BASIC_PREFIX.len()..)
216 .ok_or(BasicCredentialError::EncodingFailed)?;
217 let written = STANDARD
218 .encode_slice(user_pass.as_ref(), destination)
219 .map_err(|_| BasicCredentialError::EncodingFailed)?;
220 if written != encoded_len {
221 return Err(BasicCredentialError::EncodingFailed);
222 }
223 let mut binding = [0_u8; CREDENTIAL_BINDING_BYTES];
224 SystemRandom::new()
225 .fill(&mut binding)
226 .map_err(|_| BasicCredentialError::BindingGenerationFailed)?;
227 let binding = CredentialBinding::new(binding)
228 .map_err(|_| BasicCredentialError::BindingGenerationFailed)?;
229 Ok(Self {
230 authorization,
231 scope,
232 binding,
233 })
234 }
235
236 pub fn from_mut_bytes(
238 username: &mut [u8],
239 password: &mut [u8],
240 scope: BasicCredentialScope,
241 ) -> Result<Self, BasicCredentialError> {
242 let username =
243 BasicUsername::from_mut_bytes(username).map_err(BasicCredentialError::UsernameRejected);
244 let password =
245 BasicPassword::from_mut_bytes(password).map_err(BasicCredentialError::PasswordRejected);
246 Self::new(username?, password?, scope)
247 }
248
249 pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
250 sensitive_header_value(self.authorization.as_ref())
251 }
252
253 pub(crate) const fn scope(&self) -> &BasicCredentialScope {
254 &self.scope
255 }
256
257 pub(crate) const fn binding(&self) -> CredentialBinding {
258 self.binding
259 }
260
261 #[cfg(test)]
262 pub(crate) fn owned_bytes(&self) -> &[u8] {
263 self.authorization.as_ref()
264 }
265}
266
267impl fmt::Debug for BasicCredential {
268 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
269 formatter.write_str("BasicCredential([redacted])")
270 }
271}
272
273struct SecretBytes(Vec<u8>);
274
275impl SecretBytes {
276 fn copy_from(value: &[u8]) -> Result<Self, ()> {
277 let mut bytes = Vec::new();
278 bytes.try_reserve_exact(value.len()).map_err(|_| ())?;
279 bytes.extend_from_slice(value);
280 Ok(Self(bytes))
281 }
282
283 fn with_capacity(capacity: usize) -> Result<Self, ()> {
284 let mut bytes = Vec::new();
285 bytes.try_reserve_exact(capacity).map_err(|_| ())?;
286 Ok(Self(bytes))
287 }
288
289 fn extend(&mut self, value: &[u8]) -> Result<(), BasicCredentialError> {
290 self.0
291 .try_reserve(value.len())
292 .map_err(|_| BasicCredentialError::AllocationFailed)?;
293 self.0.extend_from_slice(value);
294 Ok(())
295 }
296
297 fn push(&mut self, value: u8) -> Result<(), BasicCredentialError> {
298 self.0
299 .try_reserve(1)
300 .map_err(|_| BasicCredentialError::AllocationFailed)?;
301 self.0.push(value);
302 Ok(())
303 }
304
305 fn resize(&mut self, len: usize) -> Result<(), BasicCredentialError> {
306 let additional = len.saturating_sub(self.0.len());
307 self.0
308 .try_reserve(additional)
309 .map_err(|_| BasicCredentialError::AllocationFailed)?;
310 self.0.resize(len, 0);
311 Ok(())
312 }
313
314 fn len(&self) -> usize {
315 self.0.len()
316 }
317
318 fn as_mut(&mut self) -> &mut [u8] {
319 &mut self.0
320 }
321}
322
323impl AsRef<[u8]> for SecretBytes {
324 fn as_ref(&self) -> &[u8] {
325 &self.0
326 }
327}
328
329impl Drop for SecretBytes {
330 fn drop(&mut self) {
331 sanitize_bytes(&mut self.0);
332 }
333}
334
335fn validate_username(value: &[u8]) -> Result<(), BasicUsernameError> {
336 if value.is_empty() {
337 return Err(BasicUsernameError::Empty);
338 }
339 if value.len() > MAX_BASIC_USERNAME_BYTES {
340 return Err(BasicUsernameError::TooLong);
341 }
342 if !value
343 .iter()
344 .all(|byte| matches!(byte, b'!'..=b'~') && *byte != b':')
345 {
346 return Err(BasicUsernameError::InvalidByte);
347 }
348 Ok(())
349}
350
351fn validate_password(value: &[u8]) -> Result<(), BasicPasswordError> {
352 if value.is_empty() {
353 return Err(BasicPasswordError::Empty);
354 }
355 if value.len() > MAX_BASIC_PASSWORD_BYTES {
356 return Err(BasicPasswordError::TooLong);
357 }
358 if !value.iter().all(|byte| matches!(byte, b' '..=b'~')) {
359 return Err(BasicPasswordError::InvalidByte);
360 }
361 Ok(())
362}
363
364#[cfg(test)]
365mod tests;