diff --git a/README.md b/README.md
index 9e6d2f4a02a8..ca13af630a98 100644
--- a/README.md
+++ b/README.md
@@ -71,11 +71,9 @@ You can find the archive for _**legacy**_ Unity support at https://github.com/aw
This SDK has optional functionality that requires the [AWS Common Runtime (CRT)](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)
bindings to be included as a dependency with your application. This functionality includes:
-* [Amazon S3 Multi-Region Access Points](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiRegionAccessPoints.html)
* [Amazon S3 Object Integrity](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html)
-* Amazon EventBridge Global Endpoints
-If the required AWS Common Runtime components are not installed you will receive an error like `Attempting to make a request that requires an implementation of AWS Signature V4a. Add a reference to the AWSSDK.Extensions.CrtIntegration NuGet package to your project to include the AWS Signature V4a signer.`,
+If the required AWS Common Runtime components are not installed you will receive an error like `Attempting to handle a request that requires additional checksums. Add a reference to the AWSSDK.Extensions.CrtIntegration NuGet package to your project to include the AWS Common Runtime checksum implementation.`,
indicating that the required dependency is missing to use the associated functionality. To install this dependency follow
the provided [instructions](#installing-the-aws-common-runtime-crt-dependency).
diff --git a/extensions/src/AWSSDK.Extensions.CrtIntegration/CrtAWS4aSigner.cs b/extensions/src/AWSSDK.Extensions.CrtIntegration/CrtAWS4aSigner.cs
index ad3361423e93..297760b72aa8 100644
--- a/extensions/src/AWSSDK.Extensions.CrtIntegration/CrtAWS4aSigner.cs
+++ b/extensions/src/AWSSDK.Extensions.CrtIntegration/CrtAWS4aSigner.cs
@@ -24,6 +24,7 @@
using AWSSDK.Extensions.CrtIntegration;
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -34,6 +35,7 @@ namespace Amazon.Extensions.CrtIntegration
///
/// Asymmetric Sigv4 (SigV4a) protocol signer using the implementation provided by Aws.Crt.Auth
///
+ [Obsolete("Use Amazon.Runtime.Internal.Auth.AWS4aSigner instead."), EditorBrowsable(EditorBrowsableState.Never)]
public class CrtAWS4aSigner : IAWSSigV4aProvider
{
///
diff --git a/generator/.DevConfigs/b495f893-31aa-4e17-866c-af78e4da6e8b.json b/generator/.DevConfigs/b495f893-31aa-4e17-866c-af78e4da6e8b.json
new file mode 100644
index 000000000000..df3bceb7e824
--- /dev/null
+++ b/generator/.DevConfigs/b495f893-31aa-4e17-866c-af78e4da6e8b.json
@@ -0,0 +1,10 @@
+{
+ "core": {
+ "updateMinimum": false,
+ "type": "patch",
+ "changeLogMessages": [
+ "Added AWS4aSigner class that implements SigV4a signing in managed code. AWS CRT is no longer used by the SDK for this purpose, and the related APIs were deprecated.",
+ "The AWS4aSigningResult.PresignedUri property was deprecated and will always be empty in objects returned by AWS4aSigner. Use the ForQueryParameters property instead, to get the query parameters for a presigned URL."
+ ]
+ }
+}
\ No newline at end of file
diff --git a/sdk/src/Core/AWSSDK.Core.NetFramework.csproj b/sdk/src/Core/AWSSDK.Core.NetFramework.csproj
index 5e8f36044fc1..2e6b45ea1e56 100644
--- a/sdk/src/Core/AWSSDK.Core.NetFramework.csproj
+++ b/sdk/src/Core/AWSSDK.Core.NetFramework.csproj
@@ -70,6 +70,7 @@
+
-
+
+
+
+
+
\ No newline at end of file
diff --git a/sdk/src/Core/Amazon.Runtime/Credentials/ImmutableCredentials.cs b/sdk/src/Core/Amazon.Runtime/Credentials/ImmutableCredentials.cs
index 4bb0e2f0bbd5..76ebd4ce8e4a 100644
--- a/sdk/src/Core/Amazon.Runtime/Credentials/ImmutableCredentials.cs
+++ b/sdk/src/Core/Amazon.Runtime/Credentials/ImmutableCredentials.cs
@@ -15,6 +15,7 @@
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
using System;
+using System.Security.Cryptography;
namespace Amazon.Runtime
{
@@ -55,6 +56,15 @@ public class ImmutableCredentials
/// Account-based endpoints take the form https://accountid.ddb.region.amazonaws.com
///
public string AccountId { get; private set; }
+
+ ///
+ /// Contains a cached AWS4a signing key for the current credentials. Do
+ /// not access this property directly; use instead.
+ ///
+ ///
+ /// The key must not be disposed, and must not be returned to the user.
+ ///
+ internal ECDsa AWS4aSigningKey;
#endregion
diff --git a/sdk/src/Core/Amazon.Runtime/Credentials/Internal/AwsV4aAuthScheme.cs b/sdk/src/Core/Amazon.Runtime/Credentials/Internal/AwsV4aAuthScheme.cs
index cbd7c8dadda3..784fcd7c7030 100644
--- a/sdk/src/Core/Amazon.Runtime/Credentials/Internal/AwsV4aAuthScheme.cs
+++ b/sdk/src/Core/Amazon.Runtime/Credentials/Internal/AwsV4aAuthScheme.cs
@@ -13,7 +13,6 @@
* permissions and limitations under the License.
*/
-using System.Threading;
using Amazon.Runtime.Identity;
using Amazon.Runtime.Internal.Auth;
@@ -25,7 +24,7 @@ namespace Amazon.Runtime.Credentials.Internal
///
public class AwsV4aAuthScheme : IAuthScheme
{
- private static ISigner _signer;
+ private static readonly ISigner _signer = new AWS4aSigner();
///
public string SchemeId => AuthSchemeOption.SigV4A;
@@ -35,14 +34,6 @@ public IIdentityResolver GetIdentityResolver(IIdentityResolverConfiguration conf
=> configuration.GetIdentityResolver();
///
- public ISigner Signer()
- {
- if (_signer == null)
- {
- Interlocked.Exchange(ref _signer, new AWS4aSignerCRTWrapper());
- }
-
- return _signer;
- }
+ public ISigner Signer() => _signer;
}
}
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4Signer.cs b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4Signer.cs
index 3753da711ea2..0db77aed030d 100644
--- a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4Signer.cs
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4Signer.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
@@ -31,7 +31,6 @@ namespace Amazon.Runtime.Internal.Auth
///
public class AWS4Signer : AbstractAWSSigner
{
-
public const string Scheme = "AWS4";
public const string Algorithm = "HMAC-SHA256";
public const string Sigv4aAlgorithm = "ECDSA-P256-SHA256";
@@ -299,7 +298,7 @@ private static void CleanHeaders(IDictionary headers)
}
}
- private static void ValidateRequest(IRequest request)
+ internal static void ValidateRequest(IRequest request)
{
Uri url = request.Endpoint;
@@ -630,7 +629,7 @@ public static byte[] ComputeHash(byte[] data)
#endregion
#region Private Signing Helpers
- static string SetPayloadSignatureHeader(IRequest request, string payloadHash)
+ internal static string SetPayloadSignatureHeader(IRequest request, string payloadHash)
{
if (request.Headers.ContainsKey(HeaderKeys.XAmzContentSha256Header))
request.Headers[HeaderKeys.XAmzContentSha256Header] = payloadHash;
@@ -704,7 +703,7 @@ public static string DetermineService(IClientConfig clientConfig, IRequest reque
/// will look for the hash as a header on the request.
///
/// Canonicalised request as a string
- protected static string CanonicalizeRequest(Uri endpoint,
+ protected internal static string CanonicalizeRequest(Uri endpoint,
string resourcePath,
string httpMethod,
IDictionary sortedHeaders,
@@ -728,7 +727,7 @@ protected static string CanonicalizeRequest(Uri endpoint,
/// will look for the hash as a header on the request.
///
/// Canonicalised request as a string
- protected static string CanonicalizeRequest(Uri endpoint,
+ protected internal static string CanonicalizeRequest(Uri endpoint,
string resourcePath,
string httpMethod,
IDictionary sortedHeaders,
@@ -761,7 +760,7 @@ protected static string CanonicalizeRequest(Uri endpoint,
///
/// Encode "/" when canonicalize resource path
/// Canonicalised request as a string
- protected static string CanonicalizeRequest(Uri endpoint,
+ protected internal static string CanonicalizeRequest(Uri endpoint,
string resourcePath,
string httpMethod,
IDictionary sortedHeaders,
@@ -866,7 +865,7 @@ protected internal static string CanonicalizeHeaders(IEnumerable
/// The headers included in the signature
/// Formatted string of header names
- protected static string CanonicalizeHeaderNames(IEnumerable> sortedHeaders)
+ protected internal static string CanonicalizeHeaderNames(IEnumerable> sortedHeaders)
{
var builder = new ValueStringBuilder(512);
@@ -886,7 +885,7 @@ protected static string CanonicalizeHeaderNames(IEnumerable
/// The in-flight request being signed
/// The fused set of parameters
- protected static List> GetParametersToCanonicalize(IRequest request)
+ protected internal static List> GetParametersToCanonicalize(IRequest request)
{
var parametersToCanonicalize = new List>();
@@ -968,7 +967,7 @@ protected static string CanonicalizeQueryParameters(string queryString, bool uri
return CanonicalizeQueryParameters(queryParams, uriEncodeParameters: uriEncodeParameters);
}
- protected static string CanonicalizeQueryParameters(IEnumerable> parameters)
+ protected internal static string CanonicalizeQueryParameters(IEnumerable> parameters)
{
return CanonicalizeQueryParameters(parameters, true);
}
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigner.cs b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigner.cs
new file mode 100644
index 000000000000..2fd4e32fc1a8
--- /dev/null
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigner.cs
@@ -0,0 +1,585 @@
+/*
+ * 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 System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using Amazon.Util;
+using Amazon.Runtime.Internal.Util;
+using Amazon.Runtime.Identity;
+using System.Threading;
+
+#if !NET7_0_OR_GREATER
+using System.Formats.Asn1;
+#endif
+
+using static Amazon.Runtime.Internal.Auth.AWS4Signer;
+
+namespace Amazon.Runtime.Internal.Auth
+{
+ ///
+ /// AWS4a protocol signer for service calls that transmit authorization in the header field "Authorization".
+ ///
+ public class AWS4aSigner : AbstractAWSSigner
+ {
+ internal const string Scheme = "AWS4A";
+
+ ///
+ /// Represents the elliptic curve used for signing requests with the AWS4a protocol.
+ ///
+ private static readonly ECCurve SigningCurve = ECCurve.NamedCurves.nistP256;
+ ///
+ /// Represents the value of N-2, where N is the order of the
+ /// NIST P-256 curve group.
+ ///
+ private static readonly byte[] NMinus2 = new byte[] {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+ 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x4F,
+ };
+
+ internal const SigningAlgorithm SignerAlgorithm = SigningAlgorithm.HmacSHA256;
+
+ public AWS4aSigner()
+ : this(true)
+ {
+ }
+
+ public AWS4aSigner(bool signPayload)
+ {
+ SignPayload = signPayload;
+ }
+
+ public bool SignPayload { get; }
+
+ public override ClientProtocol Protocol
+ {
+ get { return ClientProtocol.RestProtocol; }
+ }
+
+ ///
+ /// Calculates and signs the specified request using the AWS4a signing protocol by using the
+ /// AWS account credentials given in the method parameters. The resulting signature is added
+ /// to the request headers as 'Authorization'. Parameters supplied in the request, either in
+ /// the resource path as a query string or in the Parameters collection must not have been
+ /// uri encoded. If they have, use the SignRequest method to obtain a signature.
+ ///
+ ///
+ /// The request to compute the signature for. Additional headers mandated by the AWS4a protocol
+ /// ('host' and 'x-amz-date') 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.
+ ///
+ ///
+ /// If any problems are encountered while signing the request.
+ ///
+ public override void Sign(IRequest request,
+ IClientConfig clientConfig,
+ RequestMetrics metrics,
+ BaseIdentity identity)
+ {
+ if (identity is not AWSCredentials credentials)
+ {
+ throw new AmazonClientException($"The identity parameter must be of type AWSCredentials for the signer {nameof(AWS4aSigner)}.");
+ }
+
+ var immutableCredentials = credentials.GetCredentials();
+ if (immutableCredentials is null)
+ {
+ return;
+ }
+
+ var signingResult = SignRequest(request, clientConfig, metrics, immutableCredentials);
+ request.AWS4aSignerResult = signingResult;
+ request.Headers[HeaderKeys.AuthorizationHeader] = signingResult.ForAuthorizationHeader;
+ }
+
+ ///
+ /// Calculates and signs the specified request using the AWS4a signing protocol by using the
+ /// AWS account credentials given in the method parameters.
+ ///
+ ///
+ /// The request to compute the signature for. Additional headers mandated by the AWS4a protocol
+ /// ('host' and 'x-amz-date') 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.
+ ///
+ ///
+ /// If any problems are encountered while signing the request.
+ ///
+ ///
+ /// Parameters passed as part of the resource path should be uri-encoded prior to
+ /// entry to the signer. Parameters passed in the request.Parameters collection should
+ /// be not be encoded; encoding will be done for these parameters as part of the
+ /// construction of the canonical request.
+ ///
+ public AWS4aSigningResult SignRequest(IRequest request,
+ IClientConfig clientConfig,
+ RequestMetrics metrics,
+ ImmutableCredentials credentials)
+ {
+ ValidateRequest(request);
+ var signedAt = InitializeHeaders(request.Headers, request.Endpoint);
+
+ var serviceSigningName = !string.IsNullOrEmpty(request.OverrideSigningServiceName)
+ ? request.OverrideSigningServiceName
+ : DetermineService(clientConfig, request);
+
+ 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 region = DetermineSigningRegion(clientConfig, clientConfig.RegionEndpointServiceName, request.AlternateEndpoint, request);
+ request.DeterminedSigningRegion = region;
+ request.Headers[HeaderKeys.XAmzRegionSetHeader] = region;
+ SetXAmzTrailerHeader(request.Headers, request.TrailingHeaders);
+
+ var parametersToCanonicalize = GetParametersToCanonicalize(request);
+ var canonicalParameters = CanonicalizeQueryParameters(parametersToCanonicalize);
+
+ // If the request should use a fixed x-amz-content-sha256 header value, determine the appropriate one
+ var bodySha = request.TrailingHeaders?.Count > 0
+ ? V4aStreamingBodySha256WithTrailer
+ : V4aStreamingBodySha256;
+
+ var bodyHash = SetRequestBodyHash(request, SignPayload, bodySha, ChunkedUploadWrapperStream.V4A_SIGNATURE_LENGTH);
+ var sortedHeaders = SortAndPruneHeaders(request.Headers);
+
+ var canonicalRequest = CanonicalizeRequest(request.Endpoint,
+ request.ResourcePath,
+ request.HttpMethod,
+ sortedHeaders,
+ canonicalParameters,
+ bodyHash,
+ request.PathResources,
+ request.UseDoubleEncoding);
+ metrics?.AddProperty(Metric.CanonicalRequest, canonicalRequest);
+ request.SignatureVersion = SignatureVersion.SigV4a;
+ return ComputeSignature(credentials,
+ request.DeterminedSigningRegion,
+ signedAt,
+ serviceSigningName,
+ CanonicalizeHeaderNames(sortedHeaders),
+ canonicalRequest,
+ metrics);
+ }
+
+ #region Public Signing Helpers
+
+ ///
+ /// Computes and returns an AWS4a signature for the specified canonicalized request
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static AWS4aSigningResult ComputeSignature(ImmutableCredentials credentials,
+ string regionSet,
+ DateTime signedAt,
+ string service,
+ string signedHeaders,
+ string canonicalRequest)
+ {
+ return ComputeSignature(credentials, regionSet, signedAt, service, signedHeaders, canonicalRequest, null);
+ }
+
+ ///
+ /// Computes and returns an AWS4a signature for the specified canonicalized request
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static AWS4aSigningResult ComputeSignature(ImmutableCredentials credentials,
+ string regionSet,
+ DateTime signedAt,
+ string service,
+ string signedHeaders,
+ string canonicalRequest,
+ RequestMetrics metrics)
+ {
+ var dateStamp = FormatDateTime(signedAt, AWSSDKUtils.ISO8601BasicDateFormat);
+ var scope = string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}", dateStamp, service, Terminator);
+
+ var stringToSignBuilder = new StringBuilder();
+ stringToSignBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n",
+ AWS4aAlgorithmTag,
+ FormatDateTime(signedAt, AWSSDKUtils.ISO8601BasicDateTimeFormat),
+ scope);
+
+ var canonicalRequestHashBytes = AWS4Signer.ComputeHash(canonicalRequest);
+ stringToSignBuilder.Append(AWSSDKUtils.ToHex(canonicalRequestHashBytes, true));
+
+ metrics?.AddProperty(Metric.StringToSign, stringToSignBuilder);
+
+ var stringToSign = stringToSignBuilder.ToString();
+ var signature = AWSSDKUtils.ToHex(SignBlob(credentials, stringToSign), true);
+ return new AWS4aSigningResult(credentials.AccessKey, signedAt, signedHeaders, scope, regionSet, signature, service, "", credentials);
+ }
+
+ ///
+ /// Adds one to a large unsigned integer represented by a sequence of bytes in constant time.
+ ///
+ ///
+ /// Implementation adapted from .
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ private static void AddOneConstantTime(byte[] data)
+ {
+ uint carry = 1;
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ int index = data.Length - i - 1;
+ uint current_digit = data[index] + carry;
+ carry = (current_digit >> 8) & 1;
+ data[index] = (byte)(current_digit & 0xFF);
+ }
+ }
+
+ ///
+ /// Compares two byte arrays in constant time.
+ ///
+ /// The first byte array to compare.
+ /// The second byte array to compare.
+ /// and have a different length.
+ ///
+ /// Implementation adapted from .
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ private static int CompareConstantTime(byte[] lhs, byte[] rhs)
+ {
+ if (lhs.Length != rhs.Length)
+ throw new ArgumentException("Arrays must be of equal length for constant time comparison.");
+
+ byte gt = 0, eq = 0;
+
+ for (int i = 0; i < lhs.Length; i++)
+ {
+ int lhs_digit = lhs[i];
+ int rhs_digit = rhs[i];
+
+ gt |= (byte)(((rhs_digit - lhs_digit) >> 31) & eq);
+ eq &= (byte)((((lhs_digit ^ rhs_digit) - 1) >> 31) & 1);
+ }
+
+ return gt + gt + eq - 1;
+ }
+
+ ///
+ /// Compute and return the signing key for the request.
+ ///
+ /// Access key credential.
+ /// Secret access key credential.
+ /// Computed signing key
+ public static ECDsa ComputeSigningKey(string awsAccessKey, string awsSecretAccessKey)
+ {
+ byte[] kvalue = null;
+ byte[] ksecret = null;
+
+ try
+ {
+ ksecret = Encoding.UTF8.GetBytes(Scheme + awsSecretAccessKey);
+
+ // The key value is constructed as follows:
+ // 0x00000001 || "AWS4-ECDSA-P256-SHA256" || 0x00 || AccessKeyId || CounterValue as uint8_t || 0x00000100(Length)
+ kvalue = new byte[sizeof(uint) + AWS4aAlgorithmTag.Length + 1 + Encoding.UTF8.GetByteCount(awsAccessKey) + 1 + sizeof(uint)];
+ int idx = 0;
+ kvalue[idx + 3] = 1;
+ idx += 4;
+ idx += Encoding.UTF8.GetBytes(AWS4aAlgorithmTag, 0, AWS4aAlgorithmTag.Length, kvalue, idx);
+ idx++;
+ idx += Encoding.UTF8.GetBytes(awsAccessKey, 0, awsAccessKey.Length, kvalue, idx);
+ ref byte counterValue = ref kvalue[idx++];
+ kvalue[idx + 2] = 1;
+
+ counterValue = 1;
+ while (counterValue < 0xFF)
+ {
+ byte[] kDerived = ComputeKeyedHash(SignerAlgorithm, ksecret, kvalue);
+ if (CompareConstantTime(kDerived, NMinus2) > 0)
+ {
+ // increment the counter value
+ counterValue++;
+ continue;
+ }
+
+ AddOneConstantTime(kDerived);
+ var ecdsa = ECDsa.Create(new ECParameters
+ {
+ Curve = SigningCurve,
+ D = kDerived,
+ });
+ Array.Clear(kDerived, 0, kDerived.Length);
+ return ecdsa;
+ }
+
+ throw new AmazonClientException("Failed to derive a SigV4a key for the request.");
+ }
+ finally
+ {
+ // clean up all secrets, regardless of how initially seeded (for simplicity)
+ if (ksecret != null)
+ Array.Clear(ksecret, 0, ksecret.Length);
+ if (kvalue != null)
+ Array.Clear(kvalue, 0, kvalue.Length);
+ }
+ }
+
+ private static ECDsa GetCachedSigningKey(ImmutableCredentials credentials)
+ {
+ // First, check if the credentials already have a cached signing key.
+ if (credentials.AWS4aSigningKey is { } key)
+ {
+ return key;
+ }
+
+ // Otherwise, compute one and try setting it in a thread-safe manner.
+ ECDsa newKey = ComputeSigningKey(credentials.AccessKey, credentials.SecretKey);
+ ECDsa existingKey = Interlocked.CompareExchange(ref credentials.AWS4aSigningKey, newKey, null);
+ if (existingKey != null)
+ {
+ // If another thread beat us to setting the key, use that, and dispose the one we generated, to save resources.
+ newKey.Dispose();
+ return existingKey;
+ }
+ return newKey;
+ }
+
+ ///
+ /// Returns the ECDSA signature for an arbitrary blob using the specified credentials.
+ ///
+ /// The credentials to use.
+ /// The data to sign.
+ public static byte[] SignBlob(ImmutableCredentials credentials, string data)
+ {
+ return SignBlob(credentials, Encoding.UTF8.GetBytes(data));
+ }
+
+ ///
+ /// Returns the ECDSA signature for an arbitrary blob using the specified credentials.
+ ///
+ /// The credentials to use.
+ /// The data to sign.
+ public static byte[] SignBlob(ImmutableCredentials credentials, byte[] data)
+ {
+ var key = GetCachedSigningKey(credentials);
+#if NET7_0_OR_GREATER
+ return key.SignData(data, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
+#else
+ return ConvertToRfc3279DerSequence(key.SignData(data, HashAlgorithmName.SHA256));
+#endif
+ }
+
+#if !NET7_0_OR_GREATER
+ private static byte[] ConvertToRfc3279DerSequence(byte[] signature)
+ {
+ var writer = new AsnWriter(AsnEncodingRules.DER);
+ writer.PushSequence();
+ writer.WriteIntegerUnsigned(signature.AsSpan(0, signature.Length / 2)); // R value
+ writer.WriteIntegerUnsigned(signature.AsSpan(signature.Length / 2)); // S value
+ writer.PopSequence();
+ return writer.Encode();
+ }
+#endif
+ #endregion
+ }
+
+ ///
+ /// AWS4a protocol signer for Amazon S3 presigned urls.
+ ///
+ public static class AWS4aPreSignedUrlSigner
+ {
+ // 7 days is the maximum period for presigned url expiry with AWS4a
+ public const Int64 MaxAWS4aPreSignedUrlExpiry = 7 * 24 * 60 * 60;
+
+ public static readonly IEnumerable ServicesUsingUnsignedPayload = AWS4PreSignedUrlSigner.ServicesUsingUnsignedPayload;
+
+ ///
+ /// Calculates the AWS4a signature for a presigned url.
+ ///
+ ///
+ /// The request to compute the signature for. Additional headers mandated by the AWS4a protocol
+ /// ('host' and 'x-amz-date') will be added to the request before signing. If the Expires parameter
+ /// is present, it is renamed to 'X-Amz-Expires' before signing.
+ ///
+ ///
+ /// Adding supporting data for the service call required by the signer (notably authentication
+ /// region, endpoint and service name).
+ ///
+ ///
+ /// Metrics for the request
+ ///
+ ///
+ /// The AWS credentials for the account making the service call.
+ ///
+ ///
+ /// If any problems are encountered while signing the request.
+ ///
+ ///
+ /// Parameters passed as part of the resource path should be uri-encoded prior to
+ /// entry to the signer. Parameters passed in the request.Parameters collection should
+ /// be not be encoded; encoding will be done for these parameters as part of the
+ /// construction of the canonical request.
+ ///
+ public static AWS4aSigningResult SignRequest(IRequest request,
+ IClientConfig clientConfig,
+ RequestMetrics metrics,
+ ImmutableCredentials credentials)
+ {
+ var service = "s3";
+ if (!string.IsNullOrEmpty(request.OverrideSigningServiceName))
+ service = request.OverrideSigningServiceName;
+ return SignRequest(request, clientConfig, metrics, credentials, service, null);
+ }
+
+ ///
+ /// Calculates the AWS4a signature for a presigned url.
+ ///
+ ///
+ /// The request to compute the signature for. Additional headers mandated by the AWS4a protocol
+ /// ('host' and 'x-amz-date') will be added to the request before signing. If the Expires parameter
+ /// is present, it is renamed to 'X-Amz-Expires' before signing.
+ ///
+ ///
+ /// Adding supporting data for the service call required by the signer (notably authentication
+ /// region, endpoint and service name).
+ ///
+ ///
+ /// Metrics for the request
+ ///
+ ///
+ /// The AWS credentials for the account making the service call.
+ ///
+ ///
+ /// The service to sign for
+ ///
+ ///
+ /// The region to sign to, if null then the region the client is configured for will be used.
+ ///
+ ///
+ /// If any problems are encountered while signing the request.
+ ///
+ ///
+ /// Parameters passed as part of the resource path should be uri-encoded prior to
+ /// entry to the signer. Parameters passed in the request.Parameters collection should
+ /// be not be encoded; encoding will be done for these parameters as part of the
+ /// construction of the canonical request.
+ ///
+ /// The X-Amz-Content-SHA256 is cleared out of the request.
+ /// If the request is for S3 then the UNSIGNED_PAYLOAD value is used to generate the canonical request.
+ /// If the request isn't for S3 then the empty body SHA is used to generate the canonical request.
+ ///
+ public static AWS4aSigningResult SignRequest(IRequest request,
+ IClientConfig clientConfig,
+ RequestMetrics metrics,
+ ImmutableCredentials credentials,
+ string service,
+ string overrideSigningRegion)
+ {
+ if (service == "s3" || service == "s3express")
+ {
+ // 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;
+ }
+
+ // clean up any prior signature in the headers if resigning
+ request.Headers.Remove(HeaderKeys.AuthorizationHeader);
+ if (!request.Headers.ContainsKey(HeaderKeys.HostHeader))
+ {
+ var hostHeader = request.Endpoint.Host;
+ if (!request.Endpoint.IsDefaultPort)
+ hostHeader += ":" + request.Endpoint.Port;
+ request.Headers.Add(HeaderKeys.HostHeader, hostHeader);
+ }
+
+ var signedAt = CorrectClockSkew.GetCorrectedUtcNowForEndpoint(request.Endpoint.ToString());
+ request.SignedAt = signedAt;
+ var regionSet = overrideSigningRegion ?? DetermineSigningRegion(clientConfig, clientConfig.RegionEndpointServiceName, request.AlternateEndpoint, request);
+
+ // AWS4a presigned urls got S3 are expected to contain a 'UNSIGNED-PAYLOAD' magic string
+ // during signing (other services use the empty-body sha)
+ request.Headers.Remove(HeaderKeys.XAmzContentSha256Header);
+
+ var sortedHeaders = SortAndPruneHeaders(request.Headers);
+ var canonicalizedHeaderNames = CanonicalizeHeaderNames(sortedHeaders);
+
+ var parametersToCanonicalize = GetParametersToCanonicalize(request);
+ parametersToCanonicalize.Add(new KeyValuePair(HeaderKeys.XAmzAlgorithm, AWS4aAlgorithmTag));
+ parametersToCanonicalize.Add(new KeyValuePair(HeaderKeys.XAmzRegionSetHeader, regionSet));
+ var xAmzCredentialValue = string.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}/{3}",
+ credentials.AccessKey,
+ FormatDateTime(signedAt, AWSSDKUtils.ISO8601BasicDateFormat),
+ service,
+ Terminator);
+ parametersToCanonicalize.Add(new KeyValuePair(HeaderKeys.XAmzCredential, xAmzCredentialValue));
+
+ parametersToCanonicalize.Add(new KeyValuePair(HeaderKeys.XAmzDateHeader, FormatDateTime(signedAt, AWSSDKUtils.ISO8601BasicDateTimeFormat)));
+ parametersToCanonicalize.Add(new KeyValuePair(HeaderKeys.XAmzSignedHeadersHeader, canonicalizedHeaderNames));
+
+ var canonicalQueryParams = CanonicalizeQueryParameters(parametersToCanonicalize);
+
+ var canonicalRequest = CanonicalizeRequest(request.Endpoint,
+ request.ResourcePath,
+ request.HttpMethod,
+ sortedHeaders,
+ canonicalQueryParams,
+ ServicesUsingUnsignedPayload.Contains(service) ? UnsignedPayload : EmptyBodySha256,
+ request.PathResources,
+ request.UseDoubleEncoding);
+ metrics?.AddProperty(Metric.CanonicalRequest, canonicalRequest);
+
+ return AWS4aSigner.ComputeSignature(credentials,
+ regionSet,
+ signedAt,
+ service,
+ canonicalizedHeaderNames,
+ canonicalRequest,
+ metrics);
+ }
+ }
+}
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSignerCRTWrapper.cs b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSignerCRTWrapper.cs
index 03f5c76cd930..a3f9159563e1 100644
--- a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSignerCRTWrapper.cs
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSignerCRTWrapper.cs
@@ -19,6 +19,7 @@
using Amazon.Util.Internal;
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
@@ -29,6 +30,7 @@ namespace Amazon.Runtime.Internal.Auth
///
/// Asymmetric SigV4 signer using a the AWS Common Runtime implementation of SigV4a via AWSSDK.Extensions.CrtIntegration
///
+ [Obsolete("Use AWS4aSigner instead."), EditorBrowsable(EditorBrowsableState.Never)]
public class AWS4aSignerCRTWrapper : AbstractAWSSigner
{
internal const string CRT_WRAPPER_ASSEMBLY_NAME = "AWSSDK.Extensions.CrtIntegration";
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigningResult.cs b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigningResult.cs
index c5130dc4acdf..fd4d331e9657 100644
--- a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigningResult.cs
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigningResult.cs
@@ -13,7 +13,9 @@
* permissions and limitations under the License.
*/
+using Amazon.Util;
using System;
+using System.Globalization;
using System.Text;
namespace Amazon.Runtime.Internal.Auth
@@ -38,7 +40,7 @@ public class AWS4aSigningResult : AWSSigningResultBase
/// The access key that was included in the signature
/// Date/time (UTC) that the signature was computed
/// The collection of headers names that were included in the signature
- /// Formatted 'scope' value for signing (YYYYMMDD/region/service/aws4_request)
+ /// Formatted 'scope' value for signing (YYYYMMDD/service/aws4_request)
/// The set of AWS regions this signature is valid for
/// Computed signature
/// Service the request was signed for
@@ -87,6 +89,25 @@ public override string ForAuthorizationHeader
}
}
+ ///
+ /// Returns the signature in a form usable as a set of query string parameters.
+ ///
+ public string ForQueryParameters
+ {
+ get
+ {
+ var authParams = new StringBuilder()
+ .AppendFormat("{0}={1}", AWSSDKUtils.UrlEncode(HeaderKeys.XAmzAlgorithm, false), AWSSDKUtils.UrlEncode(AWS4Signer.AWS4aAlgorithmTag, false))
+ .AppendFormat("&{0}={1}", AWSSDKUtils.UrlEncode(HeaderKeys.XAmzRegionSetHeader, false), AWSSDKUtils.UrlEncode(RegionSet, false))
+ .AppendFormat("&{0}={1}", AWSSDKUtils.UrlEncode(HeaderKeys.XAmzCredential, false), AWSSDKUtils.UrlEncode(string.Format(CultureInfo.InvariantCulture, "{0}/{1}", AccessKeyId, Scope), false))
+ .AppendFormat("&{0}={1}", AWSSDKUtils.UrlEncode(HeaderKeys.XAmzDateHeader, false), AWSSDKUtils.UrlEncode(ISO8601DateTime, false))
+ .AppendFormat("&{0}={1}", AWSSDKUtils.UrlEncode(HeaderKeys.XAmzSignedHeadersHeader, false), AWSSDKUtils.UrlEncode(SignedHeaders, false))
+ .AppendFormat("&{0}={1}", AWSSDKUtils.UrlEncode(HeaderKeys.XAmzSignature, false), AWSSDKUtils.UrlEncode(Signature, false));
+
+ return authParams.ToString();
+ }
+ }
+
///
/// Returns the set of regions this signature is valid for
///
@@ -99,6 +120,7 @@ public string RegionSet
///
/// Returns the full presigned Uri
///
+ [Obsolete("This property is always empty in objects returned by AWS4aSigner. Use the ForQueryParameters property instead, to get the query parameters for a presigned URL.")]
public string PresignedUri
{
get { return _presignedUri; }
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWSEndpointAuthSchemeSigner.cs b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWSEndpointAuthSchemeSigner.cs
index e9cd7d5d5d5d..93ff7a68f72d 100644
--- a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWSEndpointAuthSchemeSigner.cs
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWSEndpointAuthSchemeSigner.cs
@@ -34,7 +34,7 @@ public override void Sign(IRequest request, IClientConfig clientConfig, RequestM
{
var useSigV4 = request.SignatureVersion == SignatureVersion.SigV4;
var signer = SelectSigner(this, useSigV4, request, clientConfig);
- var aws4aSigner = signer as AWS4aSignerCRTWrapper;
+ var aws4aSigner = signer as AWS4aSigner;
var aws4Signer = signer as AWS4Signer;
var useV4a = aws4aSigner != null;
var useV4 = aws4Signer != null;
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AbstractAWSSigner.cs b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AbstractAWSSigner.cs
index 7afd8fe2286e..7bfae5ce9fca 100644
--- a/sdk/src/Core/Amazon.Runtime/Internal/Auth/AbstractAWSSigner.cs
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Auth/AbstractAWSSigner.cs
@@ -44,21 +44,21 @@ private AWS4Signer AWS4SignerInstance
}
}
- private AWS4aSignerCRTWrapper _aws4aSignerCRTWrapper;
- private AWS4aSignerCRTWrapper AWS4aSignerCRTWrapperInstance
+ private AWS4aSigner _aws4aSigner;
+ private AWS4aSigner AWS4aSignerInstance
{
get
{
- if (_aws4aSignerCRTWrapper == null)
+ if (_aws4aSigner == null)
{
lock (_lock)
{
- if (_aws4aSignerCRTWrapper == null)
- _aws4aSignerCRTWrapper = new AWS4aSignerCRTWrapper();
+ if (_aws4aSigner == null)
+ _aws4aSigner = new AWS4aSigner();
}
}
- return _aws4aSignerCRTWrapper;
+ return _aws4aSigner;
}
}
@@ -171,7 +171,7 @@ protected AbstractAWSSigner SelectSigner(AbstractAWSSigner defaultSigner,bool us
IRequest request, IClientConfig config)
{
if (request.SignatureVersion == SignatureVersion.SigV4a)
- return AWS4aSignerCRTWrapperInstance;
+ return AWS4aSignerInstance;
else if (UseV4Signing(useSigV4Setting, request, config))
return AWS4SignerInstance;
else
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Auth/S3Signer.cs b/sdk/src/Core/Amazon.Runtime/Internal/Auth/S3Signer.cs
index 798fb0393ebc..3531f4b286ae 100644
--- a/sdk/src/Core/Amazon.Runtime/Internal/Auth/S3Signer.cs
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Auth/S3Signer.cs
@@ -55,7 +55,7 @@ public override void Sign(IRequest request, IClientConfig clientConfig, RequestM
{
var signer = SelectSigner(this, true, request, clientConfig);
var aws4Signer = signer as AWS4Signer;
- var aws4aSigner = signer as AWS4aSignerCRTWrapper;
+ var aws4aSigner = signer as AWS4aSigner;
var useV4 = aws4Signer != null;
var useV4a = aws4aSigner != null;
@@ -73,6 +73,7 @@ public override void Sign(IRequest request, IClientConfig clientConfig, RequestM
if (useV4a)
{
var signingResult = aws4aSigner.SignRequest(request, clientConfig, metrics, immutableCredentials);
+ request.Headers[HeaderKeys.AuthorizationHeader] = signingResult.ForAuthorizationHeader;
if (request.UseChunkEncoding)
{
request.AWS4aSignerResult = signingResult;
diff --git a/sdk/src/Core/Amazon.Runtime/Internal/Util/ChunkedUploadWrapperStream.cs b/sdk/src/Core/Amazon.Runtime/Internal/Util/ChunkedUploadWrapperStream.cs
index 01ab2da21cae..e4d5e514c716 100644
--- a/sdk/src/Core/Amazon.Runtime/Internal/Util/ChunkedUploadWrapperStream.cs
+++ b/sdk/src/Core/Amazon.Runtime/Internal/Util/ChunkedUploadWrapperStream.cs
@@ -93,10 +93,6 @@ internal ChunkedUploadWrapperStream(Stream stream, int wrappedStreamBufferSize,
{
throw new AmazonClientException($"{nameof(ChunkedUploadWrapperStream)} was initialized without a SigV4 or SigV4a signing result.");
}
- else if (headerSigningResult is AWS4aSigningResult)
- {
- Sigv4aSigner = new AWS4aSignerCRTWrapper();
- }
HeaderSigningResult = headerSigningResult;
PreviousChunkSignature = headerSigningResult?.Signature;
@@ -286,11 +282,6 @@ private async Task FillInputBufferAsync(CancellationToken cancellationToken
///
private AWSSigningResultBase HeaderSigningResult { get; set; }
- ///
- /// SigV4a signer
- ///
- private AWS4aSignerCRTWrapper Sigv4aSigner { get; set; }
-
///
/// Computed signature of the chunk prior to the one in-flight in hex,
/// for either SigV4 or SigV4a
@@ -321,23 +312,16 @@ private void ConstructOutputBufferChunk(int dataLen)
// variable-length size of the embedded chunk data in hex
chunkHeader.Append(dataLen.ToString("X", CultureInfo.InvariantCulture));
+ var chunkStringToSign = BuildChunkedStringToSign(CHUNK_STRING_TO_SIGN_PREFIX, HeaderSigningResult.ISO8601DateTime,
+ HeaderSigningResult.Scope, PreviousChunkSignature, dataLen, _inputBuffer);
+
string chunkSignature = "";
if (HeaderSigningResult is AWS4aSigningResult v4aHeaderSigningResult)
{
- if (isFinalDataChunk) // _inputBuffer still contains previous chunk, but this is the final 0 content chunk so sign null
- {
- chunkSignature = Sigv4aSigner.SignChunk(null, PreviousChunkSignature, v4aHeaderSigningResult);
- }
- else
- {
- chunkSignature = Sigv4aSigner.SignChunk(new MemoryStream(_inputBuffer), PreviousChunkSignature, v4aHeaderSigningResult);
- }
+ chunkSignature = AWSSDKUtils.ToHex(AWS4aSigner.SignBlob(v4aHeaderSigningResult.Credentials, chunkStringToSign), true);
}
else if (HeaderSigningResult is AWS4SigningResult v4HeaderSingingResult) // SigV4
{
- var chunkStringToSign = BuildChunkedStringToSign(CHUNK_STRING_TO_SIGN_PREFIX, v4HeaderSingingResult.ISO8601DateTime,
- v4HeaderSingingResult.Scope, PreviousChunkSignature, dataLen, _inputBuffer);
-
chunkSignature = AWSSDKUtils.ToHex(AWS4Signer.SignBlob(v4HeaderSingingResult.GetSigningKey(), chunkStringToSign), true);
}
@@ -408,24 +392,25 @@ private string ConstructSignedTrailersChunk()
_trailingHeaders[ChecksumUtils.GetChecksumHeaderKey(_trailingChecksum)] = Convert.ToBase64String(_hashAlgorithm.Hash);
}
- string chunkSignature;
- if (HeaderSigningResult is AWS4SigningResult)
- {
- var sortedTrailingHeaders = AWS4Signer.SortAndPruneHeaders(_trailingHeaders);
- var canonicalizedTrailingHeaders = AWS4Signer.CanonicalizeHeaders(sortedTrailingHeaders);
+ var sortedTrailingHeaders = AWS4Signer.SortAndPruneHeaders(_trailingHeaders);
+ var canonicalizedTrailingHeaders = AWS4Signer.CanonicalizeHeaders(sortedTrailingHeaders);
- var chunkStringToSign =
- TRAILING_HEADER_STRING_TO_SIGN_PREFIX + "\n" +
- HeaderSigningResult.ISO8601DateTime + "\n" +
- HeaderSigningResult.Scope + "\n" +
- PreviousChunkSignature + "\n" +
- AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(canonicalizedTrailingHeaders), true);
+ var chunkStringToSign =
+ TRAILING_HEADER_STRING_TO_SIGN_PREFIX + "\n" +
+ HeaderSigningResult.ISO8601DateTime + "\n" +
+ HeaderSigningResult.Scope + "\n" +
+ PreviousChunkSignature + "\n" +
+ AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(canonicalizedTrailingHeaders), true);
- chunkSignature = AWSSDKUtils.ToHex(AWS4Signer.SignBlob(((AWS4SigningResult)HeaderSigningResult).GetSigningKey(), chunkStringToSign), true);
+ string chunkSignature;
+ if (HeaderSigningResult is AWS4SigningResult aws4Result)
+ {
+ chunkSignature = AWSSDKUtils.ToHex(AWS4Signer.SignBlob(aws4Result.GetSigningKey(), chunkStringToSign), true);
}
else // SigV4a
{
- chunkSignature = Sigv4aSigner.SignTrailingHeaderChunk(_trailingHeaders, PreviousChunkSignature, (AWS4aSigningResult)HeaderSigningResult).PadRight(V4A_SIGNATURE_LENGTH, '*');
+ var aws4aResult = (AWS4aSigningResult)HeaderSigningResult;
+ chunkSignature = AWSSDKUtils.ToHex(AWS4aSigner.SignBlob(aws4aResult.Credentials, chunkStringToSign), true).PadRight(V4A_SIGNATURE_LENGTH, '*');
}
var chunk = new StringBuilder();
diff --git a/sdk/src/Core/Amazon.Runtime/Pipeline/Handlers/BaseEndpointResolver.cs b/sdk/src/Core/Amazon.Runtime/Pipeline/Handlers/BaseEndpointResolver.cs
index 5991a79634aa..87b66c5f2ee5 100644
--- a/sdk/src/Core/Amazon.Runtime/Pipeline/Handlers/BaseEndpointResolver.cs
+++ b/sdk/src/Core/Amazon.Runtime/Pipeline/Handlers/BaseEndpointResolver.cs
@@ -162,16 +162,6 @@ private static void SetAuthenticationAndHeaders(IRequest request, Endpoint endpo
}
case "sigv4a":
{
- // If there are multiple authentication schemes but the CRT dependency is not available,
- // we will proceed to check the next value in authSchemes.
- if (hasMultipleSchemes)
- {
- if (!IsCrtDependencyAvailable())
- {
- continue;
- }
- }
-
request.SignatureVersion = SignatureVersion.SigV4a;
var signingRegions = ((List