1 | /* |
2 | * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"). |
5 | * You may not use this file except in compliance with the License. |
6 | * A copy of the License is located at |
7 | * |
8 | * http://aws.amazon.com/apache2.0 |
9 | * |
10 | * or in the "license" file accompanying this file. This file is distributed |
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
12 | * express or implied. See the License for the specific language governing |
13 | * permissions and limitations under the License. |
14 | */ |
15 | |
16 | #include <aws/core/auth/AWSAuthSigner.h> |
17 | |
18 | #include <aws/core/auth/AWSCredentialsProvider.h> |
19 | #include <aws/core/client/ClientConfiguration.h> |
20 | #include <aws/core/http/HttpRequest.h> |
21 | #include <aws/core/http/HttpResponse.h> |
22 | #include <aws/core/utils/DateTime.h> |
23 | #include <aws/core/utils/HashingUtils.h> |
24 | #include <aws/core/utils/Outcome.h> |
25 | #include <aws/core/utils/StringUtils.h> |
26 | #include <aws/core/utils/logging/LogMacros.h> |
27 | #include <aws/core/utils/memory/AWSMemory.h> |
28 | #include <aws/core/utils/crypto/Sha256.h> |
29 | #include <aws/core/utils/crypto/Sha256HMAC.h> |
30 | #include <aws/core/utils/stream/PreallocatedStreamBuf.h> |
31 | #include <aws/core/utils/event/EventMessage.h> |
32 | #include <aws/core/utils/event/EventHeader.h> |
33 | |
34 | #include <cstdio> |
35 | #include <iomanip> |
36 | #include <math.h> |
37 | #include <cstring> |
38 | |
39 | using namespace Aws; |
40 | using namespace Aws::Client; |
41 | using namespace Aws::Auth; |
42 | using namespace Aws::Http; |
43 | using namespace Aws::Utils; |
44 | using namespace Aws::Utils::Logging; |
45 | |
46 | static const char* EQ = "=" ; |
47 | static const char* AWS_HMAC_SHA256 = "AWS4-HMAC-SHA256" ; |
48 | static const char* EVENT_STREAM_CONTENT_SHA256 = "STREAMING-AWS4-HMAC-SHA256-EVENTS" ; |
49 | static const char* EVENT_STREAM_PAYLOAD = "AWS4-HMAC-SHA256-PAYLOAD" ; |
50 | static const char* AWS4_REQUEST = "aws4_request" ; |
51 | static const char* = "SignedHeaders" ; |
52 | static const char* CREDENTIAL = "Credential" ; |
53 | static const char* NEWLINE = "\n" ; |
54 | static const char* = "X-Amz-SignedHeaders" ; |
55 | static const char* X_AMZ_ALGORITHM = "X-Amz-Algorithm" ; |
56 | static const char* X_AMZ_CREDENTIAL = "X-Amz-Credential" ; |
57 | static const char* UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD" ; |
58 | static const char* X_AMZ_SIGNATURE = "X-Amz-Signature" ; |
59 | static const char* X_AMZN_TRACE_ID = "x-amzn-trace-id" ; |
60 | static const char* X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256" ; |
61 | static const char* USER_AGENT = "user-agent" ; |
62 | static const char* SIGNING_KEY = "AWS4" ; |
63 | static const char* LONG_DATE_FORMAT_STR = "%Y%m%dT%H%M%SZ" ; |
64 | static const char* SIMPLE_DATE_FORMAT_STR = "%Y%m%d" ; |
65 | static const char* EMPTY_STRING_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ; |
66 | |
67 | static const char v4LogTag[] = "AWSAuthV4Signer" ; |
68 | static const char v4StreamingLogTag[] = "AWSAuthEventStreamV4Signer" ; |
69 | |
70 | namespace Aws |
71 | { |
72 | namespace Auth |
73 | { |
74 | const char SIGNATURE[] = "Signature" ; |
75 | const char SIGV4_SIGNER[] = "SignatureV4" ; |
76 | const char EVENTSTREAM_SIGV4_SIGNER[] = "EventStreamSignatureV4" ; |
77 | const char [] = ":chunk-signature" ; |
78 | const char [] = ":date" ; |
79 | const char NULL_SIGNER[] = "NullSigner" ; |
80 | } |
81 | } |
82 | |
83 | static Aws::String CanonicalizeRequestSigningString(HttpRequest& request, bool urlEscapePath) |
84 | { |
85 | request.CanonicalizeRequest(); |
86 | Aws::StringStream signingStringStream; |
87 | signingStringStream << HttpMethodMapper::GetNameForHttpMethod(request.GetMethod()); |
88 | |
89 | URI uriCpy = request.GetUri(); |
90 | // Many AWS services do not decode the URL before calculating SignatureV4 on their end. |
91 | // This results in the signature getting calculated with a double encoded URL. |
92 | // That means we have to double encode it here for the signature to match on the service side. |
93 | if(urlEscapePath) |
94 | { |
95 | // RFC3986 is how we encode the URL before sending it on the wire. |
96 | auto rfc3986EncodedPath = URI::URLEncodePathRFC3986(uriCpy.GetPath()); |
97 | uriCpy.SetPath(rfc3986EncodedPath); |
98 | // However, SignatureV4 uses this URL encoding scheme |
99 | signingStringStream << NEWLINE << uriCpy.GetURLEncodedPath() << NEWLINE; |
100 | } |
101 | else |
102 | { |
103 | // For the services that DO decode the URL first; we don't need to double encode it. |
104 | uriCpy.SetPath(uriCpy.GetURLEncodedPath()); |
105 | signingStringStream << NEWLINE << uriCpy.GetPath() << NEWLINE; |
106 | } |
107 | |
108 | if (request.GetQueryString().find('=') != std::string::npos) |
109 | { |
110 | signingStringStream << request.GetQueryString().substr(1) << NEWLINE; |
111 | } |
112 | else if (request.GetQueryString().size() > 1) |
113 | { |
114 | signingStringStream << request.GetQueryString().substr(1) << "=" << NEWLINE; |
115 | } |
116 | else |
117 | { |
118 | signingStringStream << NEWLINE; |
119 | } |
120 | |
121 | return signingStringStream.str(); |
122 | } |
123 | |
124 | static Http::HeaderValueCollection (Http::HeaderValueCollection&& ) |
125 | { |
126 | Http::HeaderValueCollection ; |
127 | for (const auto& : headers) |
128 | { |
129 | auto = StringUtils::Trim(header.first.c_str()); |
130 | auto = StringUtils::Trim(header.second.c_str()); |
131 | |
132 | //multiline gets converted to line1,line2,etc... |
133 | auto = StringUtils::SplitOnLine(trimmedHeaderValue); |
134 | Aws::String = headerMultiLine.size() == 0 ? "" : headerMultiLine[0]; |
135 | |
136 | if (headerMultiLine.size() > 1) |
137 | { |
138 | for(size_t i = 1; i < headerMultiLine.size(); ++i) |
139 | { |
140 | headerValue += "," ; |
141 | headerValue += StringUtils::Trim(headerMultiLine[i].c_str()); |
142 | } |
143 | } |
144 | |
145 | //duplicate spaces need to be converted to one. |
146 | Aws::String::iterator new_end = |
147 | std::unique(headerValue.begin(), headerValue.end(), |
148 | [=](char lhs, char rhs) { return (lhs == rhs) && (lhs == ' '); } |
149 | ); |
150 | headerValue.erase(new_end, headerValue.end()); |
151 | |
152 | canonicalHeaders[trimmedHeaderName] = headerValue; |
153 | } |
154 | |
155 | return canonicalHeaders; |
156 | } |
157 | |
158 | AWSAuthV4Signer::AWSAuthV4Signer(const std::shared_ptr<Auth::AWSCredentialsProvider>& credentialsProvider, |
159 | const char* serviceName, const Aws::String& region, PayloadSigningPolicy signingPolicy, bool urlEscapePath) : |
160 | m_includeSha256HashHeader(true), |
161 | m_credentialsProvider(credentialsProvider), |
162 | m_serviceName(serviceName), |
163 | m_region(region), |
164 | m_hash(Aws::MakeUnique<Aws::Utils::Crypto::Sha256>(v4LogTag)), |
165 | m_HMAC(Aws::MakeUnique<Aws::Utils::Crypto::Sha256HMAC>(v4LogTag)), |
166 | m_unsignedHeaders({USER_AGENT, X_AMZN_TRACE_ID}), |
167 | m_payloadSigningPolicy(signingPolicy), |
168 | m_urlEscapePath(urlEscapePath) |
169 | { |
170 | //go ahead and warm up the signing cache. |
171 | ComputeHash(credentialsProvider->GetAWSCredentials().GetAWSSecretKey(), DateTime::CalculateGmtTimestampAsString(SIMPLE_DATE_FORMAT_STR), region, m_serviceName); |
172 | } |
173 | |
174 | AWSAuthV4Signer::~AWSAuthV4Signer() |
175 | { |
176 | // empty destructor in .cpp file to keep from needing the implementation of (AWSCredentialsProvider, Sha256, Sha256HMAC) in the header file |
177 | } |
178 | |
179 | |
180 | bool AWSAuthV4Signer::(const Aws::String& ) const |
181 | { |
182 | return m_unsignedHeaders.find(Aws::Utils::StringUtils::ToLower(header.c_str())) == m_unsignedHeaders.cend(); |
183 | } |
184 | |
185 | bool AWSAuthV4Signer::SignRequest(Aws::Http::HttpRequest& request, const char* region, bool signBody) const |
186 | { |
187 | AWSCredentials credentials = m_credentialsProvider->GetAWSCredentials(); |
188 | |
189 | //don't sign anonymous requests |
190 | if (credentials.GetAWSAccessKeyId().empty() || credentials.GetAWSSecretKey().empty()) |
191 | { |
192 | return true; |
193 | } |
194 | |
195 | if (!credentials.GetSessionToken().empty()) |
196 | { |
197 | request.SetAwsSessionToken(credentials.GetSessionToken()); |
198 | } |
199 | |
200 | Aws::String payloadHash(UNSIGNED_PAYLOAD); |
201 | switch(m_payloadSigningPolicy) |
202 | { |
203 | case PayloadSigningPolicy::Always: |
204 | signBody = true; |
205 | break; |
206 | case PayloadSigningPolicy::Never: |
207 | signBody = false; |
208 | break; |
209 | case PayloadSigningPolicy::RequestDependent: |
210 | // respect the request setting |
211 | default: |
212 | break; |
213 | } |
214 | |
215 | if(signBody || request.GetUri().GetScheme() != Http::Scheme::HTTPS) |
216 | { |
217 | payloadHash = ComputePayloadHash(request); |
218 | if (payloadHash.empty()) |
219 | { |
220 | return false; |
221 | } |
222 | } |
223 | else |
224 | { |
225 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Note: Http payloads are not being signed. signPayloads=" << signBody |
226 | << " http scheme=" << Http::SchemeMapper::ToString(request.GetUri().GetScheme())); |
227 | } |
228 | |
229 | if(m_includeSha256HashHeader) |
230 | { |
231 | request.SetHeaderValue(X_AMZ_CONTENT_SHA256, payloadHash); |
232 | } |
233 | |
234 | //calculate date header to use in internal signature (this also goes into date header). |
235 | DateTime now = GetSigningTimestamp(); |
236 | Aws::String = now.ToGmtString(LONG_DATE_FORMAT_STR); |
237 | request.SetHeaderValue(AWS_DATE_HEADER, dateHeaderValue); |
238 | |
239 | Aws::StringStream ; |
240 | Aws::StringStream ; |
241 | |
242 | for (const auto& : CanonicalizeHeaders(request.GetHeaders())) |
243 | { |
244 | if(ShouldSignHeader(header.first)) |
245 | { |
246 | headersStream << header.first.c_str() << ":" << header.second.c_str() << NEWLINE; |
247 | signedHeadersStream << header.first.c_str() << ";" ; |
248 | } |
249 | } |
250 | |
251 | Aws::String = headersStream.str(); |
252 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Header String: " << canonicalHeadersString); |
253 | |
254 | //calculate signed headers parameter |
255 | Aws::String = signedHeadersStream.str(); |
256 | //remove that last semi-colon |
257 | if (!signedHeadersValue.empty()) |
258 | { |
259 | signedHeadersValue.pop_back(); |
260 | } |
261 | |
262 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Signed Headers value:" << signedHeadersValue); |
263 | |
264 | //generate generalized canonicalized request string. |
265 | Aws::String canonicalRequestString = CanonicalizeRequestSigningString(request, m_urlEscapePath); |
266 | |
267 | //append v4 stuff to the canonical request string. |
268 | canonicalRequestString.append(canonicalHeadersString); |
269 | canonicalRequestString.append(NEWLINE); |
270 | canonicalRequestString.append(signedHeadersValue); |
271 | canonicalRequestString.append(NEWLINE); |
272 | canonicalRequestString.append(payloadHash); |
273 | |
274 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Request String: " << canonicalRequestString); |
275 | |
276 | //now compute sha256 on that request string |
277 | auto hashResult = m_hash->Calculate(canonicalRequestString); |
278 | if (!hashResult.IsSuccess()) |
279 | { |
280 | AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to hash (sha256) request string" ); |
281 | AWS_LOGSTREAM_DEBUG(v4LogTag, "The request string is: \"" << canonicalRequestString << "\"" ); |
282 | return false; |
283 | } |
284 | |
285 | auto sha256Digest = hashResult.GetResult(); |
286 | Aws::String cannonicalRequestHash = HashingUtils::HexEncode(sha256Digest); |
287 | Aws::String simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); |
288 | |
289 | Aws::String signingRegion = region ? region : m_region; |
290 | Aws::String stringToSign = GenerateStringToSign(dateHeaderValue, simpleDate, cannonicalRequestHash, signingRegion, m_serviceName); |
291 | auto finalSignature = GenerateSignature(credentials, stringToSign, simpleDate, signingRegion, m_serviceName); |
292 | |
293 | Aws::StringStream ss; |
294 | ss << AWS_HMAC_SHA256 << " " << CREDENTIAL << EQ << credentials.GetAWSAccessKeyId() << "/" << simpleDate |
295 | << "/" << signingRegion << "/" << m_serviceName << "/" << AWS4_REQUEST << ", " << SIGNED_HEADERS << EQ |
296 | << signedHeadersValue << ", " << SIGNATURE << EQ << finalSignature; |
297 | |
298 | auto awsAuthString = ss.str(); |
299 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Signing request with: " << awsAuthString); |
300 | request.SetAwsAuthorization(awsAuthString); |
301 | request.SetSigningAccessKey(credentials.GetAWSAccessKeyId()); |
302 | request.SetSigningRegion(signingRegion); |
303 | return true; |
304 | } |
305 | |
306 | bool AWSAuthV4Signer::PresignRequest(Aws::Http::HttpRequest& request, long long expirationTimeInSeconds) const |
307 | { |
308 | return PresignRequest(request, m_region.c_str(), expirationTimeInSeconds); |
309 | } |
310 | |
311 | bool AWSAuthV4Signer::PresignRequest(Aws::Http::HttpRequest& request, const char* region, long long expirationInSeconds) const |
312 | { |
313 | return PresignRequest(request, region, m_serviceName.c_str(), expirationInSeconds); |
314 | } |
315 | |
316 | bool AWSAuthV4Signer::PresignRequest(Aws::Http::HttpRequest& request, const char* region, const char* serviceName, long long expirationTimeInSeconds) const |
317 | { |
318 | AWSCredentials credentials = m_credentialsProvider->GetAWSCredentials(); |
319 | |
320 | //don't sign anonymous requests |
321 | if (credentials.GetAWSAccessKeyId().empty() || credentials.GetAWSSecretKey().empty()) |
322 | { |
323 | return true; |
324 | } |
325 | |
326 | Aws::StringStream intConversionStream; |
327 | intConversionStream << expirationTimeInSeconds; |
328 | request.AddQueryStringParameter(Http::X_AMZ_EXPIRES_HEADER, intConversionStream.str()); |
329 | |
330 | if (!credentials.GetSessionToken().empty()) |
331 | { |
332 | request.AddQueryStringParameter(Http::AWS_SECURITY_TOKEN, credentials.GetSessionToken()); |
333 | } |
334 | |
335 | //calculate date header to use in internal signature (this also goes into date header). |
336 | DateTime now = GetSigningTimestamp(); |
337 | Aws::String dateQueryValue = now.ToGmtString(LONG_DATE_FORMAT_STR); |
338 | request.AddQueryStringParameter(Http::AWS_DATE_HEADER, dateQueryValue); |
339 | |
340 | Aws::StringStream ; |
341 | Aws::StringStream ; |
342 | for (const auto& : CanonicalizeHeaders(request.GetHeaders())) |
343 | { |
344 | if(ShouldSignHeader(header.first)) |
345 | { |
346 | headersStream << header.first.c_str() << ":" << header.second.c_str() << NEWLINE; |
347 | signedHeadersStream << header.first.c_str() << ";" ; |
348 | } |
349 | } |
350 | |
351 | Aws::String = headersStream.str(); |
352 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Header String: " << canonicalHeadersString); |
353 | |
354 | //calculate signed headers parameter |
355 | Aws::String (signedHeadersStream.str()); |
356 | //remove that last semi-colon |
357 | if (!signedHeadersValue.empty()) |
358 | { |
359 | signedHeadersValue.pop_back(); |
360 | } |
361 | |
362 | request.AddQueryStringParameter(X_AMZ_SIGNED_HEADERS, signedHeadersValue); |
363 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Signed Headers value: " << signedHeadersValue); |
364 | |
365 | Aws::StringStream ss; |
366 | Aws::String signingRegion = region ? region : m_region; |
367 | Aws::String signingServiceName = serviceName ? serviceName : m_serviceName; |
368 | Aws::String simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); |
369 | ss << credentials.GetAWSAccessKeyId() << "/" << simpleDate |
370 | << "/" << signingRegion << "/" << signingServiceName << "/" << AWS4_REQUEST; |
371 | |
372 | request.AddQueryStringParameter(X_AMZ_ALGORITHM, AWS_HMAC_SHA256); |
373 | request.AddQueryStringParameter(X_AMZ_CREDENTIAL, ss.str()); |
374 | ss.str("" ); |
375 | |
376 | request.SetSigningAccessKey(credentials.GetAWSAccessKeyId()); |
377 | request.SetSigningRegion(signingRegion); |
378 | |
379 | //generate generalized canonicalized request string. |
380 | Aws::String canonicalRequestString = CanonicalizeRequestSigningString(request, m_urlEscapePath); |
381 | |
382 | //append v4 stuff to the canonical request string. |
383 | canonicalRequestString.append(canonicalHeadersString); |
384 | canonicalRequestString.append(NEWLINE); |
385 | canonicalRequestString.append(signedHeadersValue); |
386 | canonicalRequestString.append(NEWLINE); |
387 | if (ServiceRequireUnsignedPayload(signingServiceName)) |
388 | { |
389 | canonicalRequestString.append(UNSIGNED_PAYLOAD); |
390 | } |
391 | else |
392 | { |
393 | canonicalRequestString.append(EMPTY_STRING_SHA256); |
394 | } |
395 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Request String: " << canonicalRequestString); |
396 | |
397 | //now compute sha256 on that request string |
398 | auto hashResult = m_hash->Calculate(canonicalRequestString); |
399 | if (!hashResult.IsSuccess()) |
400 | { |
401 | AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to hash (sha256) request string" ); |
402 | AWS_LOGSTREAM_DEBUG(v4LogTag, "The request string is: \"" << canonicalRequestString << "\"" ); |
403 | return false; |
404 | } |
405 | |
406 | auto sha256Digest = hashResult.GetResult(); |
407 | auto cannonicalRequestHash = HashingUtils::HexEncode(sha256Digest); |
408 | |
409 | auto stringToSign = GenerateStringToSign(dateQueryValue, simpleDate, cannonicalRequestHash, signingRegion, signingServiceName); |
410 | |
411 | auto finalSigningHash = GenerateSignature(credentials, stringToSign, simpleDate, signingRegion, signingServiceName); |
412 | if (finalSigningHash.empty()) |
413 | { |
414 | return false; |
415 | } |
416 | |
417 | //add that the signature to the query string |
418 | request.AddQueryStringParameter(X_AMZ_SIGNATURE, finalSigningHash); |
419 | |
420 | return true; |
421 | } |
422 | |
423 | bool AWSAuthV4Signer::ServiceRequireUnsignedPayload(const Aws::String& serviceName) const |
424 | { |
425 | // S3 uses a magic string (instead of the empty string) for its body hash for presigned URLs as outlined here: |
426 | // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html |
427 | // this is true for PUT, POST, GET, DELETE and HEAD operations. |
428 | // However, other services (for example RDS) implement the specification as outlined here: |
429 | // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html |
430 | // which states that body-less requests should use the empty-string SHA256 hash. |
431 | return "s3" == serviceName; |
432 | } |
433 | |
434 | Aws::String AWSAuthV4Signer::GenerateSignature(const AWSCredentials& credentials, const Aws::String& stringToSign, |
435 | const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const |
436 | { |
437 | auto key = ComputeHash(credentials.GetAWSSecretKey(), simpleDate, region, serviceName); |
438 | return GenerateSignature(stringToSign, key); |
439 | } |
440 | |
441 | Aws::String AWSAuthV4Signer::GenerateSignature(const Aws::String& stringToSign, const ByteBuffer& key) const |
442 | { |
443 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Final String to sign: " << stringToSign); |
444 | |
445 | Aws::StringStream ss; |
446 | |
447 | auto hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)stringToSign.c_str(), stringToSign.length()), key); |
448 | if (!hashResult.IsSuccess()) |
449 | { |
450 | AWS_LOGSTREAM_ERROR(v4LogTag, "Unable to hmac (sha256) final string" ); |
451 | AWS_LOGSTREAM_DEBUG(v4LogTag, "The final string is: \"" << stringToSign << "\"" ); |
452 | return {}; |
453 | } |
454 | |
455 | //now we finally sign our request string with our hex encoded derived hash. |
456 | auto finalSigningDigest = hashResult.GetResult(); |
457 | |
458 | auto finalSigningHash = HashingUtils::HexEncode(finalSigningDigest); |
459 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Final computed signing hash: " << finalSigningHash); |
460 | |
461 | return finalSigningHash; |
462 | } |
463 | |
464 | Aws::String AWSAuthV4Signer::ComputePayloadHash(Aws::Http::HttpRequest& request) const |
465 | { |
466 | if (!request.GetContentBody()) |
467 | { |
468 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Using cached empty string sha256 " << EMPTY_STRING_SHA256 << " because payload is empty." ); |
469 | return EMPTY_STRING_SHA256; |
470 | } |
471 | |
472 | //compute hash on payload if it exists. |
473 | auto hashResult = m_hash->Calculate(*request.GetContentBody()); |
474 | |
475 | if(request.GetContentBody()) |
476 | { |
477 | request.GetContentBody()->clear(); |
478 | request.GetContentBody()->seekg(0); |
479 | } |
480 | |
481 | if (!hashResult.IsSuccess()) |
482 | { |
483 | AWS_LOGSTREAM_ERROR(v4LogTag, "Unable to hash (sha256) request body" ); |
484 | return {}; |
485 | } |
486 | |
487 | auto sha256Digest = hashResult.GetResult(); |
488 | |
489 | Aws::String payloadHash(HashingUtils::HexEncode(sha256Digest)); |
490 | AWS_LOGSTREAM_DEBUG(v4LogTag, "Calculated sha256 " << payloadHash << " for payload." ); |
491 | return payloadHash; |
492 | } |
493 | |
494 | Aws::String AWSAuthV4Signer::GenerateStringToSign(const Aws::String& dateValue, const Aws::String& simpleDate, |
495 | const Aws::String& canonicalRequestHash, const Aws::String& region, const Aws::String& serviceName) const |
496 | { |
497 | //generate the actual string we will use in signing the final request. |
498 | Aws::StringStream ss; |
499 | |
500 | ss << AWS_HMAC_SHA256 << NEWLINE << dateValue << NEWLINE << simpleDate << "/" << region << "/" |
501 | << serviceName << "/" << AWS4_REQUEST << NEWLINE << canonicalRequestHash; |
502 | |
503 | return ss.str(); |
504 | } |
505 | |
506 | Aws::Utils::ByteBuffer AWSAuthV4Signer::ComputeHash(const Aws::String& secretKey, |
507 | const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const |
508 | { |
509 | Aws::String signingKey(SIGNING_KEY); |
510 | signingKey.append(secretKey); |
511 | auto hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)simpleDate.c_str(), simpleDate.length()), |
512 | ByteBuffer((unsigned char*)signingKey.c_str(), signingKey.length())); |
513 | |
514 | if (!hashResult.IsSuccess()) |
515 | { |
516 | AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to HMAC (SHA256) date string \"" << simpleDate << "\"" ); |
517 | return {}; |
518 | } |
519 | |
520 | auto kDate = hashResult.GetResult(); |
521 | hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)region.c_str(), region.length()), kDate); |
522 | if (!hashResult.IsSuccess()) |
523 | { |
524 | AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to HMAC (SHA256) region string \"" << region << "\"" ); |
525 | return {}; |
526 | } |
527 | |
528 | auto kRegion = hashResult.GetResult(); |
529 | hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)serviceName.c_str(), serviceName.length()), kRegion); |
530 | if (!hashResult.IsSuccess()) |
531 | { |
532 | AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to HMAC (SHA256) service string \"" << m_serviceName << "\"" ); |
533 | return {}; |
534 | } |
535 | |
536 | auto kService = hashResult.GetResult(); |
537 | hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)AWS4_REQUEST, strlen(AWS4_REQUEST)), kService); |
538 | if (!hashResult.IsSuccess()) |
539 | { |
540 | AWS_LOGSTREAM_ERROR(v4LogTag, "Unable to HMAC (SHA256) request string" ); |
541 | AWS_LOGSTREAM_DEBUG(v4LogTag, "The request string is: \"" << AWS4_REQUEST << "\"" ); |
542 | return {}; |
543 | } |
544 | return hashResult.GetResult(); |
545 | } |
546 | |
547 | |
548 | AWSAuthEventStreamV4Signer::AWSAuthEventStreamV4Signer(const std::shared_ptr<Auth::AWSCredentialsProvider>& |
549 | credentialsProvider, const char* serviceName, const Aws::String& region) : |
550 | m_serviceName(serviceName), |
551 | m_region(region), |
552 | m_credentialsProvider(credentialsProvider) |
553 | { |
554 | |
555 | m_unsignedHeaders.emplace_back(X_AMZN_TRACE_ID); |
556 | m_unsignedHeaders.emplace_back(USER_AGENT_HEADER); |
557 | } |
558 | |
559 | bool AWSAuthEventStreamV4Signer::SignRequest(Aws::Http::HttpRequest& request, const char* region, bool /* signBody */) const |
560 | { |
561 | AWSCredentials credentials = m_credentialsProvider->GetAWSCredentials(); |
562 | |
563 | //don't sign anonymous requests |
564 | if (credentials.GetAWSAccessKeyId().empty() || credentials.GetAWSSecretKey().empty()) |
565 | { |
566 | return true; |
567 | } |
568 | |
569 | if (!credentials.GetSessionToken().empty()) |
570 | { |
571 | request.SetAwsSessionToken(credentials.GetSessionToken()); |
572 | } |
573 | |
574 | request.SetHeaderValue(X_AMZ_CONTENT_SHA256, EVENT_STREAM_CONTENT_SHA256); |
575 | |
576 | //calculate date header to use in internal signature (this also goes into date header). |
577 | DateTime now = GetSigningTimestamp(); |
578 | Aws::String = now.ToGmtString(LONG_DATE_FORMAT_STR); |
579 | request.SetHeaderValue(AWS_DATE_HEADER, dateHeaderValue); |
580 | |
581 | Aws::StringStream ; |
582 | Aws::StringStream ; |
583 | |
584 | for (const auto& : CanonicalizeHeaders(request.GetHeaders())) |
585 | { |
586 | if(ShouldSignHeader(header.first)) |
587 | { |
588 | headersStream << header.first.c_str() << ":" << header.second.c_str() << NEWLINE; |
589 | signedHeadersStream << header.first.c_str() << ";" ; |
590 | } |
591 | } |
592 | |
593 | Aws::String = headersStream.str(); |
594 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Canonical Header String: " << canonicalHeadersString); |
595 | |
596 | //calculate signed headers parameter |
597 | Aws::String = signedHeadersStream.str(); |
598 | //remove that last semi-colon |
599 | if (!signedHeadersValue.empty()) |
600 | { |
601 | signedHeadersValue.pop_back(); |
602 | } |
603 | |
604 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Signed Headers value:" << signedHeadersValue); |
605 | |
606 | //generate generalized canonicalized request string. |
607 | Aws::String canonicalRequestString = CanonicalizeRequestSigningString(request, true/* m_urlEscapePath */); |
608 | |
609 | //append v4 stuff to the canonical request string. |
610 | canonicalRequestString.append(canonicalHeadersString); |
611 | canonicalRequestString.append(NEWLINE); |
612 | canonicalRequestString.append(signedHeadersValue); |
613 | canonicalRequestString.append(NEWLINE); |
614 | canonicalRequestString.append(EVENT_STREAM_CONTENT_SHA256); |
615 | |
616 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Canonical Request String: " << canonicalRequestString); |
617 | |
618 | //now compute sha256 on that request string |
619 | auto hashResult = m_hash.Calculate(canonicalRequestString); |
620 | if (!hashResult.IsSuccess()) |
621 | { |
622 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to hash (sha256) request string" ); |
623 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "The request string is: \"" << canonicalRequestString << "\"" ); |
624 | return false; |
625 | } |
626 | |
627 | auto sha256Digest = hashResult.GetResult(); |
628 | Aws::String cannonicalRequestHash = HashingUtils::HexEncode(sha256Digest); |
629 | Aws::String simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); |
630 | |
631 | Aws::String signingRegion = region ? region : m_region; |
632 | Aws::String stringToSign = GenerateStringToSign(dateHeaderValue, simpleDate, cannonicalRequestHash, signingRegion, m_serviceName); |
633 | auto finalSignature = GenerateSignature(credentials, stringToSign, simpleDate, signingRegion, m_serviceName); |
634 | |
635 | Aws::StringStream ss; |
636 | ss << AWS_HMAC_SHA256 << " " << CREDENTIAL << EQ << credentials.GetAWSAccessKeyId() << "/" << simpleDate |
637 | << "/" << signingRegion << "/" << m_serviceName << "/" << AWS4_REQUEST << ", " << SIGNED_HEADERS << EQ |
638 | << signedHeadersValue << ", " << SIGNATURE << EQ << HashingUtils::HexEncode(finalSignature); |
639 | |
640 | auto awsAuthString = ss.str(); |
641 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Signing request with: " << awsAuthString); |
642 | request.SetAwsAuthorization(awsAuthString); |
643 | request.SetSigningAccessKey(credentials.GetAWSAccessKeyId()); |
644 | request.SetSigningRegion(signingRegion); |
645 | return true; |
646 | } |
647 | |
648 | // this works regardless if the current machine is Big/Little Endian |
649 | static void WriteBigEndian(Aws::String& str, uint64_t n) |
650 | { |
651 | int shift = 56; |
652 | while(shift >= 0) |
653 | { |
654 | str.push_back((n >> shift) & 0xFF); |
655 | shift -= 8; |
656 | } |
657 | } |
658 | |
659 | bool AWSAuthEventStreamV4Signer::SignEventMessage(Event::Message& message, Aws::String& priorSignature) const |
660 | { |
661 | using Event::EventHeaderValue; |
662 | |
663 | Aws::StringStream stringToSign; |
664 | stringToSign << EVENT_STREAM_PAYLOAD << NEWLINE; |
665 | const DateTime now = GetSigningTimestamp(); |
666 | const auto simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); |
667 | stringToSign << now.ToGmtString(LONG_DATE_FORMAT_STR) << NEWLINE |
668 | << simpleDate << "/" << m_region << "/" |
669 | << m_serviceName << "/aws4_request" << NEWLINE << priorSignature << NEWLINE; |
670 | |
671 | |
672 | Aws::String ; |
673 | nonSignatureHeaders.push_back(char(sizeof(EVENTSTREAM_DATE_HEADER) - 1)); // length of the string |
674 | nonSignatureHeaders += EVENTSTREAM_DATE_HEADER; |
675 | nonSignatureHeaders.push_back(static_cast<char>(EventHeaderValue::EventHeaderType::TIMESTAMP)); // type of the value |
676 | WriteBigEndian(nonSignatureHeaders, static_cast<uint64_t>(now.Millis())); // the value of the timestamp in big-endian |
677 | |
678 | auto hashOutcome = m_hash.Calculate(nonSignatureHeaders); |
679 | if (!hashOutcome.IsSuccess()) |
680 | { |
681 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to hash (sha256) non-signature headers." ); |
682 | return false; |
683 | } |
684 | |
685 | const auto = hashOutcome.GetResult(); |
686 | stringToSign << HashingUtils::HexEncode(nonSignatureHeadersHash) << NEWLINE; |
687 | |
688 | if (message.GetEventPayload().empty()) |
689 | { |
690 | AWS_LOGSTREAM_WARN(v4StreamingLogTag, "Attempting to sign an empty message (no payload and no headers). " |
691 | "It is unlikely that this is the intended behavior." ); |
692 | } |
693 | else |
694 | { |
695 | // use a preallocatedStreamBuf to avoid making a copy. |
696 | // The Hashing API requires either Aws::String or IStream as input. |
697 | // TODO: the hashing API should be accept 'unsigned char*' as input. |
698 | Utils::Stream::PreallocatedStreamBuf streamBuf(message.GetEventPayload().data(), message.GetEventPayload().size()); |
699 | Aws::IOStream payload(&streamBuf); |
700 | hashOutcome = m_hash.Calculate(payload); |
701 | |
702 | if (!hashOutcome.IsSuccess()) |
703 | { |
704 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to hash (sha256) non-signature headers." ); |
705 | return false; |
706 | } |
707 | const auto payloadHash = hashOutcome.GetResult(); |
708 | stringToSign << HashingUtils::HexEncode(payloadHash); |
709 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Payload hash - " << HashingUtils::HexEncode(payloadHash)); |
710 | } |
711 | |
712 | Utils::ByteBuffer finalSignatureDigest = GenerateSignature(m_credentialsProvider->GetAWSCredentials(), stringToSign.str(), simpleDate, m_region, m_serviceName); |
713 | const auto finalSignature = HashingUtils::HexEncode(finalSignatureDigest); |
714 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Final computed signing hash: " << finalSignature); |
715 | priorSignature = finalSignature; |
716 | |
717 | message.InsertEventHeader(EVENTSTREAM_DATE_HEADER, EventHeaderValue(now.Millis(), EventHeaderValue::EventHeaderType::TIMESTAMP)); |
718 | message.InsertEventHeader(EVENTSTREAM_SIGNATURE_HEADER, std::move(finalSignatureDigest)); |
719 | |
720 | AWS_LOGSTREAM_INFO(v4StreamingLogTag, "Event chunk final signature - " << finalSignature); |
721 | return true; |
722 | } |
723 | |
724 | bool AWSAuthEventStreamV4Signer::(const Aws::String& ) const |
725 | { |
726 | return std::find(m_unsignedHeaders.cbegin(), m_unsignedHeaders.cend(), Aws::Utils::StringUtils::ToLower(header.c_str())) == m_unsignedHeaders.cend(); |
727 | } |
728 | |
729 | Utils::ByteBuffer AWSAuthEventStreamV4Signer::GenerateSignature(const AWSCredentials& credentials, const Aws::String& stringToSign, |
730 | const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const |
731 | { |
732 | Utils::Threading::ReaderLockGuard guard(m_derivedKeyLock); |
733 | const auto& secretKey = credentials.GetAWSSecretKey(); |
734 | if (secretKey != m_currentSecretKey || simpleDate != m_currentDateStr) |
735 | { |
736 | guard.UpgradeToWriterLock(); |
737 | // double-checked lock to prevent updating twice |
738 | if (m_currentDateStr != simpleDate || m_currentSecretKey != secretKey) |
739 | { |
740 | m_currentSecretKey = secretKey; |
741 | m_currentDateStr = simpleDate; |
742 | m_derivedKey = ComputeHash(m_currentSecretKey, m_currentDateStr, region, serviceName); |
743 | } |
744 | |
745 | } |
746 | return GenerateSignature(stringToSign, m_derivedKey); |
747 | } |
748 | |
749 | Utils::ByteBuffer AWSAuthEventStreamV4Signer::GenerateSignature(const Aws::String& stringToSign, const ByteBuffer& key) const |
750 | { |
751 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Final String to sign: " << stringToSign); |
752 | |
753 | Aws::StringStream ss; |
754 | |
755 | auto hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)stringToSign.c_str(), stringToSign.length()), key); |
756 | if (!hashResult.IsSuccess()) |
757 | { |
758 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Unable to hmac (sha256) final string" ); |
759 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "The final string is: \"" << stringToSign << "\"" ); |
760 | return {}; |
761 | } |
762 | |
763 | return hashResult.GetResult(); |
764 | } |
765 | |
766 | Aws::String AWSAuthEventStreamV4Signer::GenerateStringToSign(const Aws::String& dateValue, const Aws::String& simpleDate, |
767 | const Aws::String& canonicalRequestHash, const Aws::String& region, const Aws::String& serviceName) const |
768 | { |
769 | //generate the actual string we will use in signing the final request. |
770 | Aws::StringStream ss; |
771 | |
772 | ss << AWS_HMAC_SHA256 << NEWLINE << dateValue << NEWLINE << simpleDate << "/" << region << "/" |
773 | << serviceName << "/" << AWS4_REQUEST << NEWLINE << canonicalRequestHash; |
774 | |
775 | return ss.str(); |
776 | } |
777 | |
778 | Aws::Utils::ByteBuffer AWSAuthEventStreamV4Signer::ComputeHash(const Aws::String& secretKey, |
779 | const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const |
780 | { |
781 | Aws::String signingKey(SIGNING_KEY); |
782 | signingKey.append(secretKey); |
783 | auto hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)simpleDate.c_str(), simpleDate.length()), |
784 | ByteBuffer((unsigned char*)signingKey.c_str(), signingKey.length())); |
785 | |
786 | if (!hashResult.IsSuccess()) |
787 | { |
788 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to HMAC (SHA256) date string \"" << simpleDate << "\"" ); |
789 | return {}; |
790 | } |
791 | |
792 | auto kDate = hashResult.GetResult(); |
793 | hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)region.c_str(), region.length()), kDate); |
794 | if (!hashResult.IsSuccess()) |
795 | { |
796 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to HMAC (SHA256) region string \"" << region << "\"" ); |
797 | return {}; |
798 | } |
799 | |
800 | auto kRegion = hashResult.GetResult(); |
801 | hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)serviceName.c_str(), serviceName.length()), kRegion); |
802 | if (!hashResult.IsSuccess()) |
803 | { |
804 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to HMAC (SHA256) service string \"" << m_serviceName << "\"" ); |
805 | return {}; |
806 | } |
807 | |
808 | auto kService = hashResult.GetResult(); |
809 | hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)AWS4_REQUEST, strlen(AWS4_REQUEST)), kService); |
810 | if (!hashResult.IsSuccess()) |
811 | { |
812 | AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Unable to HMAC (SHA256) request string" ); |
813 | AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "The request string is: \"" << AWS4_REQUEST << "\"" ); |
814 | return {}; |
815 | } |
816 | return hashResult.GetResult(); |
817 | } |
818 | |