/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using Amazon.Runtime; using Amazon.Runtime.Internal; using Amazon.Runtime.Internal.Auth; using Amazon.Runtime.Internal.Util; using Amazon.Runtime.SharedInterfaces; using Amazon.Util; using Aws.Crt.Auth; using Aws.Crt.Http; using AWSSDK.Extensions.CrtIntegration; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; namespace Amazon.Extensions.CrtIntegration { /// /// Asymmetric Sigv4 (SigV4a) protocol signer using the implementation provided by Aws.Crt.Auth /// public class CrtAWS4aSigner : IAWSSigV4aProvider { public CrtAWS4aSigner() : this(true) { } public CrtAWS4aSigner(bool signPayload) { SignPayload = signPayload; } public bool SignPayload { get; private set; } /// /// Protocol for the requests being signed /// public ClientProtocol Protocol { get { return ClientProtocol.RestProtocol; } } /// /// Calculates and signs the specified request using the Asymmetric SigV4 signing protocol /// by using theAWS account credentials given in the method parameters. The resulting signature /// is added to the request headers as 'Authorization'. /// /// /// The request to compute the signature for. Additional headers mandated by the /// SigV4a protocol will be added to the request before signing. /// /// /// Client configuration data encompassing the service call (notably authentication /// region, endpoint and service name). /// /// Metrics for the request /// The AWS credentials for the account making the service call public void Sign(IRequest request, IClientConfig clientConfig, RequestMetrics metrics, ImmutableCredentials credentials) { SignRequest(request, clientConfig, metrics, credentials); } /// /// Calculates the signature for the specified request using the Asymmetric SigV4 signing protocol /// /// /// The request to compute the signature for. Additional headers mandated by the /// SigV4a protocol will be added to the request before signing. /// /// /// Client configuration data encompassing the service call (notably authentication /// region, endpoint and service name). /// /// Metrics for the request /// The AWS credentials for the account making the service call /// AWS4aSigningResult for the given request public AWS4aSigningResult SignRequest(IRequest request, IClientConfig clientConfig, RequestMetrics metrics, ImmutableCredentials credentials) { var signedAt = AWS4Signer.InitializeHeaders(request.Headers, request.Endpoint); var serviceSigningName = !string.IsNullOrEmpty(request.OverrideSigningServiceName) ? request.OverrideSigningServiceName : AWS4Signer.DetermineService(clientConfig); if (serviceSigningName == "s3") { // Older versions of the S3 package can be used with newer versions of Core, this guarantees no double encoding will be used. // The new behavior uses endpoint resolution rules, which are not present prior to 3.7.100 request.UseDoubleEncoding = false; } var regionSet = AWS4Signer.DetermineSigningRegion(clientConfig, clientConfig.RegionEndpointServiceName, request.AlternateEndpoint, request); request.DeterminedSigningRegion = regionSet; AWS4Signer.SetXAmzTrailerHeader(request.Headers, request.TrailingHeaders); var signingConfig = PrepareCRTSigningConfig( AwsSignatureType.HTTP_REQUEST_VIA_HEADERS, regionSet, serviceSigningName, signedAt, credentials, request.UseDoubleEncoding); // If the request should use a fixed x-amz-content-sha256 header value, determine the appropriate one var fixedBodyHash = request.TrailingHeaders?.Count > 0 ? AWS4Signer.V4aStreamingBodySha256WithTrailer : AWS4Signer.V4aStreamingBodySha256; signingConfig.SignedBodyValue = AWS4Signer.SetRequestBodyHash(request, SignPayload, fixedBodyHash, ChunkedUploadWrapperStream.V4A_SIGNATURE_LENGTH); var crtRequest = CrtHttpRequestConverter.ConvertToCrtRequest(request); var signingResult = AwsSigner.SignHttpRequest(crtRequest, signingConfig); var authorizationValue = Encoding.Default.GetString(signingResult.Get().Signature); var signedCrtRequest = signingResult.Get().SignedRequest; CrtHttpRequestConverter.CopyHeadersFromCrtRequest(request, signedCrtRequest); var dateStamp = AWS4Signer.FormatDateTime(signedAt, AWSSDKUtils.ISO8601BasicDateFormat); var scope = string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}", dateStamp, serviceSigningName, AWS4Signer.Terminator); AWS4aSigningResult result = new AWS4aSigningResult( credentials.AccessKey, signedAt, CrtHttpRequestConverter.ExtractSignedHeaders(signedCrtRequest), scope, regionSet, authorizationValue, serviceSigningName, "", credentials); return result; } /// /// /// Calculates the signature for the specified request using the Asymmetric SigV4 /// signing protocol in preparation for generating a presigned URL. /// /// /// The request to compute the signature for. Additional headers mandated by the /// SigV4a protocol will be added to the request before signing. /// /// /// Client configuration data encompassing the service call (notably authentication /// region, endpoint and service name). /// /// Metrics for the request /// The AWS credentials for the account making the service call /// Service to sign the request for /// Region to sign the request for /// AWS4aSigningResult for the given request public AWS4aSigningResult Presign4a(IRequest request, IClientConfig clientConfig, RequestMetrics metrics, ImmutableCredentials credentials, string serviceSigningName, string overrideSigningRegion) { if (serviceSigningName == "s3") { // Older versions of the S3 package can be used with newer versions of Core, this guarantees no double encoding will be used. // The new behavior uses endpoint resolution rules, which are not present prior to 3.7.100 request.UseDoubleEncoding = false; } var signedAt = AWS4Signer.InitializeHeaders(request.Headers, request.Endpoint); var regionSet = overrideSigningRegion ?? AWS4Signer.DetermineSigningRegion(clientConfig, clientConfig.RegionEndpointServiceName, request.AlternateEndpoint, request); var signingConfig = PrepareCRTSigningConfig( AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS, regionSet, serviceSigningName, signedAt, credentials, request.UseDoubleEncoding); if (AWS4PreSignedUrlSigner.ServicesUsingUnsignedPayload.Contains(serviceSigningName)) { signingConfig.SignedBodyValue = AWS4Signer.UnsignedPayload; } else { signingConfig.SignedBodyValue = AWS4Signer.EmptyBodySha256; } // The expiration may have already be set in a header when marshalling the GetPreSignedUrlRequest -> IRequest if (request.Parameters != null && request.Parameters.ContainsKey(HeaderKeys.XAmzExpires)) { signingConfig.ExpirationInSeconds = Convert.ToUInt64(request.Parameters[HeaderKeys.XAmzExpires]); } var crtRequest = CrtHttpRequestConverter.ConvertToCrtRequest(request); var signingResult = AwsSigner.SignHttpRequest(crtRequest, signingConfig); string authorizationValue = Encoding.Default.GetString(signingResult.Get().Signature); var dateStamp = AWS4Signer.FormatDateTime(signedAt, AWSSDKUtils.ISO8601BasicDateFormat); var scope = string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}", dateStamp, serviceSigningName, AWS4Signer.Terminator); AWS4aSigningResult result = new AWS4aSigningResult( credentials.AccessKey, signedAt, CrtHttpRequestConverter.ExtractSignedHeaders(signingResult.Get().SignedRequest), scope, regionSet, authorizationValue, serviceSigningName, signingResult.Get().SignedRequest.Uri, credentials); return result; } /// /// Signs one chunk of a request when transferring a payload using multiple chunks /// /// Content of the current chunk to sign /// Signature of the previously signed chunk /// Signing result for the "seed" signature consisting of headers /// Signature of the current chunk public string SignChunk(Stream chunkBody, string previousSignature, AWS4aSigningResult headerSigningResult) { var signingConfig = PrepareCRTSigningConfig(AwsSignatureType.HTTP_REQUEST_CHUNK, headerSigningResult); signingConfig.SignedBodyHeader = AwsSignedBodyHeaderType.NONE; // The previous signature may be padded with '*' up to 144 characters, which is used // when actually sending a chunk but not when calculating the next chunk's signature. previousSignature = previousSignature.TrimEnd('*'); var signingResult = AwsSigner.SignChunk(chunkBody, Encoding.UTF8.GetBytes(previousSignature), signingConfig); return Encoding.UTF8.GetString(signingResult.Get().Signature); } /// /// Signs the final chunk containing trailing headers /// /// Trailing header keys and values /// Signature of the previously signed chunk /// Signing result for the "seed" signature consisting of headers /// Signature of the trailing header chunk public string SignTrailingHeaderChunk(IDictionary trailingHeaders, string previousSignature, AWS4aSigningResult headerSigningResult) { var signingConfig = PrepareCRTSigningConfig(AwsSignatureType.HTTP_REQUEST_TRAILING_HEADERS, headerSigningResult); signingConfig.SignedBodyHeader = AwsSignedBodyHeaderType.NONE; var headerArray = trailingHeaders.Select(kvp => new HttpHeader(kvp.Key, kvp.Value)).ToArray(); // The previous signature may be padded with '*' up to 144 characters, which is used // when actually sending a chunk but not when calculating the next chunk's signature. previousSignature = previousSignature.TrimEnd('*'); var signingResult = AwsSigner.SignTrailingHeaders(headerArray, Encoding.UTF8.GetBytes(previousSignature), signingConfig); return Encoding.UTF8.GetString(signingResult.Get().Signature); } /// /// Helper function to set up an Aws.Crt.Auth.SigningConfig /// /// Signature type /// Signing result for the request's headers /// Prepared CRT signing configuration public AwsSigningConfig PrepareCRTSigningConfig(AwsSignatureType signatureType, AWS4aSigningResult headerSigningResult) { return PrepareCRTSigningConfig(signatureType, headerSigningResult.RegionSet, headerSigningResult.Service, headerSigningResult.DateTime, headerSigningResult.Credentials, useDoubleEncoding: true); } /// /// Helper function to set up an Aws.Crt.Auth.SigningConfig /// /// Signature type /// Signing region /// Service to sign the request for /// Timestamp to sign at /// The AWS credentials for the account making the service call /// Use double uri encoding when required /// Prepared CRT signing configuration public AwsSigningConfig PrepareCRTSigningConfig(AwsSignatureType signatureType, string region, string service, DateTime signedAt, ImmutableCredentials credentials, bool useDoubleEncoding) { var signingConfig = new AwsSigningConfig { Algorithm = AwsSigningAlgorithm.SIGV4A, SignedBodyHeader = AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA256, SignatureType = signatureType, Region = region, Service = service, Timestamp = new DateTimeOffset(signedAt), Credentials = new Credentials(credentials.AccessKey, credentials.SecretKey, credentials.Token) }; signingConfig.UseDoubleUriEncode = useDoubleEncoding; signingConfig.ShouldNormalizeUriPath = useDoubleEncoding; return signingConfig; } } }