Skip to content

Add managed SigV4a signer. - #3923

Open
teo-tsirpanis wants to merge 26 commits into
aws:feature/4.1from
teo-tsirpanis:sigv4a
Open

Add managed SigV4a signer.#3923
teo-tsirpanis wants to merge 26 commits into
aws:feature/4.1from
teo-tsirpanis:sigv4a

Conversation

@teo-tsirpanis

Copy link
Copy Markdown
Contributor

Description

This PR adds a managed implementation of the AWS SigV4a algorithm, contained in the newly added Amazon.Runtime.Internal.Auth.AWS4aSigner class. All uses of the old signer that forward to the CRT were replaced. The CRT signer and related APIs were deprecated, but were left otherwise untouched.

Motivation and Context

Fixes #3881.

Testing

  • Added a small unit test for the signing key derivation process, ported from the existing tests of the CRT signer in extensions.
    • There were several more tests that were not moved over at the moment, because of a perceived low value, considering that: they would necessitate writing a managed SigV4a verifier as well, equivalent tests for SigV4 do not exist, and that the code paths that use the SigV4 and SigV4a signers have become more similar, which reduces the room for error.
  • Tested by performing a request and generating a pre-signed URL, on an S3 multi-region access point.
  • There are already several existing integration tests (which I did not run), that use SigV4a signing.

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project
  • My change requires a change to the documentation
  • I have updated the documentation accordingly
  • I have read the README document
  • I have added tests to cover my changes
  • All new and existing tests passed

License

  • I confirm that this pull request can be released under the Apache 2 license

@dscpinheiro
dscpinheiro changed the base branch from main to development July 19, 2025 21:11
Comment thread sdk/src/Core/Amazon.Runtime/Credentials/ImmutableCredentials.cs Outdated
Comment on lines +389 to +406
#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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that we format the signature in ASN.1 is not documented anywhere, and stumped me for some days. I sent feedback to update the documentation.

