GameLiftPlugin/Source/AWSSDK/Include/smithy/client/features/ChecksumInterceptor.h (232 lines of code) (raw):

/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #pragma once #include <smithy/interceptor/Interceptor.h> #include <aws/core/AmazonWebServiceRequest.h> #include <aws/core/http/HttpRequest.h> #include <aws/core/http/HttpResponse.h> #include <aws/core/utils/HashingUtils.h> #include <aws/core/utils/crypto/MD5.h> #include <aws/core/utils/crypto/CRC32.h> #include <aws/core/utils/crypto/Sha256.h> #include <aws/core/utils/crypto/Sha1.h> #include <aws/core/utils/crypto/PrecalculatedHash.h> #include <aws/core/platform/Environment.h> #include <iomanip> namespace smithy { namespace client { static const char AWS_SMITHY_CLIENT_CHECKSUM[] = "AwsSmithyClientChecksums"; static const char CHECKSUM_CONTENT_MD5_HEADER[] = "content-md5"; class ChecksumInterceptor: public smithy::interceptor::Interceptor { public: using HeaderValueCollection = Aws::Http::HeaderValueCollection; using HashingUtils = Aws::Utils::HashingUtils; using MD5 = Aws::Utils::Crypto::MD5; using CRC32 = Aws::Utils::Crypto::CRC32; using CRC32C = Aws::Utils::Crypto::CRC32C; using Sha256 = Aws::Utils::Crypto::Sha256; using Sha1 = Aws::Utils::Crypto::Sha1; using PrecalculatedHash = Aws::Utils::Crypto::PrecalculatedHash; ~ChecksumInterceptor() override = default; ChecksumInterceptor() = default; ChecksumInterceptor(const ChecksumInterceptor& other) = delete; ChecksumInterceptor(ChecksumInterceptor&& other) noexcept = default; ChecksumInterceptor& operator=(const ChecksumInterceptor& other) = delete; ChecksumInterceptor& operator=(ChecksumInterceptor&& other) noexcept = default; static std::shared_ptr<Aws::IOStream> GetBodyStream(const Aws::AmazonWebServiceRequest& request) { if (request.GetBody() != nullptr) { return request.GetBody(); } // Return an empty string stream for no body return Aws::MakeShared<Aws::StringStream>(AWS_SMITHY_CLIENT_CHECKSUM, ""); } ModifyRequestOutcome ModifyBeforeSigning(interceptor::InterceptorContext& context) override { const auto& httpRequest = context.GetTransmitRequest(); const auto& request = context.GetModeledRequest(); if (httpRequest == nullptr) { return Aws::Client::AWSError<Aws::Client::CoreErrors>{Aws::Client::CoreErrors::VALIDATION, "ValidationErrorException", "Checksum request validation missing request", false}; } Aws::String checksumAlgorithmName = Aws::Utils::StringUtils::ToLower( request.GetChecksumAlgorithmName().c_str()); if (request.GetServiceSpecificParameters()) { auto requestChecksumOverride = request.GetServiceSpecificParameters()->parameterMap.find( "overrideChecksum"); if (requestChecksumOverride != request.GetServiceSpecificParameters()->parameterMap.end()) { checksumAlgorithmName = requestChecksumOverride->second; } } bool shouldSkipChecksum = request.GetServiceSpecificParameters() && request.GetServiceSpecificParameters()->parameterMap.find("overrideChecksumDisable") != request.GetServiceSpecificParameters()->parameterMap.end(); //Check if user has provided the checksum algorithm if (!checksumAlgorithmName.empty() && !shouldSkipChecksum) { // Check if user has provided a checksum value for the specified algorithm const Aws::String checksumType = "x-amz-checksum-" + checksumAlgorithmName; const HeaderValueCollection& headers = request.GetHeaders(); const auto checksumHeader = headers.find(checksumType); bool checksumValueAndAlgorithmProvided = checksumHeader != headers.end(); // For non-streaming payload, the resolved checksum location is always header. // For streaming payload, the resolved checksum location depends on whether it is an unsigned payload, we let AwsAuthSigner decide it. if (request.IsStreaming() && checksumValueAndAlgorithmProvided) { const auto hash = Aws::MakeShared<PrecalculatedHash>( AWS_SMITHY_CLIENT_CHECKSUM, checksumHeader->second); httpRequest->SetRequestHash(checksumAlgorithmName, hash); } else if (checksumValueAndAlgorithmProvided) { httpRequest->SetHeaderValue(checksumType, checksumHeader->second); } else if (checksumAlgorithmName == "crc32") { if (request.IsStreaming()) { httpRequest->SetRequestHash(checksumAlgorithmName, Aws::MakeShared<CRC32>(AWS_SMITHY_CLIENT_CHECKSUM)); } else { httpRequest->SetHeaderValue(checksumType, HashingUtils::Base64Encode( HashingUtils::CalculateCRC32(*(GetBodyStream(request))))); } } else if (checksumAlgorithmName == "crc32c") { if (request.IsStreaming()) { httpRequest->SetRequestHash(checksumAlgorithmName, Aws::MakeShared<CRC32C>(AWS_SMITHY_CLIENT_CHECKSUM)); } else { httpRequest->SetHeaderValue(checksumType, HashingUtils::Base64Encode( HashingUtils::CalculateCRC32C(*(GetBodyStream(request))))); } } else if (checksumAlgorithmName == "sha256") { if (request.IsStreaming()) { httpRequest->SetRequestHash(checksumAlgorithmName, Aws::MakeShared<Sha256>(AWS_SMITHY_CLIENT_CHECKSUM)); } else { httpRequest->SetHeaderValue(checksumType, HashingUtils::Base64Encode( HashingUtils::CalculateSHA256(*(GetBodyStream(request))))); } } else if (checksumAlgorithmName == "sha1") { if (request.IsStreaming()) { httpRequest->SetRequestHash(checksumAlgorithmName, Aws::MakeShared<Sha1>(AWS_SMITHY_CLIENT_CHECKSUM)); } else { httpRequest->SetHeaderValue(checksumType, HashingUtils::Base64Encode( HashingUtils::CalculateSHA1(*(GetBodyStream(request))))); } } else if (checksumAlgorithmName == "md5" && headers.find(CHECKSUM_CONTENT_MD5_HEADER) == headers.end()) { httpRequest->SetHeaderValue(CHECKSUM_CONTENT_MD5_HEADER, HashingUtils::Base64Encode( HashingUtils::CalculateMD5(*(GetBodyStream(request))))); } else { AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_CHECKSUM, "Checksum algorithm: " << checksumAlgorithmName << "is not supported by SDK."); } } // Response checksums if (request.ShouldValidateResponseChecksum()) { for (const Aws::String& responseChecksumAlgorithmName : request.GetResponseChecksumAlgorithmNames()) { checksumAlgorithmName = Aws::Utils::StringUtils::ToLower(responseChecksumAlgorithmName.c_str()); if (checksumAlgorithmName == "crc32c") { std::shared_ptr<CRC32C> crc32c = Aws::MakeShared< CRC32C>(AWS_SMITHY_CLIENT_CHECKSUM); httpRequest->AddResponseValidationHash("crc32c", crc32c); } else if (checksumAlgorithmName == "crc32") { std::shared_ptr<CRC32> crc32 = Aws::MakeShared< CRC32>(AWS_SMITHY_CLIENT_CHECKSUM); httpRequest->AddResponseValidationHash("crc32", crc32); } else if (checksumAlgorithmName == "sha1") { std::shared_ptr<Sha1> sha1 = Aws::MakeShared<Sha1>( AWS_SMITHY_CLIENT_CHECKSUM); httpRequest->AddResponseValidationHash("sha1", sha1); } else if (checksumAlgorithmName == "sha256") { std::shared_ptr<Sha256> sha256 = Aws::MakeShared< Sha256>(AWS_SMITHY_CLIENT_CHECKSUM); httpRequest->AddResponseValidationHash("sha256", sha256); } else { AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_CHECKSUM, "Checksum algorithm: " << checksumAlgorithmName << " is not supported in validating response body yet."); } } } return httpRequest; } ModifyResponseOutcome ModifyBeforeDeserialization(interceptor::InterceptorContext& context) override { const auto httpRequest = context.GetTransmitRequest(); const auto httpResponse = context.GetTransmitResponse(); if (httpRequest == nullptr || httpResponse == nullptr) { return Aws::Client::AWSError<Aws::Client::CoreErrors>{Aws::Client::CoreErrors::VALIDATION, "ValidationErrorException", "Checksum response validation missing request or response", false}; } for (const auto& hashIterator : httpRequest->GetResponseValidationHashes()) { Aws::String checksumHeaderKey = Aws::String("x-amz-checksum-") + hashIterator.first; // TODO: If checksum ends with -#, then skip if (httpResponse->HasHeader(checksumHeaderKey.c_str())) { const Aws::String& checksumHeaderValue = httpResponse->GetHeader(checksumHeaderKey); if (HashingUtils::Base64Encode(hashIterator.second->GetHash().GetResult()) != checksumHeaderValue) { auto error = Aws::Client::AWSError<Aws::Client::CoreErrors>{ Aws::Client::CoreErrors::VALIDATION, "", "Response checksums mismatch", false/*retryable*/}; error.SetResponseHeaders(httpResponse->GetHeaders()); error.SetResponseCode(httpResponse->GetResponseCode()); error.SetRemoteHostIpAddress( httpResponse->GetOriginatingRequest().GetResolvedRemoteHost()); AWS_LOGSTREAM_ERROR(AWS_SMITHY_CLIENT_CHECKSUM, error); return {error}; } // Validate only a single checksum returned in an HTTP response break; } } return httpResponse; } }; } }