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_types::config_bag::{ConfigBag, Storable, StoreReplace};
15use aws_smithy_types::Document;
16use aws_types::region::{Region, SigningRegion, SigningRegionSet};
17use aws_types::SigningName;
18use std::error::Error as StdError;
19use std::fmt;
20use std::time::Duration;
21
22pub mod sigv4;
24
25#[cfg(feature = "sigv4a")]
26pub mod sigv4a;
28
29#[derive(Debug, Eq, PartialEq, Clone, Copy)]
31pub enum HttpSignatureType {
32 HttpRequestHeaders,
34
35 HttpRequestQueryParams,
39}
40
41#[derive(Clone, Debug, Eq, PartialEq)]
43#[non_exhaustive]
44pub struct SigningOptions {
45 pub double_uri_encode: bool,
47 pub content_sha256_header: bool,
49 pub normalize_uri_path: bool,
51 pub omit_session_token: bool,
53 pub payload_override: Option<SignableBody<'static>>,
55 pub signature_type: HttpSignatureType,
57 pub signing_optional: bool,
59 pub expires_in: Option<Duration>,
61}
62
63impl Default for SigningOptions {
64 fn default() -> Self {
65 Self {
66 double_uri_encode: true,
67 content_sha256_header: false,
68 normalize_uri_path: true,
69 omit_session_token: false,
70 payload_override: None,
71 signature_type: HttpSignatureType::HttpRequestHeaders,
72 signing_optional: false,
73 expires_in: None,
74 }
75 }
76}
77
78pub(crate) type SessionTokenNameOverrideFn = Box<
79 dyn Fn(&SigningSettings, &ConfigBag) -> Result<Option<&'static str>, BoxError>
80 + Send
81 + Sync
82 + 'static,
83>;
84
85pub struct SigV4SessionTokenNameOverride {
87 name_override: SessionTokenNameOverrideFn,
88}
89
90impl SigV4SessionTokenNameOverride {
91 pub fn new<F>(name_override: F) -> Self
93 where
94 F: Fn(&SigningSettings, &ConfigBag) -> Result<Option<&'static str>, BoxError>
95 + Send
96 + Sync
97 + 'static,
98 {
99 Self {
100 name_override: Box::new(name_override),
101 }
102 }
103
104 pub fn name_override(
106 &self,
107 settings: &SigningSettings,
108 config_bag: &ConfigBag,
109 ) -> Result<Option<&'static str>, BoxError> {
110 (self.name_override)(settings, config_bag)
111 }
112}
113
114impl fmt::Debug for SigV4SessionTokenNameOverride {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.debug_struct("SessionTokenNameOverride").finish()
117 }
118}
119
120impl Storable for SigV4SessionTokenNameOverride {
121 type Storer = StoreReplace<Self>;
122}
123
124#[derive(Clone, Debug, Default, PartialEq, Eq)]
129pub struct SigV4OperationSigningConfig {
130 pub region: Option<SigningRegion>,
134 pub region_set: Option<SigningRegionSet>,
138 pub name: Option<SigningName>,
140 pub signing_options: SigningOptions,
142}
143
144impl Storable for SigV4OperationSigningConfig {
145 type Storer = StoreReplace<Self>;
146}
147
148fn settings(operation_config: &SigV4OperationSigningConfig) -> SigningSettings {
149 let mut settings = SigningSettings::default();
150 settings.percent_encoding_mode = if operation_config.signing_options.double_uri_encode {
151 PercentEncodingMode::Double
152 } else {
153 PercentEncodingMode::Single
154 };
155 settings.payload_checksum_kind = if operation_config.signing_options.content_sha256_header {
156 PayloadChecksumKind::XAmzSha256
157 } else {
158 PayloadChecksumKind::NoHeader
159 };
160 settings.uri_path_normalization_mode = if operation_config.signing_options.normalize_uri_path {
161 UriPathNormalizationMode::Enabled
162 } else {
163 UriPathNormalizationMode::Disabled
164 };
165 settings.session_token_mode = if operation_config.signing_options.omit_session_token {
166 SessionTokenMode::Exclude
167 } else {
168 SessionTokenMode::Include
169 };
170 settings.signature_location = match operation_config.signing_options.signature_type {
171 HttpSignatureType::HttpRequestHeaders => SignatureLocation::Headers,
172 HttpSignatureType::HttpRequestQueryParams => SignatureLocation::QueryParams,
173 };
174 settings.expires_in = operation_config.signing_options.expires_in;
175 settings
176}
177
178#[derive(Debug)]
179enum SigV4SigningError {
180 MissingOperationSigningConfig,
181 MissingSigningRegion,
182 #[cfg(feature = "sigv4a")]
183 MissingSigningRegionSet,
184 MissingSigningName,
185 WrongIdentityType(Identity),
186 BadTypeInEndpointAuthSchemeConfig(&'static str),
187}
188
189impl fmt::Display for SigV4SigningError {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 use SigV4SigningError::*;
192 let mut w = |s| f.write_str(s);
193 match self {
194 MissingOperationSigningConfig => w("missing operation signing config"),
195 MissingSigningRegion => w("missing signing region"),
196 #[cfg(feature = "sigv4a")]
197 MissingSigningRegionSet => w("missing signing region set"),
198 MissingSigningName => w("missing signing name"),
199 WrongIdentityType(identity) => {
200 write!(f, "wrong identity type for SigV4/sigV4a. Expected AWS credentials but got `{identity:?}`")
201 }
202 BadTypeInEndpointAuthSchemeConfig(field_name) => {
203 write!(
204 f,
205 "unexpected type for `{field_name}` in endpoint auth scheme config",
206 )
207 }
208 }
209 }
210}
211
212impl StdError for SigV4SigningError {}
213
214fn extract_endpoint_auth_scheme_signing_name(
215 endpoint_config: &AuthSchemeEndpointConfig<'_>,
216) -> Result<Option<SigningName>, SigV4SigningError> {
217 use SigV4SigningError::BadTypeInEndpointAuthSchemeConfig as UnexpectedType;
218
219 match extract_field_from_endpoint_config("signingName", endpoint_config) {
220 Some(Document::String(s)) => Ok(Some(SigningName::from(s.to_string()))),
221 None => Ok(None),
222 _ => Err(UnexpectedType("signingName")),
223 }
224}
225
226fn extract_endpoint_auth_scheme_signing_region(
227 endpoint_config: &AuthSchemeEndpointConfig<'_>,
228) -> Result<Option<SigningRegion>, SigV4SigningError> {
229 use SigV4SigningError::BadTypeInEndpointAuthSchemeConfig as UnexpectedType;
230
231 match extract_field_from_endpoint_config("signingRegion", endpoint_config) {
232 Some(Document::String(s)) => Ok(Some(SigningRegion::from(Region::new(s.clone())))),
233 None => Ok(None),
234 _ => Err(UnexpectedType("signingRegion")),
235 }
236}
237
238fn extract_field_from_endpoint_config<'a>(
239 field_name: &'static str,
240 endpoint_config: &'a AuthSchemeEndpointConfig<'_>,
241) -> Option<&'a Document> {
242 endpoint_config
243 .as_document()
244 .and_then(Document::as_object)
245 .and_then(|config| config.get(field_name))
246}
247
248fn apply_signing_instructions(
249 instructions: SigningInstructions,
250 request: &mut HttpRequest,
251) -> Result<(), BoxError> {
252 let (new_headers, new_query) = instructions.into_parts();
253 for header in new_headers.into_iter() {
254 let mut value = http_02x::HeaderValue::from_str(header.value()).unwrap();
255 value.set_sensitive(header.sensitive());
256 request.headers_mut().insert(header.name(), value);
257 }
258
259 if !new_query.is_empty() {
260 let mut query = aws_smithy_http::query_writer::QueryWriter::new_from_string(request.uri())?;
261 for (name, value) in new_query {
262 query.insert(name, &value);
263 }
264 request.set_uri(query.build_uri())?;
265 }
266 Ok(())
267}