/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace smithy { static const char AWS_SMITHY_CLIENT_SIGNING_TAG[] = "AwsClientRequestSigning"; //4 Minutes static const std::chrono::milliseconds TIME_DIFF_MAX = std::chrono::minutes(4); //-4 Minutes static const std::chrono::milliseconds TIME_DIFF_MIN = std::chrono::minutes(-4); template class AwsClientRequestSigning { public: using HttpRequest = Aws::Http::HttpRequest; using SigningError = Aws::Client::AWSError; using SigningOutcome = Aws::Utils::FutureOutcome, SigningError>; using HttpResponseOutcome = Aws::Utils::Outcome, Aws::Client::AWSError>; static SigningOutcome SignRequest(std::shared_ptr HTTPRequest, const AuthSchemeOption& authSchemeOption, const Aws::UnorderedMap& authSchemes) { auto authSchemeIt = authSchemes.find(authSchemeOption.schemeId); if (authSchemeIt == authSchemes.end()) { assert(!"Auth scheme has not been found for a given auth option!"); return (SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE, "", "Requested AuthSchemeOption was not found within client Auth Schemes", false/*retryable*/)); } const AuthSchemesVariantT& authScheme = authSchemeIt->second; return SignWithAuthScheme(std::move(HTTPRequest), authScheme, authSchemeOption); } static bool AdjustClockSkew(HttpResponseOutcome& outcome, const AuthSchemeOption& authSchemeOption, const Aws::UnorderedMap& authSchemes) { assert(!outcome.IsSuccess()); AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_SIGNING_TAG, "If the signature check failed. This could be because of a time skew. Attempting to adjust the signer."); using DateTime = Aws::Utils::DateTime; DateTime serverTime = smithy::client::Utils::GetServerTimeFromError(outcome.GetError()); auto authSchemeIt = authSchemes.find(authSchemeOption.schemeId); if (authSchemeIt == authSchemes.end()) { assert(!"Auth scheme has not been found for a given auth option!"); return false; } AuthSchemesVariantT authScheme = authSchemeIt->second; ClockSkewVisitor visitor(outcome, serverTime, authSchemeOption); authScheme.Visit(visitor); return visitor.m_resultShouldWait; } protected: struct SignerVisitor { SignerVisitor(std::shared_ptr httpRequest, const AuthSchemeOption& targetAuthSchemeOption) : m_httpRequest(std::move(httpRequest)), m_targetAuthSchemeOption(targetAuthSchemeOption) { } const std::shared_ptr m_httpRequest; const AuthSchemeOption& m_targetAuthSchemeOption; Aws::Crt::Optional result; template void operator()(AuthSchemeAlternativeT& authScheme) { // Auth Scheme Variant alternative contains the requested auth option assert(strcmp(authScheme.schemeId, m_targetAuthSchemeOption.schemeId) == 0); using IdentityT = typename std::remove_reference::type::IdentityT; using IdentityResolver = IdentityResolverBase; using Signer = AwsSignerBase; std::shared_ptr identityResolver = authScheme.identityResolver(); if (!identityResolver) { result.emplace(SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE, "", "Auth scheme provided a nullptr identityResolver", false/*retryable*/)); return; } auto identityResult = identityResolver->getIdentity(m_targetAuthSchemeOption.identityProperties(), m_targetAuthSchemeOption.identityProperties()); if (!identityResult.IsSuccess()) { result.emplace(identityResult.GetError()); return; } auto identity = std::move(identityResult.GetResultWithOwnership()); std::shared_ptr signer = authScheme.signer(); if (!signer) { result.emplace(SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE, "", "Auth scheme provided a nullptr signer", false/*retryable*/)); return; } result.emplace(signer->sign(m_httpRequest, *identity, m_targetAuthSchemeOption.signerProperties())); } }; static SigningOutcome SignWithAuthScheme(std::shared_ptr httpRequest, const AuthSchemesVariantT& authSchemesVariant, const AuthSchemeOption& targetAuthSchemeOption) { SignerVisitor visitor(httpRequest, targetAuthSchemeOption); AuthSchemesVariantT authSchemesVariantCopy(authSchemesVariant); // TODO: allow const visiting authSchemesVariantCopy.Visit(visitor); if (!visitor.result) { return (SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE, "", "Failed to sign with an unknown error", false/*retryable*/)); } return std::move(*visitor.result); } struct ClockSkewVisitor { using DateTime = Aws::Utils::DateTime; using DateFormat = Aws::Utils::DateFormat; using ClientError = Aws::Client::AWSError; ClockSkewVisitor(HttpResponseOutcome& outcome, const DateTime& serverTime, const AuthSchemeOption& targetAuthSchemeOption) : m_outcome(outcome), m_serverTime(serverTime), m_targetAuthSchemeOption(targetAuthSchemeOption) { } bool m_resultShouldWait = false; HttpResponseOutcome& m_outcome; const Aws::Utils::DateTime& m_serverTime; const AuthSchemeOption& m_targetAuthSchemeOption; template void operator()(AuthSchemeAlternativeT& authScheme) { // Auth Scheme Variant alternative contains the requested auth option assert(strcmp(authScheme.schemeId, m_targetAuthSchemeOption.schemeId) == 0); using IdentityT = typename std::remove_reference::type::IdentityT; using Signer = AwsSignerBase; std::shared_ptr signer = authScheme.signer(); if (!signer) { AWS_LOGSTREAM_ERROR(AWS_SMITHY_CLIENT_SIGNING_TAG, "Failed to adjust signing clock skew. Signer is null."); return; } const auto signingTimestamp = signer->GetSigningTimestamp(); if (!m_serverTime.WasParseSuccessful() || m_serverTime == DateTime()) { AWS_LOGSTREAM_DEBUG(AWS_SMITHY_CLIENT_SIGNING_TAG, "Date header was not found in the response, can't attempt to detect clock skew"); return; } AWS_LOGSTREAM_DEBUG(AWS_SMITHY_CLIENT_SIGNING_TAG, "Server time is " << m_serverTime.ToGmtString(DateFormat::RFC822) << ", while client time is " << DateTime::Now().ToGmtString(DateFormat::RFC822)); auto diff = DateTime::Diff(m_serverTime, signingTimestamp); //only try again if clock skew was the cause of the error. if (diff >= TIME_DIFF_MAX || diff <= TIME_DIFF_MIN) { diff = DateTime::Diff(m_serverTime, DateTime::Now()); AWS_LOGSTREAM_INFO(AWS_SMITHY_CLIENT_SIGNING_TAG, "Computed time difference as " << diff.count() << " milliseconds. Adjusting signer with the skew."); signer->SetClockSkew(diff); ClientError newError(m_outcome.GetError()); newError.SetRetryableType(Aws::Client::RetryableType::RETRYABLE); m_outcome = std::move(newError); m_resultShouldWait = true; } } }; }; }