Skip to content

Commit

Permalink
AwsSignatureHandler now overrides the synchronous Send() method in .N…
Browse files Browse the repository at this point in the history
…ET 5. See FantasticFiasco#478.
  • Loading branch information
Timo van Zijll Langhout committed Aug 30, 2021
1 parent 25c3fb4 commit 84d8a2f
Show file tree
Hide file tree
Showing 9 changed files with 1,180 additions and 8 deletions.
33 changes: 33 additions & 0 deletions src/AwsSignatureHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,38 @@ await Signer.SignAsync(

return await base.SendAsync(request, cancellationToken);
}

#if NET5_0_OR_GREATER

/// <inheritdoc />
protected override HttpResponseMessage Send(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Given the idempotent nature of message handlers, lets remove request headers that
// might have been added by an prior attempt to send the request
request.Headers.Remove(HeaderKeys.AuthorizationHeader);
request.Headers.Remove(HeaderKeys.XAmzContentSha256Header);
request.Headers.Remove(HeaderKeys.XAmzDateHeader);
request.Headers.Remove(HeaderKeys.XAmzSecurityTokenHeader);

var immutableCredentials = settings.Credentials.GetCredentials();

var signingTask = Signer.SignAsync(
request,
null,
EmptyRequestHeaders,
DateTime.UtcNow,
settings.RegionName,
settings.ServiceName,
immutableCredentials,
async: false);

System.Diagnostics.Debug.Assert(signingTask.IsCompletedSuccessfully, "The operation should have completed synchronously.");

return base.Send(request, cancellationToken);
}

#endif
}
}
2 changes: 1 addition & 1 deletion src/AwsSignatureVersion4.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>The buttoned-up and boring, but deeply analyzed, implementation of Signature Version 4 (SigV4) in .NET.</Description>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net45;netstandard2.0;net5.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
Expand Down
11 changes: 8 additions & 3 deletions src/Private/CanonicalRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static (string, string) Build(
// URI-encoded twice (
// <see href="https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html">
// except for Amazon S3 which only gets URI-encoded once</see>).
var canonicalResourcePath = GetCanonicalResourcePath(serviceName, request.RequestUri);
var canonicalResourcePath = GetCanonicalResourcePath(serviceName, request.RequestUri!);

builder.Append($"{canonicalResourcePath}\n");

Expand All @@ -81,7 +81,7 @@ public static (string, string) Build(
// string for parameters that have no value.
// e. Append the ampersand character (&) after each parameter value, except for the
// last value in the list.
var parameters = SortQueryParameters(request.RequestUri.Query)
var parameters = SortQueryParameters(request.RequestUri!.Query)
.SelectMany(
parameter => parameter.Value.Select(
parameterValue => $"{AWSSDKUtils.UrlEncode(parameter.Key, false)}={AWSSDKUtils.UrlEncode(parameterValue, false)}"));
Expand Down Expand Up @@ -166,7 +166,12 @@ public static SortedList<string, List<string>> SortQueryParameters(string query)
sortedQueryParameters.Add(parameterName, parameterValues);
}

parameterValues.AddRange(queryParameters.GetValues(parameterName));
parameterValues.AddRange(queryParameters.GetValues(parameterName) ??
#if NET45
new string[0]);
#else
Array.Empty<string>());
#endif
}

// Sort the query parameter values
Expand Down
28 changes: 26 additions & 2 deletions src/Private/ContentHash.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Amazon.Runtime.Internal.Auth;
using Amazon.Util;
Expand All @@ -11,7 +12,7 @@ namespace AwsSignatureVersion4.Private
/// </summary>
public static class ContentHash
{
public static async Task<string> CalculateAsync(HttpContent content)
public static async Task<string> CalculateAsync(HttpContent? content)
{
// Use a hash (digest) function like SHA256 to create a hashed value from the payload
// in the body of the HTTP or HTTPS request.
Expand All @@ -28,5 +29,28 @@ public static async Task<string> CalculateAsync(HttpContent content)
var hash = AWS4Signer.ComputeHash(data);
return AWSSDKUtils.ToHex(hash, true);
}

#if NET5_0_OR_GREATER

public static string Calculate(HttpContent? content)
{
// Use a hash (digest) function like SHA256 to create a hashed value from the payload
// in the body of the HTTP or HTTPS request.
//
// If the payload is empty, use an empty string as the input to the hash function.
if (content == null)
{
// Per performance reasons, use the pre-computed hash of an empty string from the
// AWS SDK
return AWS4Signer.EmptyBodySha256;
}

// AWS4Signer.ComputeHash() simply calls CryptoUtilFactory.CryptoInstance.ComputeSHA256Hash(), but omits a Stream-based overload
var stream = content.ReadAsStream();
var hash = CryptoUtilFactory.CryptoInstance.ComputeSHA256Hash(stream);
return AWSSDKUtils.ToHex(hash, true);
}

#endif
}
}
14 changes: 12 additions & 2 deletions src/Private/Signer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ public static async Task<Result> SignAsync(
DateTime now,
string regionName,
string serviceName,
ImmutableCredentials credentials)
ImmutableCredentials credentials
#if NET5_0_OR_GREATER
, bool async = true
#endif
)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (request.Headers.Contains(HeaderKeys.XAmzDateHeader)) throw new ArgumentException(ErrorMessages.XAmzDateHeaderExists, nameof(request));
Expand All @@ -31,14 +35,20 @@ public static async Task<Result> SignAsync(

UpdateRequestUri(request, baseAddress);

#if NET5_0_OR_GREATER
var contentHash = async
? await ContentHash.CalculateAsync(request.Content)
: ContentHash.Calculate(request.Content);
#else
var contentHash = await ContentHash.CalculateAsync(request.Content);
#endif

// Add required headers
request.AddHeader(HeaderKeys.XAmzDateHeader, now.ToIso8601BasicDateTime());

// Add conditional headers
request.AddHeaderIf(credentials.UseToken, HeaderKeys.XAmzSecurityTokenHeader, credentials.Token);
request.AddHeaderIf(!request.Headers.Contains(HeaderKeys.HostHeader), HeaderKeys.HostHeader, request.RequestUri.Host);
request.AddHeaderIf(!request.Headers.Contains(HeaderKeys.HostHeader), HeaderKeys.HostHeader, request.RequestUri!.Host);
request.AddHeaderIf(serviceName == ServiceName.S3, HeaderKeys.XAmzContentSha256Header, contentHash);

// Build the canonical request
Expand Down
Loading

0 comments on commit 84d8a2f

Please sign in to comment.