/// <summary>
/// Returns the full presigned Uri
/// </summary>
[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.")]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a difference in how the SigV4 and SigV4a signing result types return presigned URLs. I think that obsoleting this SigV4a-only property and unifying with SigV4 is the better choice; for reference, there are zero usages of this property outside of the S3 library, which will need the Core bump from the replacement of AWS4aSignerCRTWrapper either way.

/// <summary>
/// AWS4a protocol signer for Amazon S3 presigned urls.
/// </summary>
public static class AWS4aPreSignedUrlSigner

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the idea of making this a static class, unlike the SigV4 counterpart, which mostly has static members and an overriden method that always throws.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might need to be updated to account for the fact that the S3 library needs a newer Core version. How to write this?

The logic is moved to a separate function, and we guard from temporary resource leaks if multiple threads try to populate the cache.
@dscpinheiro

Copy link
Copy Markdown
Contributor

I know it's been a while but we're finally starting to look at this PR (I've reached out to the AppSec team at AWS asking for them to review it as well). I also tried to run this branch in our build systems and some existing tests (such as the ones in https://github.com/aws/aws-sdk-net/blob/main/sdk/test/Services/EventBridge/UnitTests/Custom/EventBridgeMRAPTests.cs) failed:

Message: 
  Test method AWSSDK.UnitTests.EventBridgeMRAPTests.UseOverrideEndpointWhenEndpointIdIsSet threw exception: 
  System.Security.Cryptography.CryptographicException: The specified key parameters are not valid. Q.X and Q.Y are required fields. Q.X, Q.Y must be the same length. If D is specified it must be the same length as Q.X and Q.Y for named curves or the same length as Order for explicit curves.

Stack Trace: 
  ECParameters.Validate()
  ECCng.ParametersToBlob(ECParameters& parameters, Func`3 namedCurveResolver, Func`2 explicitCurveResolver, CngKeyBlobFormat& format, String& curveName)
  ECDsaCng.ImportParameters(ECParameters parameters)
  ECDsa.Create(ECParameters parameters)
  AWS4aSigner.ComputeSigningKey(String awsAccessKey, String awsSecretAccessKey)
  // etc...

Our build system tests on Windows, so I believe that may be related.

@dscpinheiro dscpinheiro self-assigned this Mar 10, 2026
@teo-tsirpanis

teo-tsirpanis commented Mar 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the update @dscpinheiro. The unit test I added succeeded on .NET 8, but I reproduced the failure on .NET Framework, and upon closer look it does not support importing EC parameter sets with only $d$ set. Modern .NET added support relatively recently (dotnet/runtime#33874), which means that the PR is blocked until support gets backported to .NET Framework, if it meets the bar. c.c. @vcsjones

This also means that SigV4a signing will fail on .NET Core 3.1 as well, which is long out of support but is still targeted by tests.

Update: opened .NET Framework backport request in https://developercommunity.visualstudio.com/t/Cannot-import-elliptic-curve-parameter-s/11058177

@dscpinheiro

Copy link
Copy Markdown
Contributor

Thanks for opening that feature request, will keep an eye on it (we'd definitely prefer not having to write custom cryptography code in the SDK if we can avoid it).

On a side note I did another pass of the PR and only had two minor comments:

  • In sdk/src/Core/Amazon.Runtime/Internal/Util/ChunkedUploadWrapperStream.cs, you're using CHUNK_STRING_TO_SIGN_PREFIX and TRAILING_HEADER_STRING_TO_SIGN_PREFIX for both SigV4 and SigV4A. We should use the correct values for SigV4A (AWS4-ECDSA-P256-SHA256-PAYLOAD and AWS4-ECDSA-P256-SHA256-TRAILER) instead.
  • We also have quite a few integration tests that use SigV4A (the S3 solution is the best place to look). They also run on Windows so we'll need to confirm they pass once we decide on the path forward.

@teo-tsirpanis

Copy link
Copy Markdown
Contributor Author

I fixed ChunkedUploadWrapperStream. With that, the MultiRegionAccessPointsTests succeed locally, after pointing them to my test MRAP and porting them to .NET 8 (some low-hanging fruit from the effort has been pushed).

@vcsjones

Copy link
Copy Markdown

Personally I do not know if this is likely to be backported to any version of .NET Framework. The backport bar to .NET Framework is very high.

One thing that might work, specifically in the case of .NET Framework, is to create a PKCS#8 with an ECPrivateKey in it. You can then import that with CngKey.Import and a key format of Pkcs8PrivateBlob.

In an ECPrivateKey, the private portion, D, is required, but the public Q parameters publicKey is optional.

Then pass that key to a new instance of ECDsaCng (a subclass of ECDsa).

So:

ECDsa key;

#if NETFRAMEWORK
  byte[] assemblePkcsPrivateKey;
  CngKey cngKey = CngKey.Import(assemblePkcsPrivateKey, CngKeyBlobFormat. Pkcs8PrivateBlob);
  key = new ECDsaCng(cngKey);
#else
  key = ECDsa.Create();
  // use key.ImportParameters with D populated
#endif

The Windows CNG key importer should handle the PKCS#8 of the ECPrivateKey omitting the publicKey.

It’s not exactly trivial to implement, but it’s “just” assembling some ASN.1. No crypto point multiplication required.

@teo-tsirpanis

Copy link
Copy Markdown
Contributor Author

Thanks for chiming in @vcsjones. Upon further investigation, we could simply import a BCRYPT_ECCPRIVATE_BLOB with the public parameters set to all zeroes, which is what .NET does under the hood. I will give it a try soon.

@teo-tsirpanis

Copy link
Copy Markdown
Contributor Author

I updated the .NET Framework implementation to construct and import a BCRYPT_ECCPRIVATE_BLOB. With this, MultiRegionAccessPointsTests succeed on .NET Framework too.

@dscpinheiro

Copy link
Copy Markdown
Contributor

I ran the latest changes through our build system and there were 3 test failures (2 of them are the same but in different methods):

  • DeriveSigningKey failed in .NET Core 3.1 (I know... But still supported in the SDK) with System.Security.Cryptography.CryptographicException: The specified key parameters are not valid. Q.X and Q.Y are required fields. Q.X, Q.Y must be the same length. If D is specified it must be the same length as Q.X and Q.Y for named curves or the same length as Order for explicit curves.
  • Both TestV4aSignedMultipartUpload and PutObjectChunked (in the S3 solution) failed with System.ArgumentException: The first 9 bits of the integer value all have the same value. Ensure the input is in big-endian byte order and that all redundant leading bytes have been removed.
   at System.Formats.Asn1.AsnWriter.WriteIntegerUnsignedCore(Asn1Tag tag, ReadOnlySpan`1 value)
   at System.Formats.Asn1.AsnWriter.WriteIntegerUnsigned(ReadOnlySpan`1 value, Nullable`1 tag)
   at Amazon.Runtime.Internal.Auth.AWS4aSigner.ConvertToRfc3279DerSequence(Byte[] signature) in /_/sdk/src/Core/Amazon.Runtime/Internal/Auth/AWS4aSigner.cs:line 450
   /// etc.

@teo-tsirpanis

Copy link
Copy Markdown
Contributor Author

I fixed the signature format conversion. I copied the .NET sources that do it and added links to them, hoping it will make security review easier.

With regards to .NET Core 3.1, there's not much we can do. I thought of importing the CNG key like on .NET Framework, but I'm not sure how much helpful it would be, since it would still fail on non-Windows platforms. Are there plans to drop .NET Core 3.1 support? The AWS SDK would still be installable and (mostly) usable on .NET Core 3.1 after stopping explicit targeting and testing, through the .NET Standard 2.0 binaries.

@dscpinheiro

Copy link
Copy Markdown
Contributor

Are there plans to drop .NET Core 3.1 support?

Yes, this is something we plan to do in the next minor version of the SDK (i.e. 4.1), but that likely won't happen until later this year (after V3 reaches end of support - https://aws.amazon.com/blogs/developer/aws-sdk-for-net-v3-maintenance-mode-announcement/).

@teo-tsirpanis

Copy link
Copy Markdown
Contributor Author

Thanks; I guess we can't merge it until then. No problem, I can wait.

@dscpinheiro dscpinheiro added the pr/blocked This PR cannot be merged or reviewed, because it is blocked for some reason. label Jun 15, 2026
@teo-tsirpanis
teo-tsirpanis requested review from a team as code owners June 16, 2026 10:03
@teo-tsirpanis
teo-tsirpanis requested review from AlexDaines and dscpinheiro and removed request for a team June 16, 2026 10:03
@teo-tsirpanis
teo-tsirpanis marked this pull request as draft June 16, 2026 10:03
@dscpinheiro

Copy link
Copy Markdown
Contributor

Update: We just published https://aws.amazon.com/blogs/developer/annual-net-target-updates-for-the-aws-sdk-for-net/, which includes our plan to drop support for .NET Core 3.1 (the reason the PR was marked as "do not merge").

We can't commit to a specific date (as the post says We will attempt to align as closely as possible with the .NET November release cycle but may fall back to December depending on demands for other priorities, particularly for AWS re:Invent), but the managed SigV4A signer is something we plan to include in our V4.1 release.

@dscpinheiro
dscpinheiro changed the base branch from development to feature/4.1 August 21, 2026 23:17
@dscpinheiro

Copy link
Copy Markdown
Contributor

Sorry for more conflicts, but I just changed the destination branch here to feature/4.1 (that's where we'll keep the changes for the next minor version).

Sometime next week we'll also create a tracking issue for 4.1 (similar to what we had for the V4 GA tracker).

@teo-tsirpanis

Copy link
Copy Markdown
Contributor Author

Conflicts resolved. I also did some upkeeping.

@dscpinheiro dscpinheiro removed the pr/blocked This PR cannot be merged or reviewed, because it is blocked for some reason. label Aug 24, 2026
Comment on lines +455 to +461
var prefix = HeaderSigningResult is AWS4aSigningResult ? V4A_TRAILING_HEADER_STRING_TO_SIGN_PREFIX : V4_TRAILING_HEADER_STRING_TO_SIGN_PREFIX;
var chunkStringToSign =
prefix + "\n" +
HeaderSigningResult.ISO8601DateTime + "\n" +
HeaderSigningResult.Scope + "\n" +
PreviousChunkSignature + "\n" +
AWSSDKUtils.ToHex(AWS4Signer.ComputeHash(canonicalizedTrailingHeaders), true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While not new code, would it be better to do this with a StringBuilder?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better, but I'm not going to do it in this PR to minimize scope creep. Same with your proposed changes to other parts, which match the equivalent code in SigV4.

Comment on lines +100 to +105
.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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be more performant to avoid AppendFormat and just append the individual elements directly?

.Append(AWSSDKUtils.UrlEncode(HeaderKeys.XAmzAlgorithm, false))
.Append('=')
.Append(AWSSDKUtils.UrlEncode(AWS4Signer.AWS4aAlgorithmTag, false))
.Append('&')
.Append(AWSSDKUtils.UrlEncode(HeaderKeys.XAmzRegionSetHeader, false))
.Append('=')
// etc

Comment on lines +299 to +305
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar here.

@teo-tsirpanis
teo-tsirpanis marked this pull request as ready for review August 24, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add managed SigV4a signer.

4 participants