/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include <aws/core/auth/AWSAuthSigner.h> #include <aws/core/auth/AWSCredentialsProvider.h> #include <aws/core/client/ClientConfiguration.h> #include <aws/core/http/HttpRequest.h> #include <aws/core/http/HttpResponse.h> #include <aws/core/utils/DateTime.h> #include <aws/core/utils/HashingUtils.h> #include <aws/core/utils/Outcome.h> #include <aws/core/utils/StringUtils.h> #include <aws/core/utils/logging/LogMacros.h> #include <aws/core/utils/memory/AWSMemory.h> #include <aws/core/utils/crypto/Sha256.h> #include <aws/core/utils/crypto/Sha256HMAC.h> #include <aws/core/utils/stream/PreallocatedStreamBuf.h> #include <aws/core/utils/event/EventMessage.h> #include <aws/core/utils/event/EventHeader.h> #include <cstdio> #include <iomanip> #include <math.h> #include <cstring> using namespace Aws; using namespace Aws::Client; using namespace Aws::Auth; using namespace Aws::Http; using namespace Aws::Utils; using namespace Aws::Utils::Logging; static const char* EQ = "="; static const char* AWS_HMAC_SHA256 = "AWS4-HMAC-SHA256"; static const char* EVENT_STREAM_CONTENT_SHA256 = "STREAMING-AWS4-HMAC-SHA256-EVENTS"; static const char* EVENT_STREAM_PAYLOAD = "AWS4-HMAC-SHA256-PAYLOAD"; static const char* AWS4_REQUEST = "aws4_request"; static const char* SIGNED_HEADERS = "SignedHeaders"; static const char* CREDENTIAL = "Credential"; static const char* NEWLINE = "\n"; static const char* X_AMZ_SIGNED_HEADERS = "X-Amz-SignedHeaders"; static const char* X_AMZ_ALGORITHM = "X-Amz-Algorithm"; static const char* X_AMZ_CREDENTIAL = "X-Amz-Credential"; static const char* UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; static const char* X_AMZ_SIGNATURE = "X-Amz-Signature"; static const char* X_AMZN_TRACE_ID = "x-amzn-trace-id"; static const char* X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256"; static const char* USER_AGENT = "user-agent"; static const char* SIGNING_KEY = "AWS4"; static const char* SIMPLE_DATE_FORMAT_STR = "%Y%m%d"; static const char* EMPTY_STRING_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; static const char v4LogTag[] = "AWSAuthV4Signer"; static const char v4StreamingLogTag[] = "AWSAuthEventStreamV4Signer"; namespace Aws { namespace Auth { const char SIGNATURE[] = "Signature"; const char SIGV4_SIGNER[] = "SignatureV4"; const char EVENTSTREAM_SIGV4_SIGNER[] = "EventStreamSignatureV4"; const char EVENTSTREAM_SIGNATURE_HEADER[] = ":chunk-signature"; const char EVENTSTREAM_DATE_HEADER[] = ":date"; const char NULL_SIGNER[] = "NullSigner"; } } static Aws::String CanonicalizeRequestSigningString(HttpRequest& request, bool urlEscapePath) { request.CanonicalizeRequest(); Aws::StringStream signingStringStream; signingStringStream << HttpMethodMapper::GetNameForHttpMethod(request.GetMethod()); URI uriCpy = request.GetUri(); // Many AWS services do not decode the URL before calculating SignatureV4 on their end. // This results in the signature getting calculated with a double encoded URL. // That means we have to double encode it here for the signature to match on the service side. if(urlEscapePath) { // RFC3986 is how we encode the URL before sending it on the wire. auto rfc3986EncodedPath = URI::URLEncodePathRFC3986(uriCpy.GetPath()); uriCpy.SetPath(rfc3986EncodedPath); // However, SignatureV4 uses this URL encoding scheme signingStringStream << NEWLINE << uriCpy.GetURLEncodedPath() << NEWLINE; } else { // For the services that DO decode the URL first; we don't need to double encode it. uriCpy.SetPath(uriCpy.GetURLEncodedPath()); signingStringStream << NEWLINE << uriCpy.GetPath() << NEWLINE; } if (request.GetQueryString().find('=') != std::string::npos) { signingStringStream << request.GetQueryString().substr(1) << NEWLINE; } else if (request.GetQueryString().size() > 1) { signingStringStream << request.GetQueryString().substr(1) << "=" << NEWLINE; } else { signingStringStream << NEWLINE; } return signingStringStream.str(); } static Http::HeaderValueCollection CanonicalizeHeaders(Http::HeaderValueCollection&& headers) { Http::HeaderValueCollection canonicalHeaders; for (const auto& header : headers) { auto trimmedHeaderName = StringUtils::Trim(header.first.c_str()); auto trimmedHeaderValue = StringUtils::Trim(header.second.c_str()); //multiline gets converted to line1,line2,etc... auto headerMultiLine = StringUtils::SplitOnLine(trimmedHeaderValue); Aws::String headerValue = headerMultiLine.size() == 0 ? "" : headerMultiLine[0]; if (headerMultiLine.size() > 1) { for(size_t i = 1; i < headerMultiLine.size(); ++i) { headerValue += ","; headerValue += StringUtils::Trim(headerMultiLine[i].c_str()); } } //duplicate spaces need to be converted to one. Aws::String::iterator new_end = std::unique(headerValue.begin(), headerValue.end(), [=](char lhs, char rhs) { return (lhs == rhs) && (lhs == ' '); } ); headerValue.erase(new_end, headerValue.end()); canonicalHeaders[trimmedHeaderName] = headerValue; } return canonicalHeaders; } AWSAuthV4Signer::AWSAuthV4Signer(const std::shared_ptr<Auth::AWSCredentialsProvider>& credentialsProvider, const char* serviceName, const Aws::String& region, PayloadSigningPolicy signingPolicy, bool urlEscapePath) : m_includeSha256HashHeader(true), m_credentialsProvider(credentialsProvider), m_serviceName(serviceName), m_region(region), m_hash(Aws::MakeUnique<Aws::Utils::Crypto::Sha256>(v4LogTag)), m_HMAC(Aws::MakeUnique<Aws::Utils::Crypto::Sha256HMAC>(v4LogTag)), m_unsignedHeaders({USER_AGENT, X_AMZN_TRACE_ID}), m_payloadSigningPolicy(signingPolicy), m_urlEscapePath(urlEscapePath) { //go ahead and warm up the signing cache. ComputeHash(credentialsProvider->GetAWSCredentials().GetAWSSecretKey(), DateTime::CalculateGmtTimestampAsString(SIMPLE_DATE_FORMAT_STR), region, m_serviceName); } AWSAuthV4Signer::~AWSAuthV4Signer() { // empty destructor in .cpp file to keep from needing the implementation of (AWSCredentialsProvider, Sha256, Sha256HMAC) in the header file } bool AWSAuthV4Signer::ShouldSignHeader(const Aws::String& header) const { return m_unsignedHeaders.find(Aws::Utils::StringUtils::ToLower(header.c_str())) == m_unsignedHeaders.cend(); } bool AWSAuthV4Signer::SignRequest(Aws::Http::HttpRequest& request, const char* region, const char* serviceName, bool signBody) const { AWSCredentials credentials = m_credentialsProvider->GetAWSCredentials(); //don't sign anonymous requests if (credentials.GetAWSAccessKeyId().empty() || credentials.GetAWSSecretKey().empty()) { return true; } if (!credentials.GetSessionToken().empty()) { request.SetAwsSessionToken(credentials.GetSessionToken()); } Aws::String payloadHash(UNSIGNED_PAYLOAD); switch(m_payloadSigningPolicy) { case PayloadSigningPolicy::Always: signBody = true; break; case PayloadSigningPolicy::Never: signBody = false; break; case PayloadSigningPolicy::RequestDependent: // respect the request setting default: break; } if(signBody || request.GetUri().GetScheme() != Http::Scheme::HTTPS) { payloadHash = ComputePayloadHash(request); if (payloadHash.empty()) { return false; } } else { AWS_LOGSTREAM_DEBUG(v4LogTag, "Note: Http payloads are not being signed. signPayloads=" << signBody << " http scheme=" << Http::SchemeMapper::ToString(request.GetUri().GetScheme())); } if(m_includeSha256HashHeader) { request.SetHeaderValue(X_AMZ_CONTENT_SHA256, payloadHash); } //calculate date header to use in internal signature (this also goes into date header). DateTime now = GetSigningTimestamp(); Aws::String dateHeaderValue = now.ToGmtString(DateFormat::ISO_8601_BASIC); request.SetHeaderValue(AWS_DATE_HEADER, dateHeaderValue); Aws::StringStream headersStream; Aws::StringStream signedHeadersStream; for (const auto& header : CanonicalizeHeaders(request.GetHeaders())) { if(ShouldSignHeader(header.first)) { headersStream << header.first.c_str() << ":" << header.second.c_str() << NEWLINE; signedHeadersStream << header.first.c_str() << ";"; } } Aws::String canonicalHeadersString = headersStream.str(); AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Header String: " << canonicalHeadersString); //calculate signed headers parameter Aws::String signedHeadersValue = signedHeadersStream.str(); //remove that last semi-colon if (!signedHeadersValue.empty()) { signedHeadersValue.pop_back(); } AWS_LOGSTREAM_DEBUG(v4LogTag, "Signed Headers value:" << signedHeadersValue); //generate generalized canonicalized request string. Aws::String canonicalRequestString = CanonicalizeRequestSigningString(request, m_urlEscapePath); //append v4 stuff to the canonical request string. canonicalRequestString.append(canonicalHeadersString); canonicalRequestString.append(NEWLINE); canonicalRequestString.append(signedHeadersValue); canonicalRequestString.append(NEWLINE); canonicalRequestString.append(payloadHash); AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Request String: " << canonicalRequestString); //now compute sha256 on that request string auto hashResult = m_hash->Calculate(canonicalRequestString); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to hash (sha256) request string"); AWS_LOGSTREAM_DEBUG(v4LogTag, "The request string is: \"" << canonicalRequestString << "\""); return false; } auto sha256Digest = hashResult.GetResult(); Aws::String canonicalRequestHash = HashingUtils::HexEncode(sha256Digest); Aws::String simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); Aws::String signingRegion = region ? region : m_region; Aws::String signingServiceName = serviceName ? serviceName : m_serviceName; Aws::String stringToSign = GenerateStringToSign(dateHeaderValue, simpleDate, canonicalRequestHash, signingRegion, signingServiceName); auto finalSignature = GenerateSignature(credentials, stringToSign, simpleDate, signingRegion, signingServiceName); Aws::StringStream ss; ss << AWS_HMAC_SHA256 << " " << CREDENTIAL << EQ << credentials.GetAWSAccessKeyId() << "/" << simpleDate << "/" << signingRegion << "/" << signingServiceName << "/" << AWS4_REQUEST << ", " << SIGNED_HEADERS << EQ << signedHeadersValue << ", " << SIGNATURE << EQ << finalSignature; auto awsAuthString = ss.str(); AWS_LOGSTREAM_DEBUG(v4LogTag, "Signing request with: " << awsAuthString); request.SetAwsAuthorization(awsAuthString); request.SetSigningAccessKey(credentials.GetAWSAccessKeyId()); request.SetSigningRegion(signingRegion); return true; } bool AWSAuthV4Signer::PresignRequest(Aws::Http::HttpRequest& request, long long expirationTimeInSeconds) const { return PresignRequest(request, m_region.c_str(), expirationTimeInSeconds); } bool AWSAuthV4Signer::PresignRequest(Aws::Http::HttpRequest& request, const char* region, long long expirationInSeconds) const { return PresignRequest(request, region, m_serviceName.c_str(), expirationInSeconds); } bool AWSAuthV4Signer::PresignRequest(Aws::Http::HttpRequest& request, const char* region, const char* serviceName, long long expirationTimeInSeconds) const { AWSCredentials credentials = m_credentialsProvider->GetAWSCredentials(); //don't sign anonymous requests if (credentials.GetAWSAccessKeyId().empty() || credentials.GetAWSSecretKey().empty()) { return true; } Aws::StringStream intConversionStream; intConversionStream << expirationTimeInSeconds; request.AddQueryStringParameter(Http::X_AMZ_EXPIRES_HEADER, intConversionStream.str()); if (!credentials.GetSessionToken().empty()) { request.AddQueryStringParameter(Http::AWS_SECURITY_TOKEN, credentials.GetSessionToken()); } //calculate date header to use in internal signature (this also goes into date header). DateTime now = GetSigningTimestamp(); Aws::String dateQueryValue = now.ToGmtString(DateFormat::ISO_8601_BASIC); request.AddQueryStringParameter(Http::AWS_DATE_HEADER, dateQueryValue); Aws::StringStream headersStream; Aws::StringStream signedHeadersStream; for (const auto& header : CanonicalizeHeaders(request.GetHeaders())) { if(ShouldSignHeader(header.first)) { headersStream << header.first.c_str() << ":" << header.second.c_str() << NEWLINE; signedHeadersStream << header.first.c_str() << ";"; } } Aws::String canonicalHeadersString = headersStream.str(); AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Header String: " << canonicalHeadersString); //calculate signed headers parameter Aws::String signedHeadersValue(signedHeadersStream.str()); //remove that last semi-colon if (!signedHeadersValue.empty()) { signedHeadersValue.pop_back(); } request.AddQueryStringParameter(X_AMZ_SIGNED_HEADERS, signedHeadersValue); AWS_LOGSTREAM_DEBUG(v4LogTag, "Signed Headers value: " << signedHeadersValue); Aws::StringStream ss; Aws::String signingRegion = region ? region : m_region; Aws::String signingServiceName = serviceName ? serviceName : m_serviceName; Aws::String simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); ss << credentials.GetAWSAccessKeyId() << "/" << simpleDate << "/" << signingRegion << "/" << signingServiceName << "/" << AWS4_REQUEST; request.AddQueryStringParameter(X_AMZ_ALGORITHM, AWS_HMAC_SHA256); request.AddQueryStringParameter(X_AMZ_CREDENTIAL, ss.str()); ss.str(""); request.SetSigningAccessKey(credentials.GetAWSAccessKeyId()); request.SetSigningRegion(signingRegion); //generate generalized canonicalized request string. Aws::String canonicalRequestString = CanonicalizeRequestSigningString(request, m_urlEscapePath); //append v4 stuff to the canonical request string. canonicalRequestString.append(canonicalHeadersString); canonicalRequestString.append(NEWLINE); canonicalRequestString.append(signedHeadersValue); canonicalRequestString.append(NEWLINE); if (ServiceRequireUnsignedPayload(signingServiceName)) { canonicalRequestString.append(UNSIGNED_PAYLOAD); } else { canonicalRequestString.append(EMPTY_STRING_SHA256); } AWS_LOGSTREAM_DEBUG(v4LogTag, "Canonical Request String: " << canonicalRequestString); //now compute sha256 on that request string auto hashResult = m_hash->Calculate(canonicalRequestString); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to hash (sha256) request string"); AWS_LOGSTREAM_DEBUG(v4LogTag, "The request string is: \"" << canonicalRequestString << "\""); return false; } auto sha256Digest = hashResult.GetResult(); auto canonicalRequestHash = HashingUtils::HexEncode(sha256Digest); auto stringToSign = GenerateStringToSign(dateQueryValue, simpleDate, canonicalRequestHash, signingRegion, signingServiceName); auto finalSigningHash = GenerateSignature(credentials, stringToSign, simpleDate, signingRegion, signingServiceName); if (finalSigningHash.empty()) { return false; } //add that the signature to the query string request.AddQueryStringParameter(X_AMZ_SIGNATURE, finalSigningHash); return true; } bool AWSAuthV4Signer::ServiceRequireUnsignedPayload(const Aws::String& serviceName) const { // S3 uses a magic string (instead of the empty string) for its body hash for presigned URLs as outlined here: // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html // this is true for PUT, POST, GET, DELETE and HEAD operations. // However, other services (for example RDS) implement the specification as outlined here: // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html // which states that body-less requests should use the empty-string SHA256 hash. return "s3" == serviceName || "s3-object-lambda" == serviceName; } Aws::String AWSAuthV4Signer::GenerateSignature(const AWSCredentials& credentials, const Aws::String& stringToSign, const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const { auto key = ComputeHash(credentials.GetAWSSecretKey(), simpleDate, region, serviceName); return GenerateSignature(stringToSign, key); } Aws::String AWSAuthV4Signer::GenerateSignature(const Aws::String& stringToSign, const ByteBuffer& key) const { AWS_LOGSTREAM_DEBUG(v4LogTag, "Final String to sign: " << stringToSign); Aws::StringStream ss; auto hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)stringToSign.c_str(), stringToSign.length()), key); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Unable to hmac (sha256) final string"); AWS_LOGSTREAM_DEBUG(v4LogTag, "The final string is: \"" << stringToSign << "\""); return {}; } //now we finally sign our request string with our hex encoded derived hash. auto finalSigningDigest = hashResult.GetResult(); auto finalSigningHash = HashingUtils::HexEncode(finalSigningDigest); AWS_LOGSTREAM_DEBUG(v4LogTag, "Final computed signing hash: " << finalSigningHash); return finalSigningHash; } Aws::String AWSAuthV4Signer::ComputePayloadHash(Aws::Http::HttpRequest& request) const { if (!request.GetContentBody()) { AWS_LOGSTREAM_DEBUG(v4LogTag, "Using cached empty string sha256 " << EMPTY_STRING_SHA256 << " because payload is empty."); return EMPTY_STRING_SHA256; } //compute hash on payload if it exists. auto hashResult = m_hash->Calculate(*request.GetContentBody()); if(request.GetContentBody()) { request.GetContentBody()->clear(); request.GetContentBody()->seekg(0); } if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Unable to hash (sha256) request body"); return {}; } auto sha256Digest = hashResult.GetResult(); Aws::String payloadHash(HashingUtils::HexEncode(sha256Digest)); AWS_LOGSTREAM_DEBUG(v4LogTag, "Calculated sha256 " << payloadHash << " for payload."); return payloadHash; } Aws::String AWSAuthV4Signer::GenerateStringToSign(const Aws::String& dateValue, const Aws::String& simpleDate, const Aws::String& canonicalRequestHash, const Aws::String& region, const Aws::String& serviceName) const { //generate the actual string we will use in signing the final request. Aws::StringStream ss; ss << AWS_HMAC_SHA256 << NEWLINE << dateValue << NEWLINE << simpleDate << "/" << region << "/" << serviceName << "/" << AWS4_REQUEST << NEWLINE << canonicalRequestHash; return ss.str(); } Aws::Utils::ByteBuffer AWSAuthV4Signer::ComputeHash(const Aws::String& secretKey, const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const { Aws::String signingKey(SIGNING_KEY); signingKey.append(secretKey); auto hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)simpleDate.c_str(), simpleDate.length()), ByteBuffer((unsigned char*)signingKey.c_str(), signingKey.length())); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to HMAC (SHA256) date string \"" << simpleDate << "\""); return {}; } auto kDate = hashResult.GetResult(); hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)region.c_str(), region.length()), kDate); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to HMAC (SHA256) region string \"" << region << "\""); return {}; } auto kRegion = hashResult.GetResult(); hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)serviceName.c_str(), serviceName.length()), kRegion); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Failed to HMAC (SHA256) service string \"" << m_serviceName << "\""); return {}; } auto kService = hashResult.GetResult(); hashResult = m_HMAC->Calculate(ByteBuffer((unsigned char*)AWS4_REQUEST, strlen(AWS4_REQUEST)), kService); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4LogTag, "Unable to HMAC (SHA256) request string"); AWS_LOGSTREAM_DEBUG(v4LogTag, "The request string is: \"" << AWS4_REQUEST << "\""); return {}; } return hashResult.GetResult(); } AWSAuthEventStreamV4Signer::AWSAuthEventStreamV4Signer(const std::shared_ptr<Auth::AWSCredentialsProvider>& credentialsProvider, const char* serviceName, const Aws::String& region) : m_serviceName(serviceName), m_region(region), m_credentialsProvider(credentialsProvider) { m_unsignedHeaders.emplace_back(X_AMZN_TRACE_ID); m_unsignedHeaders.emplace_back(USER_AGENT_HEADER); } bool AWSAuthEventStreamV4Signer::SignRequest(Aws::Http::HttpRequest& request, const char* region, const char* serviceName, bool /* signBody */) const { AWSCredentials credentials = m_credentialsProvider->GetAWSCredentials(); //don't sign anonymous requests if (credentials.GetAWSAccessKeyId().empty() || credentials.GetAWSSecretKey().empty()) { return true; } if (!credentials.GetSessionToken().empty()) { request.SetAwsSessionToken(credentials.GetSessionToken()); } request.SetHeaderValue(X_AMZ_CONTENT_SHA256, EVENT_STREAM_CONTENT_SHA256); //calculate date header to use in internal signature (this also goes into date header). DateTime now = GetSigningTimestamp(); Aws::String dateHeaderValue = now.ToGmtString(DateFormat::ISO_8601_BASIC); request.SetHeaderValue(AWS_DATE_HEADER, dateHeaderValue); Aws::StringStream headersStream; Aws::StringStream signedHeadersStream; for (const auto& header : CanonicalizeHeaders(request.GetHeaders())) { if(ShouldSignHeader(header.first)) { headersStream << header.first.c_str() << ":" << header.second.c_str() << NEWLINE; signedHeadersStream << header.first.c_str() << ";"; } } Aws::String canonicalHeadersString = headersStream.str(); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Canonical Header String: " << canonicalHeadersString); //calculate signed headers parameter Aws::String signedHeadersValue = signedHeadersStream.str(); //remove that last semi-colon if (!signedHeadersValue.empty()) { signedHeadersValue.pop_back(); } AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Signed Headers value:" << signedHeadersValue); //generate generalized canonicalized request string. Aws::String canonicalRequestString = CanonicalizeRequestSigningString(request, true/* m_urlEscapePath */); //append v4 stuff to the canonical request string. canonicalRequestString.append(canonicalHeadersString); canonicalRequestString.append(NEWLINE); canonicalRequestString.append(signedHeadersValue); canonicalRequestString.append(NEWLINE); canonicalRequestString.append(EVENT_STREAM_CONTENT_SHA256); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Canonical Request String: " << canonicalRequestString); //now compute sha256 on that request string auto hashResult = m_hash.Calculate(canonicalRequestString); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to hash (sha256) request string"); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "The request string is: \"" << canonicalRequestString << "\""); return false; } auto sha256Digest = hashResult.GetResult(); Aws::String canonicalRequestHash = HashingUtils::HexEncode(sha256Digest); Aws::String simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); Aws::String signingRegion = region ? region : m_region; Aws::String signingServiceName = serviceName ? serviceName : m_serviceName; Aws::String stringToSign = GenerateStringToSign(dateHeaderValue, simpleDate, canonicalRequestHash, signingRegion, signingServiceName); auto finalSignature = GenerateSignature(credentials, stringToSign, simpleDate, signingRegion, signingServiceName); Aws::StringStream ss; ss << AWS_HMAC_SHA256 << " " << CREDENTIAL << EQ << credentials.GetAWSAccessKeyId() << "/" << simpleDate << "/" << signingRegion << "/" << signingServiceName << "/" << AWS4_REQUEST << ", " << SIGNED_HEADERS << EQ << signedHeadersValue << ", " << SIGNATURE << EQ << HashingUtils::HexEncode(finalSignature); auto awsAuthString = ss.str(); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Signing request with: " << awsAuthString); request.SetAwsAuthorization(awsAuthString); request.SetSigningAccessKey(credentials.GetAWSAccessKeyId()); request.SetSigningRegion(signingRegion); return true; } // this works regardless if the current machine is Big/Little Endian static void WriteBigEndian(Aws::String& str, uint64_t n) { int shift = 56; while(shift >= 0) { str.push_back((n >> shift) & 0xFF); shift -= 8; } } bool AWSAuthEventStreamV4Signer::SignEventMessage(Event::Message& message, Aws::String& priorSignature) const { using Event::EventHeaderValue; Aws::StringStream stringToSign; stringToSign << EVENT_STREAM_PAYLOAD << NEWLINE; const DateTime now = GetSigningTimestamp(); const auto simpleDate = now.ToGmtString(SIMPLE_DATE_FORMAT_STR); stringToSign << now.ToGmtString(DateFormat::ISO_8601_BASIC) << NEWLINE << simpleDate << "/" << m_region << "/" << m_serviceName << "/aws4_request" << NEWLINE << priorSignature << NEWLINE; Aws::String nonSignatureHeaders; nonSignatureHeaders.push_back(char(sizeof(EVENTSTREAM_DATE_HEADER) - 1)); // length of the string nonSignatureHeaders += EVENTSTREAM_DATE_HEADER; nonSignatureHeaders.push_back(static_cast<char>(EventHeaderValue::EventHeaderType::TIMESTAMP)); // type of the value WriteBigEndian(nonSignatureHeaders, static_cast<uint64_t>(now.Millis())); // the value of the timestamp in big-endian auto hashOutcome = m_hash.Calculate(nonSignatureHeaders); if (!hashOutcome.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to hash (sha256) non-signature headers."); return false; } const auto nonSignatureHeadersHash = hashOutcome.GetResult(); stringToSign << HashingUtils::HexEncode(nonSignatureHeadersHash) << NEWLINE; if (message.GetEventPayload().empty()) { AWS_LOGSTREAM_WARN(v4StreamingLogTag, "Attempting to sign an empty message (no payload and no headers). " "It is unlikely that this is the intended behavior."); } else { // use a preallocatedStreamBuf to avoid making a copy. // The Hashing API requires either Aws::String or IStream as input. // TODO: the hashing API should be accept 'unsigned char*' as input. Utils::Stream::PreallocatedStreamBuf streamBuf(message.GetEventPayload().data(), message.GetEventPayload().size()); Aws::IOStream payload(&streamBuf); hashOutcome = m_hash.Calculate(payload); if (!hashOutcome.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to hash (sha256) non-signature headers."); return false; } const auto payloadHash = hashOutcome.GetResult(); stringToSign << HashingUtils::HexEncode(payloadHash); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Payload hash - " << HashingUtils::HexEncode(payloadHash)); } Utils::ByteBuffer finalSignatureDigest = GenerateSignature(m_credentialsProvider->GetAWSCredentials(), stringToSign.str(), simpleDate, m_region, m_serviceName); const auto finalSignature = HashingUtils::HexEncode(finalSignatureDigest); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Final computed signing hash: " << finalSignature); priorSignature = finalSignature; message.InsertEventHeader(EVENTSTREAM_DATE_HEADER, EventHeaderValue(now.Millis(), EventHeaderValue::EventHeaderType::TIMESTAMP)); message.InsertEventHeader(EVENTSTREAM_SIGNATURE_HEADER, std::move(finalSignatureDigest)); AWS_LOGSTREAM_INFO(v4StreamingLogTag, "Event chunk final signature - " << finalSignature); return true; } bool AWSAuthEventStreamV4Signer::ShouldSignHeader(const Aws::String& header) const { return std::find(m_unsignedHeaders.cbegin(), m_unsignedHeaders.cend(), Aws::Utils::StringUtils::ToLower(header.c_str())) == m_unsignedHeaders.cend(); } Utils::ByteBuffer AWSAuthEventStreamV4Signer::GenerateSignature(const AWSCredentials& credentials, const Aws::String& stringToSign, const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const { Utils::Threading::ReaderLockGuard guard(m_derivedKeyLock); const auto& secretKey = credentials.GetAWSSecretKey(); if (secretKey != m_currentSecretKey || simpleDate != m_currentDateStr) { guard.UpgradeToWriterLock(); // double-checked lock to prevent updating twice if (m_currentDateStr != simpleDate || m_currentSecretKey != secretKey) { m_currentSecretKey = secretKey; m_currentDateStr = simpleDate; m_derivedKey = ComputeHash(m_currentSecretKey, m_currentDateStr, region, serviceName); } } return GenerateSignature(stringToSign, m_derivedKey); } Utils::ByteBuffer AWSAuthEventStreamV4Signer::GenerateSignature(const Aws::String& stringToSign, const ByteBuffer& key) const { AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "Final String to sign: " << stringToSign); Aws::StringStream ss; auto hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)stringToSign.c_str(), stringToSign.length()), key); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Unable to hmac (sha256) final string"); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "The final string is: \"" << stringToSign << "\""); return {}; } return hashResult.GetResult(); } Aws::String AWSAuthEventStreamV4Signer::GenerateStringToSign(const Aws::String& dateValue, const Aws::String& simpleDate, const Aws::String& canonicalRequestHash, const Aws::String& region, const Aws::String& serviceName) const { //generate the actual string we will use in signing the final request. Aws::StringStream ss; ss << AWS_HMAC_SHA256 << NEWLINE << dateValue << NEWLINE << simpleDate << "/" << region << "/" << serviceName << "/" << AWS4_REQUEST << NEWLINE << canonicalRequestHash; return ss.str(); } Aws::Utils::ByteBuffer AWSAuthEventStreamV4Signer::ComputeHash(const Aws::String& secretKey, const Aws::String& simpleDate, const Aws::String& region, const Aws::String& serviceName) const { Aws::String signingKey(SIGNING_KEY); signingKey.append(secretKey); auto hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)simpleDate.c_str(), simpleDate.length()), ByteBuffer((unsigned char*)signingKey.c_str(), signingKey.length())); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to HMAC (SHA256) date string \"" << simpleDate << "\""); return {}; } auto kDate = hashResult.GetResult(); hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)region.c_str(), region.length()), kDate); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to HMAC (SHA256) region string \"" << region << "\""); return {}; } auto kRegion = hashResult.GetResult(); hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)serviceName.c_str(), serviceName.length()), kRegion); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Failed to HMAC (SHA256) service string \"" << m_serviceName << "\""); return {}; } auto kService = hashResult.GetResult(); hashResult = m_HMAC.Calculate(ByteBuffer((unsigned char*)AWS4_REQUEST, strlen(AWS4_REQUEST)), kService); if (!hashResult.IsSuccess()) { AWS_LOGSTREAM_ERROR(v4StreamingLogTag, "Unable to HMAC (SHA256) request string"); AWS_LOGSTREAM_DEBUG(v4StreamingLogTag, "The request string is: \"" << AWS4_REQUEST << "\""); return {}; } return hashResult.GetResult(); }