Skip to content

Commit

Permalink
Fix issue #66 (#67)
Browse files Browse the repository at this point in the history
* Create Content-Digest headers using RFC 9530
* Verify Content-Digest headers from either RFC 3230 and RFC 9530
  • Loading branch information
rogerk-unifysquare authored Nov 12, 2024
1 parent d313d8a commit 520dce1
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ public sealed class ContentDigestVerificationMiddleware : IMiddleware
/// <summary>
/// The regular expression to parse the 'content-digest' header.
/// See also <seealso href="https://www.rfc-editor.org/rfc/rfc3230"/>
/// and <seealso href="https://www.rfc-editor.org/rfc/rfc9530"/>.
/// </summary>
private static readonly Regex HeaderValueParser = new Regex(@"(?<= ^|,\s*) ([\w_\-]+) = ([0-9a-zA-Z+/=]+) (?= $|,\s*)",
private static readonly Regex HeaderValueParser = new Regex(@"(?<= ^|,\s*) ([\w_\-]+) = (?<colon>:?) ([0-9a-zA-Z+/=]+) \k<colon> (?= $|,\s*)",
RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace);

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/NSign.Client/Client/AddContentDigestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private static Task<string> GetDigestValueAsync(HttpContent content, Hash hashAl
byte[] hashOutput = hash.ComputeHash(streamToHash);

return writeBody
.ContinueWith(_ => $"{algName}={Convert.ToBase64String(hashOutput, Base64FormattingOptions.None)}");
.ContinueWith(_ => $"{algName}=:{Convert.ToBase64String(hashOutput, Base64FormattingOptions.None)}:");
}

/// <summary>
Expand All @@ -96,11 +96,11 @@ private static HashAlgorithm GetConfiguredHash(Hash alg, out string algName)
switch (alg)
{
case Hash.Sha256:
algName = Constants.HashAlgorithms.Sha256;
algName = "sha-256";
return SHA256.Create();

case Hash.Sha512:
algName = Constants.HashAlgorithms.Sha512;
algName = "sha-512";
return SHA512.Create();

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,30 +147,53 @@ public async Task OnlyUnknownAlgorithmsCausesVerificationFailuresResponseStatus(
Assert.Equal(0, Interlocked.Read(ref numCallsToNext));
}

//[InlineData(
// new string[]
// {
// "Sha-256=uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
// "sha-512=uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
// },
// "hello world",
// VerificationBehavior.RequireOnlySingleMatch)]
//public async Task SuccessfulVerificationCauseNextMiddlewareToBeCalled(
// string[] headers,
// string body,
// VerificationBehavior behavior)
//{
// options.Behavior = behavior;

// using Stream bodyStream = MakeStream(body);
// httpContext.Request.Body = bodyStream;
// httpContext.Request.Headers.Add("Content-Digest", headers);

// await middleware.InvokeAsync(httpContext, CountingMiddleware);

// Assert.Equal(200, httpContext.Response.StatusCode);
// Assert.Equal(1, Interlocked.Read(ref numCallsToNext));
//}
[Theory]
[InlineData(
@"{""hello"": ""world""}",
// Formatted per RFC 3230
"sha-512=WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew=="
)]
[InlineData(
@"{""hello"": ""world""}",
// Formatted per RFC 9530
"sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:"
)]
[InlineData(
@"{""busy"": true, ""message"": ""Your call is very important to us""}",
// Formatted per RFC 3230
"sha-512=0Y6iCBzGg5rZtoXS95Ijz03mslf6KAMCloESHObfwnHJDbkkWWQz6PhhU9kxsTbARtY2PTBOzq24uJFpHsMuAg=="
)]
[InlineData(
@"{""busy"": true, ""message"": ""Your call is very important to us""}",
// Formatted per RFC 9530
"sha-512=:0Y6iCBzGg5rZtoXS95Ijz03mslf6KAMCloESHObfwnHJDbkkWWQz6PhhU9kxsTbARtY2PTBOzq24uJFpHsMuAg==:"
)]
[InlineData(
@"hello world!",
// Formatted per RFC 3230
"sha-256=dQnlvaDHYtK6x/kNdYtbImP6Acy8VCq1498WO+CObKk=," +
"sha-512=25sc0yYt7jd1agm5BklzWJhHyqjlPTGp0ULqJwGxsoq9l4OLuaJwaLowXcjQSkWh/PB53lTWB2ZplrPMVPa2fA=="
)]
[InlineData(
@"hello world!",
// Formatted per RFC 9530
"sha-256=:dQnlvaDHYtK6x/kNdYtbImP6Acy8VCq1498WO+CObKk=:," +
"sha-512=:25sc0yYt7jd1agm5BklzWJhHyqjlPTGp0ULqJwGxsoq9l4OLuaJwaLowXcjQSkWh/PB53lTWB2ZplrPMVPa2fA==:"
)]
public async Task TestRfc3230AndRfc9530(string body, string headers)
{
options.VerificationFailuresResponseStatus = 999;
options.Behavior = VerificationBehavior.None;

using Stream bodyStream = MakeStream(body);
httpContext.Request.Body = bodyStream;
httpContext.Request.Headers["Content-Digest"] = headers;

await middleware.InvokeAsync(httpContext, CountingMiddleware);

Assert.Equal(200, httpContext.Response.StatusCode);
Assert.Equal(1, Interlocked.Read(ref numCallsToNext));
}

private Task CountingMiddleware(HttpContext context)
{
Expand Down
63 changes: 54 additions & 9 deletions test/NSign.Client.UnitTests/Client/AddContentDigestHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,42 @@ public async Task SendAsyncDoesNotAddContentDigestIfRequestDoesNotHaveContent()
}

[Theory]
[InlineData("stream", "hello world", Hash.Sha256, "SHA-256=uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=")]
[InlineData("string", "hello", Hash.Sha256, "SHA-256=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=")]
[InlineData("json", "hello", Hash.Sha256, "SHA-256=Wqdirjg/u3J688ejbUlApbjECpiUUtIwT8lY/z81Tno=")]
[InlineData("stream", "hello world", Hash.Sha512, "SHA-512=MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==")]
[InlineData("string", "hello", Hash.Sha512, "SHA-512=m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==")]
[InlineData("json", "hello", Hash.Sha512, "SHA-512=A8pplr4vsk4xdLkJruCXWp6+i+dy/3pSW5HW5ke1jDWS70Dv6Fstf1jS+XEcLqEVhW3i925IPlf/4tnpnvAQDw==")]
[InlineData(
"stream",
"hello world",
Hash.Sha256,
"sha-256=:uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=:"
)]
[InlineData(
"string",
"hello",
Hash.Sha256,
"sha-256=:LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=:"
)]
[InlineData(
"json",
"hello",
Hash.Sha256,
"sha-256=:Wqdirjg/u3J688ejbUlApbjECpiUUtIwT8lY/z81Tno=:"
)]
[InlineData(
"stream",
"hello world",
Hash.Sha512,
"sha-512=:MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==:"
)]
[InlineData(
"string",
"hello",
Hash.Sha512,
"sha-512=:m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==:"
)]
[InlineData(
"json",
"hello",
Hash.Sha512,
"sha-512=:A8pplr4vsk4xdLkJruCXWp6+i+dy/3pSW5HW5ke1jDWS70Dv6Fstf1jS+XEcLqEVhW3i925IPlf/4tnpnvAQDw==:"
)]
public async Task SendAsyncAddsOnlyConfiguredHashes(string httpContentType, string content, Hash hash, string expectedValue)
{
using HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
Expand All @@ -80,9 +110,24 @@ public async Task SendAsyncAddsOnlyConfiguredHashes(string httpContentType, stri
}

[Theory]
[InlineData("stream", "test", "SHA-256=n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=", "SHA-512=7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==")]
[InlineData("string", "test", "SHA-256=n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=", "SHA-512=7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==")]
[InlineData("json", "test", "SHA-256=TZZ6MBEb8p8OugHESLN1wWKbL+0BzfzDrtkfG1fV3V4=", "SHA-512=ceemix/T1umsPeT9f/DEUNpccmguTGuGcqp+SAhBhz9oTwjX49sBWRNNam2cvhm51qgMV+NsXMm/Fg6JsKjgJQ==")]
[InlineData(
"stream",
"test",
"sha-256=:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=:",
"sha-512=:7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==:"
)]
[InlineData(
"string",
"test",
"sha-256=:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=:",
"sha-512=:7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==:"
)]
[InlineData(
"json",
"test",
"sha-256=:TZZ6MBEb8p8OugHESLN1wWKbL+0BzfzDrtkfG1fV3V4=:",
"sha-512=:ceemix/T1umsPeT9f/DEUNpccmguTGuGcqp+SAhBhz9oTwjX49sBWRNNam2cvhm51qgMV+NsXMm/Fg6JsKjgJQ==:"
)]
public async Task SendAsyncAddsMultipleHashes(string httpContentType, string content, string expectedValue1, string expectedValue2)
{
using HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
Expand Down

0 comments on commit 520dce1

Please sign in to comment.