1use aws_sigv4::http_request::{
7 PayloadChecksumKind, PercentEncodingMode, SessionTokenMode, SignableBody, SignatureLocation,
8 SigningInstructions, SigningSettings, UriPathNormalizationMode,
9};
10use aws_smithy_runtime_api::box_error::BoxError;
11use aws_smithy_runtime_api::client::auth::AuthSchemeEndpointConfig;
12use aws_smithy_runtime_api::client::identity::Identity;
13use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
14use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
15use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer, Storable, StoreReplace};
16use aws_smithy_types::Document;
17use aws_types::region::{Region, SigningRegion, SigningRegionSet};
18use aws_types::SigningName;
19use std::borrow::Cow;
20use std::error::Error as StdError;
21use std::fmt;
22use std::time::Duration;
23
24pub mod sigv4;
26
27#[cfg(feature = "sigv4a")]
28pub mod sigv4a;
30
31#[derive(Debug, Eq, PartialEq, Clone, Copy)]
33pub enum HttpSignatureType {
34 HttpRequestHeaders,
36
37 HttpRequestQueryParams,
41}
42
43#[derive(Clone, Debug, Eq, PartialEq)]
45#[non_exhaustive]
46pub struct SigningOptions {
47 pub double_uri_encode: bool,
49 pub content_sha256_header: bool,
51 pub normalize_uri_path: bool,
53 pub omit_session_token: bool,
55 pub payload_override: Option<SignableBody<'static>>,
57 pub signature_type: HttpSignatureType,
59 pub signing_optional: bool,
61 pub expires_in: Option<Duration>,
63}
64
65impl Default for SigningOptions {
66 fn default() -> Self {
67 Self {
68 double_uri_encode: true,
69 content_sha256_header: false,
70 normalize_uri_path: true,
71 omit_session_token: false,
72 payload_override: None,
73 signature_type: HttpSignatureType::HttpRequestHeaders,
74 signing_optional: false,
75 expires_in: None,
76 }
77 }
78}
79
80pub(crate) type SessionTokenNameOverrideFn = Box<
81 dyn Fn(&SigningSettings, &ConfigBag) -> Result<Option<&'static str>, BoxError>
82 + Send
83 + Sync
84 + 'static,
85>;
86
87pub struct SigV4SessionTokenNameOverride {
89 name_override: SessionTokenNameOverrideFn,
90}
91
92impl SigV4SessionTokenNameOverride {
93 pub fn new<F>(name_override: F) -> Self
95 where
96 F: Fn(&SigningSettings, &ConfigBag) -> Result<Option<&'static str>, BoxError>
97 + Send
98 + Sync
99 + 'static,
100 {
101 Self {
102 name_override: Box::new(name_override),
103 }
104 }
105
106 pub fn name_override(
108 &self,
109 settings: &SigningSettings,
110 config_bag: &ConfigBag,
111 ) -> Result<Option<&'static str>, BoxError> {
112 (self.name_override)(settings, config_bag)
113 }
114}
115
116impl fmt::Debug for SigV4SessionTokenNameOverride {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 f.debug_struct("SessionTokenNameOverride").finish()
119 }
120}
121
122impl Storable for SigV4SessionTokenNameOverride {
123 type Storer = StoreReplace<Self>;
124}
125
126#[derive(Clone, Debug, Default, PartialEq, Eq)]
131pub struct SigV4OperationSigningConfig {
132 pub region: Option<SigningRegion>,
136 pub region_set: Option<SigningRegionSet>,
140 pub name: Option<SigningName>,
142 pub signing_options: SigningOptions,
144}
145
146impl Storable for SigV4OperationSigningConfig {
149 type Storer = StoreReplace<Self>;
150}
151
152fn settings(operation_config: &SigV4OperationSigningConfig) -> SigningSettings {
153 let mut settings = SigningSettings::default();
154 settings.percent_encoding_mode = if operation_config.signing_options.double_uri_encode {
155 PercentEncodingMode::Double
156 } else {
157 PercentEncodingMode::Single
158 };
159 settings.payload_checksum_kind = if operation_config.signing_options.content_sha256_header {
160 PayloadChecksumKind::XAmzSha256
161 } else {
162 PayloadChecksumKind::NoHeader
163 };
164 settings.uri_path_normalization_mode = if operation_config.signing_options.normalize_uri_path {
165 UriPathNormalizationMode::Enabled
166 } else {
167 UriPathNormalizationMode::Disabled
168 };
169 settings.session_token_mode = if operation_config.signing_options.omit_session_token {
170 SessionTokenMode::Exclude
171 } else {
172 SessionTokenMode::Include
173 };
174 settings.signature_location = match operation_config.signing_options.signature_type {
175 HttpSignatureType::HttpRequestHeaders => SignatureLocation::Headers,
176 HttpSignatureType::HttpRequestQueryParams => SignatureLocation::QueryParams,
177 };
178 settings.expires_in = operation_config.signing_options.expires_in;
179 settings
180}
181
182#[derive(Debug)]
183enum SigV4SigningError {
184 MissingOperationSigningConfig,
185 MissingSigningRegion,
186 #[cfg(feature = "sigv4a")]
187 MissingSigningRegionSet,
188 MissingSigningName,
189 WrongIdentityType(Identity),
190 BadTypeInEndpointAuthSchemeConfig(&'static str),
191}
192
193impl fmt::Display for SigV4SigningError {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 use SigV4SigningError::*;
196 let mut w = |s| f.write_str(s);
197 match self {
198 MissingOperationSigningConfig => w("missing operation signing config"),
199 MissingSigningRegion => w("missing signing region"),
200 #[cfg(feature = "sigv4a")]
201 MissingSigningRegionSet => w("missing signing region set"),
202 MissingSigningName => w("missing signing name"),
203 WrongIdentityType(identity) => {
204 write!(f, "wrong identity type for SigV4/sigV4a. Expected AWS credentials but got `{identity:?}`")
205 }
206 BadTypeInEndpointAuthSchemeConfig(field_name) => {
207 write!(
208 f,
209 "unexpected type for `{field_name}` in endpoint auth scheme config",
210 )
211 }
212 }
213 }
214}
215
216impl StdError for SigV4SigningError {}
217
218fn extract_endpoint_auth_scheme_signing_name(
219 endpoint_config: &AuthSchemeEndpointConfig<'_>,
220) -> Result<Option<SigningName>, SigV4SigningError> {
221 match extract_field_from_endpoint_config("signingName", endpoint_config) {
222 Some(Document::String(s)) => Ok(Some(SigningName::from(s.to_string()))),
223 None => Ok(None),
224 _ => Err(SigV4SigningError::BadTypeInEndpointAuthSchemeConfig(
225 "signingName",
226 )),
227 }
228}
229
230fn extract_endpoint_auth_scheme_signing_region(
231 endpoint_config: &AuthSchemeEndpointConfig<'_>,
232) -> Result<Option<SigningRegion>, SigV4SigningError> {
233 match extract_field_from_endpoint_config("signingRegion", endpoint_config) {
234 Some(Document::String(s)) => Ok(Some(SigningRegion::from(Region::new(s.clone())))),
235 None => Ok(None),
236 _ => Err(SigV4SigningError::BadTypeInEndpointAuthSchemeConfig(
237 "signingRegion",
238 )),
239 }
240}
241
242fn extract_endpoint_auth_scheme_signing_options<'a>(
247 endpoint_config: &AuthSchemeEndpointConfig<'_>,
248 signing_options: &'a SigningOptions,
249) -> Result<Cow<'a, SigningOptions>, SigV4SigningError> {
250 let double_uri_encode =
251 match extract_field_from_endpoint_config("disableDoubleEncoding", endpoint_config) {
252 Some(Document::Bool(b)) => Some(!b),
253 None => None,
254 _ => {
255 return Err(SigV4SigningError::BadTypeInEndpointAuthSchemeConfig(
256 "disableDoubleEncoding",
257 ))
258 }
259 };
260 let normalize_uri_path =
261 match extract_field_from_endpoint_config("disableNormalizePath", endpoint_config) {
262 Some(Document::Bool(b)) => Some(!b),
263 None => None,
264 _ => {
265 return Err(SigV4SigningError::BadTypeInEndpointAuthSchemeConfig(
266 "disableNormalizePath",
267 ))
268 }
269 };
270 match (double_uri_encode, normalize_uri_path) {
271 (None, None) => Ok(Cow::Borrowed(signing_options)),
272 (double_uri_encode, normalize_uri_path) => {
273 let mut signing_options = signing_options.clone();
274 signing_options.double_uri_encode =
275 double_uri_encode.unwrap_or(signing_options.double_uri_encode);
276 signing_options.normalize_uri_path =
277 normalize_uri_path.unwrap_or(signing_options.normalize_uri_path);
278 Ok(Cow::Owned(signing_options))
279 }
280 }
281}
282
283fn extract_field_from_endpoint_config<'a>(
284 field_name: &'static str,
285 endpoint_config: &'a AuthSchemeEndpointConfig<'_>,
286) -> Option<&'a Document> {
287 endpoint_config
288 .as_document()
289 .and_then(Document::as_object)
290 .and_then(|config| config.get(field_name))
291}
292
293fn apply_signing_instructions(
294 instructions: SigningInstructions,
295 request: &mut HttpRequest,
296) -> Result<(), BoxError> {
297 let (new_headers, new_query) = instructions.into_parts();
298 for header in new_headers.into_iter() {
299 let mut value = http_02x::HeaderValue::from_str(header.value()).unwrap();
300 value.set_sensitive(header.sensitive());
301 request.headers_mut().insert(header.name(), value);
302 }
303
304 if !new_query.is_empty() {
305 let mut query = aws_smithy_http::query_writer::QueryWriter::new_from_string(request.uri())?;
306 for (name, value) in new_query {
307 query.insert(name, &value);
308 }
309 request.set_uri(query.build_uri())?;
310 }
311 Ok(())
312}
313
314#[non_exhaustive]
317#[derive(Clone, Debug)]
318pub enum PayloadSigningOverride {
319 UnsignedPayload,
324
325 Precomputed(String),
329
330 StreamingUnsignedPayloadTrailer,
332}
333
334impl PayloadSigningOverride {
335 pub fn unsigned_payload() -> Self {
338 Self::UnsignedPayload
339 }
340
341 pub fn to_signable_body(self) -> SignableBody<'static> {
344 match self {
345 Self::UnsignedPayload => SignableBody::UnsignedPayload,
346 Self::Precomputed(checksum) => SignableBody::Precomputed(checksum),
347 Self::StreamingUnsignedPayloadTrailer => SignableBody::StreamingUnsignedPayloadTrailer,
348 }
349 }
350}
351
352impl Storable for PayloadSigningOverride {
353 type Storer = StoreReplace<Self>;
354}
355
356#[derive(Debug)]
358pub struct PayloadSigningOverrideRuntimePlugin {
359 inner: FrozenLayer,
360}
361
362impl PayloadSigningOverrideRuntimePlugin {
363 pub fn unsigned() -> Self {
366 let mut layer = Layer::new("PayloadSigningOverrideRuntimePlugin");
367 layer.store_put(PayloadSigningOverride::UnsignedPayload);
368
369 Self {
370 inner: layer.freeze(),
371 }
372 }
373}
374
375impl RuntimePlugin for PayloadSigningOverrideRuntimePlugin {
376 fn config(&self) -> Option<FrozenLayer> {
377 Some(self.inner.clone())
378 }
379}