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::error::Error as StdError;
20use std::fmt;
21use std::time::Duration;
22
23pub mod sigv4;
25
26#[cfg(feature = "sigv4a")]
27pub mod sigv4a;
29
30#[derive(Debug, Eq, PartialEq, Clone, Copy)]
32pub enum HttpSignatureType {
33 HttpRequestHeaders,
35
36 HttpRequestQueryParams,
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
44#[non_exhaustive]
45pub struct SigningOptions {
46 pub double_uri_encode: bool,
48 pub content_sha256_header: bool,
50 pub normalize_uri_path: bool,
52 pub omit_session_token: bool,
54 pub payload_override: Option<SignableBody<'static>>,
56 pub signature_type: HttpSignatureType,
58 pub signing_optional: bool,
60 pub expires_in: Option<Duration>,
62}
63
64impl Default for SigningOptions {
65 fn default() -> Self {
66 Self {
67 double_uri_encode: true,
68 content_sha256_header: false,
69 normalize_uri_path: true,
70 omit_session_token: false,
71 payload_override: None,
72 signature_type: HttpSignatureType::HttpRequestHeaders,
73 signing_optional: false,
74 expires_in: None,
75 }
76 }
77}
78
79pub(crate) type SessionTokenNameOverrideFn = Box<
80 dyn Fn(&SigningSettings, &ConfigBag) -> Result<Option<&'static str>, BoxError>
81 + Send
82 + Sync
83 + 'static,
84>;
85
86pub struct SigV4SessionTokenNameOverride {
88 name_override: SessionTokenNameOverrideFn,
89}
90
91impl SigV4SessionTokenNameOverride {
92 pub fn new<F>(name_override: F) -> Self
94 where
95 F: Fn(&SigningSettings, &ConfigBag) -> Result<Option<&'static str>, BoxError>
96 + Send
97 + Sync
98 + 'static,
99 {
100 Self {
101 name_override: Box::new(name_override),
102 }
103 }
104
105 pub fn name_override(
107 &self,
108 settings: &SigningSettings,
109 config_bag: &ConfigBag,
110 ) -> Result<Option<&'static str>, BoxError> {
111 (self.name_override)(settings, config_bag)
112 }
113}
114
115impl fmt::Debug for SigV4SessionTokenNameOverride {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.debug_struct("SessionTokenNameOverride").finish()
118 }
119}
120
121impl Storable for SigV4SessionTokenNameOverride {
122 type Storer = StoreReplace<Self>;
123}
124
125#[derive(Clone, Debug, Default, PartialEq, Eq)]
130pub struct SigV4OperationSigningConfig {
131 pub region: Option<SigningRegion>,
135 pub region_set: Option<SigningRegionSet>,
139 pub name: Option<SigningName>,
141 pub signing_options: SigningOptions,
143}
144
145impl Storable for SigV4OperationSigningConfig {
146 type Storer = StoreReplace<Self>;
147}
148
149fn settings(operation_config: &SigV4OperationSigningConfig) -> SigningSettings {
150 let mut settings = SigningSettings::default();
151 settings.percent_encoding_mode = if operation_config.signing_options.double_uri_encode {
152 PercentEncodingMode::Double
153 } else {
154 PercentEncodingMode::Single
155 };
156 settings.payload_checksum_kind = if operation_config.signing_options.content_sha256_header {
157 PayloadChecksumKind::XAmzSha256
158 } else {
159 PayloadChecksumKind::NoHeader
160 };
161 settings.uri_path_normalization_mode = if operation_config.signing_options.normalize_uri_path {
162 UriPathNormalizationMode::Enabled
163 } else {
164 UriPathNormalizationMode::Disabled
165 };
166 settings.session_token_mode = if operation_config.signing_options.omit_session_token {
167 SessionTokenMode::Exclude
168 } else {
169 SessionTokenMode::Include
170 };
171 settings.signature_location = match operation_config.signing_options.signature_type {
172 HttpSignatureType::HttpRequestHeaders => SignatureLocation::Headers,
173 HttpSignatureType::HttpRequestQueryParams => SignatureLocation::QueryParams,
174 };
175 settings.expires_in = operation_config.signing_options.expires_in;
176 settings
177}
178
179#[derive(Debug)]
180enum SigV4SigningError {
181 MissingOperationSigningConfig,
182 MissingSigningRegion,
183 #[cfg(feature = "sigv4a")]
184 MissingSigningRegionSet,
185 MissingSigningName,
186 WrongIdentityType(Identity),
187 BadTypeInEndpointAuthSchemeConfig(&'static str),
188}
189
190impl fmt::Display for SigV4SigningError {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 use SigV4SigningError::*;
193 let mut w = |s| f.write_str(s);
194 match self {
195 MissingOperationSigningConfig => w("missing operation signing config"),
196 MissingSigningRegion => w("missing signing region"),
197 #[cfg(feature = "sigv4a")]
198 MissingSigningRegionSet => w("missing signing region set"),
199 MissingSigningName => w("missing signing name"),
200 WrongIdentityType(identity) => {
201 write!(f, "wrong identity type for SigV4/sigV4a. Expected AWS credentials but got `{identity:?}`")
202 }
203 BadTypeInEndpointAuthSchemeConfig(field_name) => {
204 write!(
205 f,
206 "unexpected type for `{field_name}` in endpoint auth scheme config",
207 )
208 }
209 }
210 }
211}
212
213impl StdError for SigV4SigningError {}
214
215fn extract_endpoint_auth_scheme_signing_name(
216 endpoint_config: &AuthSchemeEndpointConfig<'_>,
217) -> Result<Option<SigningName>, SigV4SigningError> {
218 use SigV4SigningError::BadTypeInEndpointAuthSchemeConfig as UnexpectedType;
219
220 match extract_field_from_endpoint_config("signingName", endpoint_config) {
221 Some(Document::String(s)) => Ok(Some(SigningName::from(s.to_string()))),
222 None => Ok(None),
223 _ => Err(UnexpectedType("signingName")),
224 }
225}
226
227fn extract_endpoint_auth_scheme_signing_region(
228 endpoint_config: &AuthSchemeEndpointConfig<'_>,
229) -> Result<Option<SigningRegion>, SigV4SigningError> {
230 use SigV4SigningError::BadTypeInEndpointAuthSchemeConfig as UnexpectedType;
231
232 match extract_field_from_endpoint_config("signingRegion", endpoint_config) {
233 Some(Document::String(s)) => Ok(Some(SigningRegion::from(Region::new(s.clone())))),
234 None => Ok(None),
235 _ => Err(UnexpectedType("signingRegion")),
236 }
237}
238
239fn extract_field_from_endpoint_config<'a>(
240 field_name: &'static str,
241 endpoint_config: &'a AuthSchemeEndpointConfig<'_>,
242) -> Option<&'a Document> {
243 endpoint_config
244 .as_document()
245 .and_then(Document::as_object)
246 .and_then(|config| config.get(field_name))
247}
248
249fn apply_signing_instructions(
250 instructions: SigningInstructions,
251 request: &mut HttpRequest,
252) -> Result<(), BoxError> {
253 let (new_headers, new_query) = instructions.into_parts();
254 for header in new_headers.into_iter() {
255 let mut value = http_02x::HeaderValue::from_str(header.value()).unwrap();
256 value.set_sensitive(header.sensitive());
257 request.headers_mut().insert(header.name(), value);
258 }
259
260 if !new_query.is_empty() {
261 let mut query = aws_smithy_http::query_writer::QueryWriter::new_from_string(request.uri())?;
262 for (name, value) in new_query {
263 query.insert(name, &value);
264 }
265 request.set_uri(query.build_uri())?;
266 }
267 Ok(())
268}
269
270#[non_exhaustive]
273#[derive(Clone, Debug)]
274pub enum PayloadSigningOverride {
275 UnsignedPayload,
280
281 Precomputed(String),
285
286 StreamingUnsignedPayloadTrailer,
288}
289
290impl PayloadSigningOverride {
291 pub fn unsigned_payload() -> Self {
294 Self::UnsignedPayload
295 }
296
297 pub fn to_signable_body(self) -> SignableBody<'static> {
300 match self {
301 Self::UnsignedPayload => SignableBody::UnsignedPayload,
302 Self::Precomputed(checksum) => SignableBody::Precomputed(checksum),
303 Self::StreamingUnsignedPayloadTrailer => SignableBody::StreamingUnsignedPayloadTrailer,
304 }
305 }
306}
307
308impl Storable for PayloadSigningOverride {
309 type Storer = StoreReplace<Self>;
310}
311
312#[derive(Debug)]
314pub struct PayloadSigningOverrideRuntimePlugin {
315 inner: FrozenLayer,
316}
317
318impl PayloadSigningOverrideRuntimePlugin {
319 pub fn unsigned() -> Self {
322 let mut layer = Layer::new("PayloadSigningOverrideRuntimePlugin");
323 layer.store_put(PayloadSigningOverride::UnsignedPayload);
324
325 Self {
326 inner: layer.freeze(),
327 }
328 }
329}
330
331impl RuntimePlugin for PayloadSigningOverrideRuntimePlugin {
332 fn config(&self) -> Option<FrozenLayer> {
333 Some(self.inner.clone())
334 }
335}