AWS SDK for C++

AWS SDK for C++ Version 1.11.827

Loading...
Searching...
No Matches
SigV4aSigner.h
1
6#pragma once
7
8#include <smithy/identity/signer/AwsSignerBase.h>
9#include <smithy/identity/identity/AwsCredentialIdentityBase.h>
10#include <aws/core/auth/AWSCredentials.h>
11#include <aws/crt/auth/Credentials.h>
12
13#include <aws/core/http/HttpRequest.h>
14#include <aws/core/auth/signer/AWSAuthSignerHelper.h>
15#include <aws/crt/http/HttpConnection.h>
16#include <aws/crt/http/HttpRequestResponse.h>
17#include <condition_variable>
18#include <mutex>
19#include <smithy/identity/signer/built-in/SignerProperties.h>
20#include <smithy/identity/auth/AuthSchemeResolverBase.h>
21
22namespace smithy {
23 static const char* UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
24 static const char* EMPTY_STRING_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
25 static const char v4AsymmetricLogTag[] = "AWSAuthSymmetricV4Signer";
26 static const char* USER_AGENT = "user-agent";
27 static const char* X_AMZN_TRACE_ID = "x-amzn-trace-id";
28
33 class AwsSigV4aSigner : public AwsSignerBase<AwsCredentialIdentityBase> {
34
35 public:
37 explicit AwsSigV4aSigner(const Aws::String& serviceName, const Aws::String& region)
38 : m_serviceName(serviceName), m_region(region)
39 {
40 }
41
42 //for legacy constructors
44 : m_serviceName(serviceName), m_region(region), m_policy(Aws::MakeShared<Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy>("AwsSigV4aSigner", policy))
45 {
46 }
47
48 explicit AwsSigV4aSigner(const Aws::String& serviceName, const Aws::String& region, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy policy, bool urlEscape)
49 : m_serviceName(serviceName), m_region(region), m_policy(Aws::MakeShared<Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy>("AwsSigV4aSigner", policy)), m_urlEscape{urlEscape}
50 {
51 }
52
53 SigningFutureOutcome sign(std::shared_ptr<HttpRequest> httpRequest, const AwsCredentialIdentityBase& identity, SigningProperties properties) override
54 {
55 return sign(httpRequest, identity, properties, m_region, m_serviceName, m_expirationTimeInSeconds, Aws::Crt::Auth::SignatureType::HttpRequestViaHeaders);
56 }
57
58 SigningFutureOutcome presign(std::shared_ptr<HttpRequest> httpRequest, const AwsCredentialIdentityBase& identity, SigningProperties properties, const Aws::String& region, const Aws::String& serviceName, long long expirationTimeInSeconds) override
59 {
60 Aws::String signingRegion = !region.empty() ? region : m_region;
61 Aws::String signingServiceName = !serviceName.empty() ? serviceName : m_serviceName;
62 Aws::Crt::Auth::SignatureType signatureType = Aws::Crt::Auth::SignatureType::HttpRequestViaQueryParams;
63
64 const auto credentials = [&identity]() -> Aws::Auth::AWSCredentials {
65 if(identity.sessionToken().has_value() && identity.expiration().has_value())
66 {
67 return {identity.accessKeyId(), identity.secretAccessKey(), *identity.sessionToken(), *identity.expiration()};
68 }
69 if(identity.sessionToken().has_value())
70 {
71 return {identity.accessKeyId(), identity.secretAccessKey(), *identity.sessionToken()};
72 }
73 return {identity.accessKeyId(), identity.secretAccessKey()};
74 }();
75
76 //don't sign anonymous requests
77 if (credentials.GetAWSAccessKeyId().empty() || credentials.GetAWSSecretKey().empty())
78 {
79 return SigningFutureOutcome(std::move(httpRequest)) ;
80 }
81 return sign(httpRequest, identity, properties, signingRegion, signingServiceName, expirationTimeInSeconds, signatureType);
82 }
83
84 virtual ~AwsSigV4aSigner() {};
85 protected:
86
88 std::shared_ptr<Aws::Crt::Auth::Credentials>& crtCredentials,
89 const Aws::Http::HttpRequest& request,
90 const Aws::String& serviceName,
91 const Aws::String& region,
92 Aws::Crt::Auth::AwsSigningConfig& awsSigningConfig,
93 bool signBody,
94 Aws::Crt::Auth::SignatureType signatureType) const
95 {
96 awsSigningConfig.SetSigningAlgorithm(static_cast<Aws::Crt::Auth::SigningAlgorithm>(Aws::Auth::AWSSigningAlgorithm::ASYMMETRIC_SIGV4));
97 awsSigningConfig.SetSignatureType(signatureType);
98 awsSigningConfig.SetRegion(serviceName.c_str());
99 awsSigningConfig.SetService(region.c_str());
100 awsSigningConfig.SetSigningTimepoint(GetSigningTimestamp().UnderlyingTimestamp());
101 awsSigningConfig.SetUseDoubleUriEncode(m_urlEscape);
102 awsSigningConfig.SetShouldNormalizeUriPath(true);
103 awsSigningConfig.SetOmitSessionToken(false);
104 awsSigningConfig.SetShouldSignHeaderUserData(reinterpret_cast<void*>(const_cast<Aws::Set<Aws::String>*>(&m_unsignedHeaders)));
105 awsSigningConfig.SetShouldSignHeaderCallback([](const Aws::Crt::ByteCursor *name, void *user_data) {
106 Aws::Set<Aws::String>* unsignedHeaders = static_cast<Aws::Set<Aws::String>*>(user_data);
107 Aws::String headerKey(reinterpret_cast<const char*>(name->ptr), name->len);
108 return unsignedHeaders->find(Aws::Utils::StringUtils::ToLower(headerKey.c_str())) == unsignedHeaders->cend();
109 });
110 if (signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaHeaders)
111 {
112 Aws::String payloadHash(UNSIGNED_PAYLOAD);
113 if(signBody || request.GetUri().GetScheme() != Aws::Http::Scheme::HTTPS)
114 {
115 if (!request.GetContentBody())
116 {
117 AWS_LOGSTREAM_DEBUG(v4AsymmetricLogTag, "Using cached empty string sha256 " << EMPTY_STRING_SHA256 << " because payload is empty.");
118 payloadHash = EMPTY_STRING_SHA256;
119 }
120 else
121 {
122 // The hash will be calculated from the payload during signing.
123 payloadHash = {};
124 }
125 }
126 else
127 {
128 AWS_LOGSTREAM_DEBUG(v4AsymmetricLogTag, "Note: Http payloads are not being signed. signPayloads=" << signBody
129 << " http scheme=" << Aws::Http::SchemeMapper::ToString(request.GetUri().GetScheme()));
130 }
131 awsSigningConfig.SetSignedBodyValue(payloadHash.c_str());
132 awsSigningConfig.SetSignedBodyHeader(m_includeSha256HashHeader ? Aws::Crt::Auth::SignedBodyHeaderType::XAmzContentSha256 : Aws::Crt::Auth::SignedBodyHeaderType::None);
133 }
134 else if (signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaQueryParams)
135 {
136 if (ServiceRequireUnsignedPayload(serviceName))
137 {
138 awsSigningConfig.SetSignedBodyValue(UNSIGNED_PAYLOAD);
139 }
140 else
141 {
142 awsSigningConfig.SetSignedBodyValue(EMPTY_STRING_SHA256);
143 }
144 }
145 else
146 {
147 AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, "The signature type should be either \"HttpRequestViaHeaders\" or \"HttpRequestViaQueryParams\"");
148 return false;
149 }
150 awsSigningConfig.SetExpirationInSeconds(static_cast<uint64_t>(m_expirationTimeInSeconds));
151 awsSigningConfig.SetCredentials(crtCredentials);
152 return true;
153 }
154
155
156 SigningFutureOutcome sign(std::shared_ptr<HttpRequest> httpRequest, const AwsCredentialIdentityBase& identity, SigningProperties properties, const Aws::String& region, const Aws::String& svcName, long long expirationTimeInSeconds,
157 Aws::Crt::Auth::SignatureType signatureType)
158 {
159 assert(httpRequest);
160
161 const auto legacyCreds = [&identity]() -> Aws::Auth::AWSCredentials {
162 if(identity.sessionToken().has_value() && identity.expiration().has_value())
163 {
164 return {identity.accessKeyId(), identity.secretAccessKey(), *identity.sessionToken(), *identity.expiration()};
165 }
166 if(identity.sessionToken().has_value())
167 {
168 return {identity.accessKeyId(), identity.secretAccessKey(), *identity.sessionToken()};
169 }
170 return {identity.accessKeyId(), identity.secretAccessKey()};
171 }();
172
173 auto signPayloadIt = properties.find("SignPayload");
174 bool signPayload = signPayloadIt == properties.end() ? true : signPayloadIt->second.get<Aws::String>() == "true";
175
176 auto signerRegionOverrideIt = properties.find(smithy::SIGNER_REGION_PROPERTY);
177 auto regionOverride = signerRegionOverrideIt != properties.end() ? signerRegionOverrideIt->second.get<Aws::String>().c_str() : region.c_str();
178
179 auto signerServiceNameOverrideIt = properties.find(smithy::SIGNER_SERVICE_NAME);
180 auto serviceName = signerServiceNameOverrideIt != properties.end() ? signerServiceNameOverrideIt->second.get<Aws::String>().c_str() : svcName.c_str();
181
182 auto &request = *httpRequest;
183
184 //don't sign anonymous requests
185 if (legacyCreds.GetAWSAccessKeyId().empty() || legacyCreds.GetAWSSecretKey().empty())
186 {
187 return SigningFutureOutcome(std::move(httpRequest));
188 }
189
190 httpRequest->SetSigningAccessKey(legacyCreds.GetAWSAccessKeyId());
191 httpRequest->SetSigningRegion(regionOverride);
192
193 //Expiration should always have a value, since we default construct it to std::chrono::time_point<std::chrono::system_clock>::max in AWSCredentials.h
194 //SessionToken will be null for long-term credentials
195 auto crtCredentials = Aws::MakeShared<Aws::Crt::Auth::Credentials>(v4AsymmetricLogTag,
196 Aws::Crt::ByteCursorFromCString(identity.accessKeyId().c_str()),
197 Aws::Crt::ByteCursorFromCString(identity.secretAccessKey().c_str()),
198 Aws::Crt::ByteCursorFromCString(identity.sessionToken().has_value() ? identity.sessionToken()->c_str() : ""),
199 identity.expiration().has_value() ? identity.expiration()->Seconds() : UINT64_MAX);
200
201 Aws::Crt::Auth::AwsSigningConfig awsSigningConfig;
202
203 bool success = createAwsSigningConfig(crtCredentials, request, serviceName, regionOverride, awsSigningConfig, signPayload, signatureType);
204
205 if(!success)
206 {
207 AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, "Failed to get Auth configuration");
208
209 return SigningError(Aws::Client::CoreErrors::MEMORY_ALLOCATION, "", "Failed to get Auth configuration", false);
210 }
211
212 awsSigningConfig.SetRegion(regionOverride);
213 awsSigningConfig.SetService(serviceName);
214 awsSigningConfig.SetExpirationInSeconds(expirationTimeInSeconds);
215
216 std::shared_ptr<Aws::Crt::Http::HttpRequest> crtHttpRequest = request.ToCrtHttpRequest();
217
218 auto sigv4HttpRequestSigner = Aws::MakeShared<Aws::Crt::Auth::Sigv4HttpRequestSigner>(v4AsymmetricLogTag);
219 //This is an async call, so we need to wait till we have received an outcome
220 Aws::String errorMessage;
221 bool processed = false;
222 //producer function
223 sigv4HttpRequestSigner->SignRequest(crtHttpRequest, awsSigningConfig,
224 [&request, &success, &errorMessage, &processed, this, signatureType](const std::shared_ptr<Aws::Crt::Http::HttpRequest>& signedCrtHttpRequest, int errorCode) {
225 std::unique_lock<std::mutex> lock(m_mutex);
226 m_cv.wait(lock, [&]{ return !processed; });
227 success = (errorCode == AWS_ERROR_SUCCESS);
228 if (success)
229 {
230 if (signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaHeaders)
231 {
232 for (size_t i = 0; i < signedCrtHttpRequest->GetHeaderCount(); i++)
233 {
234 Aws::Crt::Optional<Aws::Crt::Http::HttpHeader> httpHeader = signedCrtHttpRequest->GetHeader(i);
235 request.SetHeaderValue(Aws::String(reinterpret_cast<const char*>(httpHeader->name.ptr), httpHeader->name.len),
236 Aws::String(reinterpret_cast<const char*>(httpHeader->value.ptr), httpHeader->value.len));
237 }
238 }
239 else if (signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaQueryParams)
240 {
241 Aws::Http::URI newPath(reinterpret_cast<const char*>(signedCrtHttpRequest->GetPath()->ptr));
242 request.GetUri().SetQueryString(newPath.GetQueryString());
243 }
244 else
245 {
246 errorMessage = "No action to take when signature type is neither \"HttpRequestViaHeaders\" nor \"HttpRequestViaQueryParams\"";
247 AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, errorMessage);
248 success = false;
249 }
250 }
251 else
252 {
253 Aws::OStringStream errStream;
254 errStream << "Encountered internal error during signing process with AWS signature version 4 (Asymmetric):" << aws_error_str(errorCode);
255 errorMessage = errStream.str();
256 AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, errorMessage);
257 }
258
259 processed = true;
260 m_cv.notify_all();
261 }
262 );
263
264 //consumer
265 {
266 std::unique_lock<std::mutex> lock(m_mutex);
267 m_cv.wait(lock, [&]{ return processed; });
268
269 }
270
271 return success? SigningFutureOutcome(std::move(httpRequest)) : SigningError(Aws::Client::CoreErrors::MEMORY_ALLOCATION, "", "Failed to sign the request with sigv4", false);
272
273 }
274
275 bool ServiceRequireUnsignedPayload(const Aws::String& serviceName) const
276 {
277 // S3 uses a magic string (instead of the empty string) for its body hash for presigned URLs as outlined here:
278 // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
279 // this is true for PUT, POST, GET, DELETE and HEAD operations.
280 // However, other services (for example RDS) implement the specification as outlined here:
281 // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
282 // which states that body-less requests should use the empty-string SHA256 hash.
283 return "s3" == serviceName || "s3-object-lambda" == serviceName;
284 }
285
288 std::shared_ptr<Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy> m_policy;
289 //params that can be exposed later
292 const bool m_urlEscape{true};
294 const Aws::Crt::Auth::SignatureType m_signatureType{Aws::Crt::Auth::SignatureType::HttpRequestViaHeaders};
295 std::condition_variable m_cv;
296 std::mutex m_mutex;
297 };
298}
virtual const std::shared_ptr< Aws::IOStream > & GetContentBody() const =0
const Aws::String & GetQueryString() const
Definition URI.h:162
Scheme GetScheme() const
Definition URI.h:66
static Aws::String ToLower(const char *source)
virtual Aws::String accessKeyId() const =0
virtual Aws::Crt::Optional< AwsIdentity::DateTime > expiration() const override=0
virtual Aws::String secretAccessKey() const =0
virtual Aws::Crt::Optional< Aws::String > sessionToken() const =0
std::condition_variable m_cv
const Aws::Crt::Auth::SignatureType m_signatureType
bool createAwsSigningConfig(std::shared_ptr< Aws::Crt::Auth::Credentials > &crtCredentials, const Aws::Http::HttpRequest &request, const Aws::String &serviceName, const Aws::String &region, Aws::Crt::Auth::AwsSigningConfig &awsSigningConfig, bool signBody, Aws::Crt::Auth::SignatureType signatureType) const
bool ServiceRequireUnsignedPayload(const Aws::String &serviceName) const
std::shared_ptr< Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy > m_policy
AwsSigV4aSigner(const Aws::String &serviceName, const Aws::String &region, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy policy)
AwsSigV4aSigner(const Aws::String &serviceName, const Aws::String &region)
const Aws::Set< Aws::String > m_unsignedHeaders
long long m_expirationTimeInSeconds
const bool m_includeSha256HashHeader
AwsSigV4aSigner(const Aws::String &serviceName, const Aws::String &region, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy policy, bool urlEscape)
SigningFutureOutcome sign(std::shared_ptr< HttpRequest > httpRequest, const AwsCredentialIdentityBase &identity, SigningProperties properties) override
SigningFutureOutcome presign(std::shared_ptr< HttpRequest > httpRequest, const AwsCredentialIdentityBase &identity, SigningProperties properties, const Aws::String &region, const Aws::String &serviceName, long long expirationTimeInSeconds) override
SigningFutureOutcome sign(std::shared_ptr< HttpRequest > httpRequest, const AwsCredentialIdentityBase &identity, SigningProperties properties, const Aws::String &region, const Aws::String &svcName, long long expirationTimeInSeconds, Aws::Crt::Auth::SignatureType signatureType)
Aws::Client::AWSError< Aws::Client::CoreErrors > SigningError
Aws::Utils::FutureOutcome< std::shared_ptr< HttpRequest >, SigningError > SigningFutureOutcome
Aws::UnorderedMap< Aws::String, Aws::Crt::Variant< Aws::String, bool > > SigningProperties
virtual Aws::Utils::DateTime GetSigningTimestamp() const
AWS_CORE_API const char * ToString(Scheme scheme)
std::basic_string< char, std::char_traits< char >, Aws::Allocator< char > > String
Definition AWSString.h:97
std::set< T, std::less< T >, Aws::Allocator< T > > Set
Definition AWSSet.h:18
std::basic_ostringstream< char, std::char_traits< char >, Aws::Allocator< char > > OStringStream
static const char v4AsymmetricLogTag[]
SMITHY_API const char * SIGNER_REGION_PROPERTY
static const char * UNSIGNED_PAYLOAD
static const char * EMPTY_STRING_SHA256
SMITHY_API const char * SIGNER_SERVICE_NAME
static const char * X_AMZN_TRACE_ID
static const char * USER_AGENT