diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationHandler.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationHandler.cs
index bc63cd1c8..257c36a15 100644
--- a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationHandler.cs
+++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationHandler.cs
@@ -66,7 +66,7 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop
var nonceGenerator = Options.NonceGenerator;
var staleNonce = Context.Items[DigestFields.Stale] as string ?? "false";
AuthenticationHandlerFeature.Set(await HandleAuthenticateOnceSafeAsync().ConfigureAwait(false), Context); // so annoying that Microsoft does not propagate AuthenticateResult properly - other have noticed as well: https://github.com/dotnet/aspnetcore/issues/44100
- Decorator.Enclose(Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale={staleNonce}, algorithm={DigestAuthenticationMiddleware.ParseAlgorithm(Options.Algorithm)}"));
+ Decorator.Enclose(Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale={staleNonce}, algorithm={DigestAuthenticationMiddleware.ParseAlgorithm(Options.DigestAlgorithm)}"));
await base.HandleChallengeAsync(properties).ConfigureAwait(false);
}
}
diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationMiddleware.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationMiddleware.cs
index 3ee46e678..ae91b3444 100644
--- a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationMiddleware.cs
+++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationMiddleware.cs
@@ -7,7 +7,6 @@
using System.Threading.Tasks;
using Cuemon.Collections.Generic;
using Cuemon.IO;
-using Cuemon.Security.Cryptography;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
@@ -61,7 +60,7 @@ await Decorator.Enclose(context).InvokeUnauthorizedExceptionAsync(Options, princ
var nonceSecret = Options.NonceSecret;
var nonceGenerator = Options.NonceGenerator;
var staleNonce = dc.Items[DigestFields.Stale] as string ?? "false";
- Decorator.Enclose(dc.Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale={staleNonce}, algorithm={ParseAlgorithm(Options.Algorithm)}"));
+ Decorator.Enclose(dc.Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale={staleNonce}, algorithm={ParseAlgorithm(Options.DigestAlgorithm)}"));
}).ConfigureAwait(false);
}
@@ -138,14 +137,22 @@ internal static DigestAuthorizationHeader AuthorizationHeaderParser(HttpContext
return DigestAuthorizationHeader.Create(authorizationHeader);
}
- internal static string ParseAlgorithm(UnkeyedCryptoAlgorithm algorithm)
+ internal static string ParseAlgorithm(DigestCryptoAlgorithm algorithm)
{
switch (algorithm)
{
- case UnkeyedCryptoAlgorithm.Sha256:
+ case DigestCryptoAlgorithm.Md5:
+ return "MD5";
+ case DigestCryptoAlgorithm.Md5Session:
+ return "MD5-sess";
+ case DigestCryptoAlgorithm.Sha256:
return "SHA-256";
- case UnkeyedCryptoAlgorithm.Sha512:
+ case DigestCryptoAlgorithm.Sha256Session:
+ return "SHA-256-sess";
+ case DigestCryptoAlgorithm.Sha512Slash256:
return "SHA-512-256";
+ case DigestCryptoAlgorithm.Sha512Slash256Session:
+ return "SHA-512-256-sess";
default:
return "MD5";
}
diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationOptions.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationOptions.cs
index 0f638d665..a8a5005b0 100644
--- a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationOptions.cs
+++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthenticationOptions.cs
@@ -23,14 +23,14 @@ public sealed class DigestAuthenticationOptions : AuthenticationOptions
/// Initial Value
///
/// -
- ///
- ///
- ///
- /// -
///
/// null
///
/// -
+ ///
+ ///
+ ///
+ /// -
///
/// A default implementation of a nonce generator.
///
@@ -58,7 +58,7 @@ public sealed class DigestAuthenticationOptions : AuthenticationOptions
///
public DigestAuthenticationOptions()
{
- Algorithm = UnkeyedCryptoAlgorithm.Sha256;
+ DigestAlgorithm = DigestCryptoAlgorithm.Sha256;
OpaqueGenerator = () => Generate.RandomString(32, Alphanumeric.Hexadecimal).ToLowerInvariant();
NonceExpiredParser = (nonce, timeToLive) =>
{
@@ -103,8 +103,14 @@ public DigestAuthenticationOptions()
///
/// The algorithm of the HTTP Digest Access Authentication.
/// Allowed values are: , and .
+ [Obsolete("This member is obsolete and will be removed in a future version. Use DigestAlgorithm property instead.")]
public UnkeyedCryptoAlgorithm Algorithm { get; set; }
+ ///
+ /// Specifies the cryptographic algorithm used in HTTP Digest Access Authentication. Default is .
+ ///
+ public DigestCryptoAlgorithm DigestAlgorithm { get; set; }
+
///
/// Gets the realm that defines the protection space.
///
@@ -139,7 +145,7 @@ public DigestAuthenticationOptions()
/// Gets or sets a value indicating whether the server should bypass the calculation of HA1 password representation.
///
/// true if the server should bypass the calculation of HA1 password representation; otherwise, false.
- /// When enabled, the server reads the HA1 value directly from a secured storage.
+ /// When enabled, the server reads the HA1 value directly from a secured storage, hence this cannot be used in combination with session variants of .
public bool UseServerSideHa1Storage { get; set; }
///
diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeaderBuilder.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeaderBuilder.cs
index de54154b9..73b2f7389 100644
--- a/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeaderBuilder.cs
+++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestAuthorizationHeaderBuilder.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Globalization;
using System.Net.Http.Headers;
using System.Text;
@@ -13,16 +14,46 @@ namespace Cuemon.AspNetCore.Authentication.Digest
///
public class DigestAuthorizationHeaderBuilder : AuthorizationHeaderBuilder
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DigestAuthorizationHeaderBuilder() : this(DigestCryptoAlgorithm.Sha256)
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
/// The algorithm to use when computing HA1, HA2 and/or RESPONSE value(s).
/// Allowed values for are: , and .
- public DigestAuthorizationHeaderBuilder(UnkeyedCryptoAlgorithm algorithm = UnkeyedCryptoAlgorithm.Sha256) : base(DigestAuthorizationHeader.Scheme)
+ [Obsolete("This constructor is obsolete and will be removed in a future version. Use DigestAlgorithm variant instead.")]
+ public DigestAuthorizationHeaderBuilder(UnkeyedCryptoAlgorithm algorithm) : this(Validator.CheckParameter(
+ () =>
+ {
+ Validator.ThrowIfEqual(algorithm, UnkeyedCryptoAlgorithm.Sha1, nameof(algorithm));
+ Validator.ThrowIfEqual(algorithm, UnkeyedCryptoAlgorithm.Sha384, nameof(algorithm));
+ switch (algorithm)
+ {
+ case UnkeyedCryptoAlgorithm.Md5:
+ return DigestCryptoAlgorithm.Md5;
+ case UnkeyedCryptoAlgorithm.Sha256:
+ return DigestCryptoAlgorithm.Sha256;
+ case UnkeyedCryptoAlgorithm.Sha512:
+ return DigestCryptoAlgorithm.Sha512Slash256;
+ default:
+ throw new InvalidEnumArgumentException(nameof(algorithm), (int)algorithm, typeof(UnkeyedCryptoAlgorithm));
+ }
+ }))
{
- Validator.ThrowIfEqual(algorithm, UnkeyedCryptoAlgorithm.Sha1, nameof(algorithm));
- Validator.ThrowIfEqual(algorithm, UnkeyedCryptoAlgorithm.Sha384, nameof(algorithm));
- Algorithm = algorithm;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The algorithm to use when computing HA1, HA2 and/or RESPONSE value(s).
+ public DigestAuthorizationHeaderBuilder(DigestCryptoAlgorithm algorithm) : base(DigestAuthorizationHeader.Scheme)
+ {
+ DigestAlgorithm = algorithm;
MapRelation(nameof(AddResponse), DigestFields.Response);
MapRelation(nameof(AddRealm), DigestFields.Realm);
MapRelation(nameof(AddUserName), DigestFields.UserName);
@@ -40,7 +71,15 @@ public DigestAuthorizationHeaderBuilder(UnkeyedCryptoAlgorithm algorithm = Unkey
/// Gets the algorithm of the HTTP Digest Access Authentication.
///
/// The algorithm of the HTTP Digest Access Authentication.
- public UnkeyedCryptoAlgorithm Algorithm { get; private set; }
+
+ [Obsolete("This member is obsolete and will be removed in a future version. Use DigestAlgorithm property instead.")]
+ public UnkeyedCryptoAlgorithm Algorithm { get; }
+
+ ///
+ /// Gets the algorithm of the HTTP Digest Access Authentication.
+ ///
+ /// The algorithm of the HTTP Digest Access Authentication.
+ public DigestCryptoAlgorithm DigestAlgorithm { get; private set; }
///
/// Associates the field with the specified .
@@ -161,7 +200,7 @@ private DigestAuthorizationHeaderBuilder AddFromWwwAuthenticateHeader(string www
public DigestAuthorizationHeaderBuilder AddFromDigestAuthorizationHeader(DigestAuthorizationHeader header)
{
Validator.ThrowIfNull(header);
- Algorithm = ParseAlgorithm(header.Algorithm);
+ DigestAlgorithm = ParseAlgorithm(header.Algorithm);
AddUserName(header.UserName);
AddRealm(header.Realm);
AddUri(header.Uri);
@@ -172,11 +211,15 @@ public DigestAuthorizationHeaderBuilder AddFromDigestAuthorizationHeader(DigestA
return this;
}
- private static UnkeyedCryptoAlgorithm ParseAlgorithm(string algorithm)
+ private static DigestCryptoAlgorithm ParseAlgorithm(string algorithm)
{
- if (algorithm.StartsWith("SHA-512", StringComparison.OrdinalIgnoreCase)) { return UnkeyedCryptoAlgorithm.Sha512; }
- if (algorithm.StartsWith("SHA-256", StringComparison.OrdinalIgnoreCase)) { return UnkeyedCryptoAlgorithm.Sha256; }
- return UnkeyedCryptoAlgorithm.Md5;
+ if (algorithm.Equals("MD5", StringComparison.OrdinalIgnoreCase)) { return DigestCryptoAlgorithm.Md5; }
+ if (algorithm.Equals("SHA-256", StringComparison.OrdinalIgnoreCase)) { return DigestCryptoAlgorithm.Sha256; }
+ if (algorithm.Equals("SHA-512-256", StringComparison.OrdinalIgnoreCase)) { return DigestCryptoAlgorithm.Sha512Slash256; }
+ if (algorithm.Equals("MD5-sess", StringComparison.OrdinalIgnoreCase)) { return DigestCryptoAlgorithm.Md5Session; }
+ if (algorithm.Equals("SHA-256-sess", StringComparison.OrdinalIgnoreCase)) { return DigestCryptoAlgorithm.Sha256Session; }
+ if (algorithm.Equals("SHA-512-256-sess", StringComparison.OrdinalIgnoreCase)) { return DigestCryptoAlgorithm.Sha512Slash256Session; }
+ throw new NotSupportedException($"The algorithm '{algorithm}' is not supported.");
}
///
@@ -198,15 +241,31 @@ public DigestAuthorizationHeaderBuilder AddResponse(string password, string meth
/// Computes a by parameter defined hash value of the required values for the HTTP Digest access authentication HA1.
///
/// The password to include in the HA1 computed value.
- /// A in the format of H(::). H is determined by .
+ /// A in the format of H(::). H is determined by .
public virtual string ComputeHash1(string password)
{
Validator.ThrowIfNullOrWhitespace(password);
ValidateData(DigestFields.UserName, DigestFields.Realm);
- return UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", Data[DigestFields.UserName], Data[DigestFields.Realm], password), o =>
+ var crypto = DigestHashFactory.CreateCrypto(DigestAlgorithm);
+
+ var ha1 = crypto.ComputeHash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", Data[DigestFields.UserName], Data[DigestFields.Realm], password), o =>
{
o.Encoding = Encoding.UTF8;
}).ToHexadecimalString();
+
+ switch (DigestAlgorithm)
+ {
+ case DigestCryptoAlgorithm.Md5Session:
+ case DigestCryptoAlgorithm.Sha256Session:
+ case DigestCryptoAlgorithm.Sha512Slash256Session:
+ ha1 = crypto.ComputeHash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", ha1, Data[DigestFields.Nonce], Data[DigestFields.ClientNonce]), o =>
+ {
+ o.Encoding = Encoding.UTF8;
+ }).ToHexadecimalString();
+ break;
+ }
+
+ return ha1;
}
///
@@ -214,7 +273,7 @@ public virtual string ComputeHash1(string password)
///
/// The HTTP method to include in the HA2 computed value.
/// The entity body to apply in the signature when qop is set to auth-int.
- /// A in the format of H(:) OR H(::H()). H is determined by .
+ /// A in the format of H(:) OR H(::H()). H is determined by .
public virtual string ComputeHash2(string method, string entityBody = null)
{
Validator.ThrowIfNullOrWhitespace(method);
@@ -226,8 +285,8 @@ public virtual string ComputeHash2(string method, string entityBody = null)
var hashFields = !hasIntegrityProtection
? string.Create(CultureInfo.InvariantCulture, $"{method}:{Data[DigestFields.DigestUri]}")
- : string.Create(CultureInfo.InvariantCulture, $"{method}:{Data[DigestFields.DigestUri]}:{UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(entityBody, o => o.Encoding = Encoding.UTF8).ToHexadecimalString()}");
- return UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(hashFields, o =>
+ : string.Create(CultureInfo.InvariantCulture, $"{method}:{Data[DigestFields.DigestUri]}:{DigestHashFactory.CreateCrypto(DigestAlgorithm).ComputeHash(entityBody, o => o.Encoding = Encoding.UTF8).ToHexadecimalString()}");
+ return DigestHashFactory.CreateCrypto(DigestAlgorithm).ComputeHash(hashFields, o =>
{
o.Encoding = Encoding.UTF8;
}).ToHexadecimalString();
@@ -238,13 +297,13 @@ public virtual string ComputeHash2(string method, string entityBody = null)
///
/// The HA1 to include in the RESPONSE computed value.
/// The HA2 to include in the RESPONSE computed value.
- /// A in the format of H(:::::). H is determined by .
+ /// A in the format of H(:::::). H is determined by .
public virtual string ComputeResponse(string hash1, string hash2)
{
Validator.ThrowIfNullOrWhitespace(hash1);
Validator.ThrowIfNullOrWhitespace(hash2);
ValidateData(DigestFields.Nonce, DigestFields.NonceCount, DigestFields.ClientNonce, DigestFields.QualityOfProtection);
- return UnkeyedHashFactory.CreateCrypto(Algorithm).ComputeHash(string.Create(CultureInfo.InvariantCulture, $"{hash1}:{Data[DigestFields.Nonce]}:{Data[DigestFields.NonceCount]}:{Data[DigestFields.ClientNonce]}:{Data[DigestFields.QualityOfProtection]}:{hash2}"), o =>
+ return DigestHashFactory.CreateCrypto(DigestAlgorithm).ComputeHash(string.Create(CultureInfo.InvariantCulture, $"{hash1}:{Data[DigestFields.Nonce]}:{Data[DigestFields.NonceCount]}:{Data[DigestFields.ClientNonce]}:{Data[DigestFields.QualityOfProtection]}:{hash2}"), o =>
{
o.Encoding = Encoding.UTF8;
}).ToHexadecimalString();
diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestCryptoAlgorithm.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestCryptoAlgorithm.cs
new file mode 100644
index 000000000..c306c7a54
--- /dev/null
+++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestCryptoAlgorithm.cs
@@ -0,0 +1,38 @@
+namespace Cuemon.AspNetCore.Authentication.Digest
+{
+ ///
+ /// Specifies the cryptographic algorithms used in Digest authentication.
+ ///
+ public enum DigestCryptoAlgorithm
+ {
+ ///
+ /// The Message Digest 5 (MD5) algorithm (128 bits).
+ ///
+ Md5 = -2,
+
+ ///
+ /// The Message Digest 5 (MD5) algorithm (128 bits) session variant.
+ ///
+ Md5Session = -1,
+
+ ///
+ /// The Secure Hashing Algorithm (SHA256) algorithm (256 bits).
+ ///
+ Sha256 = 0,
+
+ ///
+ /// The Secure Hashing Algorithm (SHA256) algorithm (256 bits) session variant.
+ ///
+ Sha256Session = 1,
+
+ ///
+ /// The Secure Hashing Algorithm (SHA512/256) algorithm (256 bits).
+ ///
+ Sha512Slash256 = 2,
+
+ ///
+ /// The Secure Hashing Algorithm (SHA512/256) algorithm (256 bits) session variant.
+ ///
+ Sha512Slash256Session = 3
+ }
+}
diff --git a/src/Cuemon.AspNetCore.Authentication/Digest/DigestHashFactory.cs b/src/Cuemon.AspNetCore.Authentication/Digest/DigestHashFactory.cs
new file mode 100644
index 000000000..13013c525
--- /dev/null
+++ b/src/Cuemon.AspNetCore.Authentication/Digest/DigestHashFactory.cs
@@ -0,0 +1,35 @@
+using System;
+using Cuemon.Security;
+using Cuemon.Security.Cryptography;
+
+namespace Cuemon.AspNetCore.Authentication.Digest
+{
+ ///
+ /// Provides access to factory methods for creating and configuring instances based on .
+ ///
+ public static class DigestHashFactory
+ {
+ ///
+ /// Creates an instance of a cryptographic implementation that derives from with the specified .
+ ///
+ /// The that defines the cryptographic implementation. Default is .
+ /// A implementation of the by parameter specified .
+ public static Hash CreateCrypto(DigestCryptoAlgorithm algorithm = default)
+ {
+ switch (algorithm)
+ {
+ case DigestCryptoAlgorithm.Md5:
+ case DigestCryptoAlgorithm.Md5Session:
+ return UnkeyedHashFactory.CreateCrypto(UnkeyedCryptoAlgorithm.Md5);
+ case DigestCryptoAlgorithm.Sha256:
+ case DigestCryptoAlgorithm.Sha256Session:
+ return UnkeyedHashFactory.CreateCrypto(UnkeyedCryptoAlgorithm.Sha256);
+ case DigestCryptoAlgorithm.Sha512Slash256:
+ case DigestCryptoAlgorithm.Sha512Slash256Session:
+ return UnkeyedHashFactory.CreateCrypto(UnkeyedCryptoAlgorithm.Sha512Slash256);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm, $"The specified {nameof(algorithm)} is not supported.");
+ }
+ }
+ }
+}
diff --git a/src/Cuemon.Security.Cryptography/SHA512256.cs b/src/Cuemon.Security.Cryptography/SHA512256.cs
new file mode 100644
index 000000000..498408445
--- /dev/null
+++ b/src/Cuemon.Security.Cryptography/SHA512256.cs
@@ -0,0 +1,219 @@
+using System;
+using System.Security.Cryptography;
+
+namespace Cuemon.Security.Cryptography
+{
+ ///
+ /// Represents the SHA-512/256 cryptographic hash algorithm, which produces a 256-bit hash value using the SHA-512 algorithm as its base.
+ ///
+ /// Full disclosure; this class was created in collaboration with OpenAI ChatGPT. Have a look at the prompt here: https://chatgpt.com/share/67fbd1fe-17d8-8010-8144-e94251c2e79d
+ public sealed class SHA512256 : HashAlgorithm
+ {
+ private const int BlockSize = 128; // 1024 bits
+ private const int DigestLength = 32; // 256 bits
+
+ private static readonly ulong[] K = new ulong[]
+ {
+ // Constants used in the SHA-512 algorithm
+ 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
+ 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
+ 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
+ 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
+ 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
+ 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
+ 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
+ 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
+ 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
+ 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
+ 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
+ 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
+ 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
+ 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
+ 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
+ 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
+ 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
+ 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
+ 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
+ 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
+ };
+
+ private static readonly ulong[] IV512_256 = new ulong[]
+ {
+ // Initial hash values for SHA-512/256
+ 0x22312194FC2BF72C, 0x9F555FA3C84C64C2,
+ 0x2393B86B6F53B151, 0x963877195940EABD,
+ 0x96283EE2A88EFFE3, 0xBE5E1E2553863992,
+ 0x2B0199FC2C85B8AA, 0x0EB72DDC81C52CA2
+ };
+
+ private ulong[] _H = new ulong[8];
+ private ulong[] _W = new ulong[80];
+ private byte[] _buffer = new byte[BlockSize];
+ private int _bufferPos;
+ private ulong _bitCountHigh;
+ private ulong _bitCountLow;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SHA512256()
+ {
+ Initialize();
+ HashSizeValue = 256;
+ }
+
+ ///
+ /// Initializes the hash algorithm, resetting its state.
+ ///
+ public override void Initialize()
+ {
+ Array.Copy(IV512_256, _H, 8);
+ Array.Clear(_buffer, 0, _buffer.Length);
+ _bufferPos = 0;
+ _bitCountHigh = 0;
+ _bitCountLow = 0;
+ }
+
+ ///
+ /// Routes data written to the object into the hash algorithm for computing the hash.
+ ///
+ /// The input data.
+ /// The offset into the byte array from which to begin using data.
+ /// The number of bytes in the array to use as data.
+ protected override void HashCore(byte[] array, int ibStart, int cbSize)
+ {
+ while (cbSize > 0)
+ {
+ int toCopy = Math.Min(BlockSize - _bufferPos, cbSize);
+ Array.Copy(array, ibStart, _buffer, _bufferPos, toCopy);
+ _bufferPos += toCopy;
+ ibStart += toCopy;
+ cbSize -= toCopy;
+
+ AddLength((ulong)(toCopy * 8)); // track total bit count
+
+ if (_bufferPos == BlockSize)
+ {
+ ProcessBlock(_buffer, 0);
+ _bufferPos = 0;
+ }
+ }
+ }
+
+ ///
+ /// Finalizes the hash computation after the last data is processed.
+ ///
+ /// The computed hash code.
+ protected override byte[] HashFinal()
+ {
+ // Padding
+ _buffer[_bufferPos++] = 0x80;
+ if (_bufferPos > BlockSize - 16)
+ {
+ Array.Clear(_buffer, _bufferPos, BlockSize - _bufferPos);
+ ProcessBlock(_buffer, 0);
+ _bufferPos = 0;
+ }
+
+ Array.Clear(_buffer, _bufferPos, BlockSize - _bufferPos);
+
+ // Append total bit count (128-bit big-endian)
+ WriteULongBE(_bitCountHigh, _buffer, BlockSize - 16);
+ WriteULongBE(_bitCountLow, _buffer, BlockSize - 8);
+ ProcessBlock(_buffer, 0);
+
+ // Produce digest (first 256 bits = first 4 H words)
+ byte[] output = new byte[DigestLength];
+ for (int i = 0; i < 4; i++)
+ {
+ WriteULongBE(_H[i], output, i * 8);
+ }
+
+ return output;
+ }
+
+ ///
+ /// Adds the specified number of bits to the total bit count.
+ ///
+ /// The number of bits to add.
+ private void AddLength(ulong bits)
+ {
+ _bitCountLow += bits;
+ if (_bitCountLow < bits)
+ _bitCountHigh++;
+ }
+
+ ///
+ /// Writes a 64-bit unsigned integer to a byte array in big-endian format.
+ ///
+ /// The value to write.
+ /// The buffer to write to.
+ /// The offset in the buffer to start writing at.
+ private static void WriteULongBE(ulong value, byte[] buffer, int offset)
+ {
+ for (int i = 7; i >= 0; i--)
+ buffer[offset + 7 - i] = (byte)(value >> (i * 8));
+ }
+
+ ///
+ /// Processes a single 1024-bit block of data.
+ ///
+ /// The block of data to process.
+ /// The offset in the block to start processing at.
+ private void ProcessBlock(byte[] block, int offset)
+ {
+ for (int i = 0; i < 16; i++)
+ {
+ _W[i] =
+ ((ulong)block[offset + i * 8 + 0] << 56) |
+ ((ulong)block[offset + i * 8 + 1] << 48) |
+ ((ulong)block[offset + i * 8 + 2] << 40) |
+ ((ulong)block[offset + i * 8 + 3] << 32) |
+ ((ulong)block[offset + i * 8 + 4] << 24) |
+ ((ulong)block[offset + i * 8 + 5] << 16) |
+ ((ulong)block[offset + i * 8 + 6] << 8) |
+ ((ulong)block[offset + i * 8 + 7]);
+ }
+
+ for (int i = 16; i < 80; i++)
+ {
+ ulong s0 = RotateRight(_W[i - 15], 1) ^ RotateRight(_W[i - 15], 8) ^ (_W[i - 15] >> 7);
+ ulong s1 = RotateRight(_W[i - 2], 19) ^ RotateRight(_W[i - 2], 61) ^ (_W[i - 2] >> 6);
+ _W[i] = _W[i - 16] + s0 + _W[i - 7] + s1;
+ }
+
+ ulong a = _H[0], b = _H[1], c = _H[2], d = _H[3];
+ ulong e = _H[4], f = _H[5], g = _H[6], h = _H[7];
+
+ for (int i = 0; i < 80; i++)
+ {
+ ulong S1 = RotateRight(e, 14) ^ RotateRight(e, 18) ^ RotateRight(e, 41);
+ ulong ch = (e & f) ^ (~e & g);
+ ulong temp1 = h + S1 + ch + K[i] + _W[i];
+ ulong S0 = RotateRight(a, 28) ^ RotateRight(a, 34) ^ RotateRight(a, 39);
+ ulong maj = (a & b) ^ (a & c) ^ (b & c);
+ ulong temp2 = S0 + maj;
+
+ h = g;
+ g = f;
+ f = e;
+ e = d + temp1;
+ d = c;
+ c = b;
+ b = a;
+ a = temp1 + temp2;
+ }
+
+ _H[0] += a; _H[1] += b; _H[2] += c; _H[3] += d;
+ _H[4] += e; _H[5] += f; _H[6] += g; _H[7] += h;
+ }
+
+ ///
+ /// Rotates the bits of a 64-bit unsigned integer to the right.
+ ///
+ /// The value to rotate.
+ /// The number of bits to rotate.
+ /// The rotated value.
+ private static ulong RotateRight(ulong x, int n) => (x >> n) | (x << (64 - n));
+ }
+}
diff --git a/src/Cuemon.Security.Cryptography/SecureHashAlgorithm512256.cs b/src/Cuemon.Security.Cryptography/SecureHashAlgorithm512256.cs
new file mode 100644
index 000000000..836ccb351
--- /dev/null
+++ b/src/Cuemon.Security.Cryptography/SecureHashAlgorithm512256.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Cuemon.Security.Cryptography
+{
+ ///
+ /// Provides a SHA-512-256 implementation of the SHA (Secure Hash Algorithm) cryptographic hashing algorithm for 512-bit hash values. This class cannot be inherited.
+ /// Implements the
+ ///
+ ///
+ ///
+ public sealed class SecureHashAlgorithm512256 : UnkeyedCryptoHash
+ {
+ ///
+ /// Produces a 256-bit hash value
+ ///
+ public const int BitSize = 256;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The which may be configured.
+ public SecureHashAlgorithm512256(Action setup) : base(() => new SHA512256(), setup)
+ {
+ }
+ }
+}
diff --git a/src/Cuemon.Security.Cryptography/UnkeyedCryptoAlgorithm.cs b/src/Cuemon.Security.Cryptography/UnkeyedCryptoAlgorithm.cs
index 994a9048e..3c72fc713 100644
--- a/src/Cuemon.Security.Cryptography/UnkeyedCryptoAlgorithm.cs
+++ b/src/Cuemon.Security.Cryptography/UnkeyedCryptoAlgorithm.cs
@@ -24,6 +24,10 @@ public enum UnkeyedCryptoAlgorithm
///
/// The Secure Hashing Algorithm (SHA512) algorithm (512 bits).
///
- Sha512 = 2
+ Sha512 = 2,
+ ///
+ /// The Secure Hashing Algorithm (SHA512/256) algorithm (256 bits).
+ ///
+ Sha512Slash256 = 3
}
-}
\ No newline at end of file
+}
diff --git a/src/Cuemon.Security.Cryptography/UnkeyedHashFactory.cs b/src/Cuemon.Security.Cryptography/UnkeyedHashFactory.cs
index 48a88ece2..9c3855a6b 100644
--- a/src/Cuemon.Security.Cryptography/UnkeyedHashFactory.cs
+++ b/src/Cuemon.Security.Cryptography/UnkeyedHashFactory.cs
@@ -25,11 +25,23 @@ public static Hash CreateCrypto(UnkeyedCryptoAlgorithm algorithm = default, Acti
return CreateCryptoSha384(setup);
case UnkeyedCryptoAlgorithm.Sha512:
return CreateCryptoSha512(setup);
+ case UnkeyedCryptoAlgorithm.Sha512Slash256:
+ return CreateCryptoSha512Slash256(setup);
default:
return CreateCryptoSha256(setup);
}
}
+ ///
+ /// Creates an instance of .
+ ///
+ /// The which may be configured.
+ /// A implementation of .
+ public static Hash CreateCryptoSha512Slash256(Action setup = null)
+ {
+ return new SecureHashAlgorithm512256(setup);
+ }
+
///
/// Creates an instance of .
///
diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationHandlerTest.cs b/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationHandlerTest.cs
index c71af5f41..ab50fba14 100644
--- a/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationHandlerTest.cs
+++ b/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationHandlerTest.cs
@@ -25,8 +25,14 @@ public DigestAccessAuthenticationHandlerTest(ITestOutputHelper output) : base(ou
{
}
- [Fact]
- public async Task HandleAuthenticateAsync_ShouldReturnContent_WithQopAuthentication()
+ [Theory]
+ [InlineData(DigestCryptoAlgorithm.Sha256)]
+ [InlineData(DigestCryptoAlgorithm.Sha256Session)]
+ [InlineData(DigestCryptoAlgorithm.Sha512Slash256)]
+ [InlineData(DigestCryptoAlgorithm.Sha512Slash256Session)]
+ [InlineData(DigestCryptoAlgorithm.Md5)]
+ [InlineData(DigestCryptoAlgorithm.Md5Session)]
+ public async Task HandleAuthenticateAsync_ShouldReturnContent_WithQopAuthentication(DigestCryptoAlgorithm algorithm)
{
using (var webApp = WebHostTestFactory.Create(services =>
{
@@ -49,6 +55,7 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_WithQopAuthenticat
return null;
};
o.RequireSecureConnection = false;
+ o.DigestAlgorithm = algorithm;
});
}, app =>
{
@@ -72,7 +79,7 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_WithQopAuthenticat
TestOutput.WriteLine("WWW-Authenticate:");
TestOutput.WriteLine(wwwAuthenticate);
- var db = new DigestAuthorizationHeaderBuilder(options.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.DigestAlgorithm)
.AddRealm(options.Realm)
.AddUserName("Agent")
.AddUri("/")
@@ -101,8 +108,14 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_WithQopAuthenticat
}
}
- [Fact]
- public async Task HandleAuthenticateAsync_ShouldReturnContent_QopAuthenticationIntegrity()
+ [Theory]
+ [InlineData(DigestCryptoAlgorithm.Sha256)]
+ [InlineData(DigestCryptoAlgorithm.Sha256Session)]
+ [InlineData(DigestCryptoAlgorithm.Sha512Slash256)]
+ [InlineData(DigestCryptoAlgorithm.Sha512Slash256Session)]
+ [InlineData(DigestCryptoAlgorithm.Md5)]
+ [InlineData(DigestCryptoAlgorithm.Md5Session)]
+ public async Task HandleAuthenticateAsync_ShouldReturnContent_QopAuthenticationIntegrity(DigestCryptoAlgorithm algorithm)
{
using (var webApp = WebHostTestFactory.Create(services =>
{
@@ -125,6 +138,7 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_QopAuthenticationI
return null;
};
o.RequireSecureConnection = false;
+ o.DigestAlgorithm = algorithm;
});
}, app =>
{
@@ -148,7 +162,7 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_QopAuthenticationI
TestOutput.WriteLine("WWW-Authenticate:");
TestOutput.WriteLine(wwwAuthenticate);
- var db = new DigestAuthorizationHeaderBuilder(options.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.DigestAlgorithm)
.AddRealm(options.Realm)
.AddUserName("Agent")
.AddUri("/")
@@ -189,13 +203,13 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_WithServerSideHa1S
.AddAuthentication(DigestAuthorizationHeader.Scheme)
.AddDigestAccess(o =>
{
- o.Algorithm = UnkeyedCryptoAlgorithm.Sha512;
+ o.DigestAlgorithm = DigestCryptoAlgorithm.Sha512Slash256;
o.UseServerSideHa1Storage = true;
o.Authenticator = (string username, out string password) =>
{
if (username == "Agent")
{
- password = "64d7c739de5dc6b5149de600751c413ef74fab0419e5a656e9f5ead5b98105b8c75ba6e18850ccd7ef2a3a13517520e158181bd34a4b68e2ab3b728acd7d066b";
+ password = "7a0adced41ceeaf77c95a4bb382a80303536fd3ee166a3a67a2dc9c100a9d7be";
var cp = new ClaimsPrincipal();
cp.AddIdentity(new ClaimsIdentity(Arguments.Yield(new Claim("Name", "Test Agent")), DigestAuthorizationHeader.Scheme));
return cp;
@@ -227,7 +241,7 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_WithServerSideHa1S
TestOutput.WriteLine("WWW-Authenticate:");
TestOutput.WriteLine(wwwAuthenticate);
- var db = new DigestAuthorizationHeaderBuilder(options.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.DigestAlgorithm)
.AddRealm(options.Realm)
.AddUserName("Agent")
.AddUri("/")
@@ -253,7 +267,7 @@ public async Task HandleAuthenticateAsync_ShouldReturnContent_WithServerSideHa1S
result = await client.GetAsync("/fake");
- Assert.Equal(UnkeyedCryptoAlgorithm.Sha512, options.Algorithm);
+ Assert.Equal(DigestCryptoAlgorithm.Sha512Slash256, options.DigestAlgorithm);
Assert.True(options.UseServerSideHa1Storage);
Assert.False(options.RequireSecureConnection);
diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationMiddlewareTest.cs b/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationMiddlewareTest.cs
index f9bf7b1b9..1190fb80c 100644
--- a/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationMiddlewareTest.cs
+++ b/test/Cuemon.AspNetCore.Authentication.Tests/Digest/DigestAccessAuthenticationMiddlewareTest.cs
@@ -117,7 +117,7 @@ public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeader(
TestOutput.WriteLine(wwwAuthenticate);
- var db = new DigestAuthorizationHeaderBuilder(options.Value.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.Value.DigestAlgorithm)
.AddRealm(options.Value.Realm)
.AddUserName("Agent")
.AddUri("/")
@@ -193,7 +193,7 @@ public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeaderN
TestOutput.WriteLine(wwwAuthenticate);
- var db = new DigestAuthorizationHeaderBuilder(options.Value.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.Value.DigestAlgorithm)
.AddRealm(options.Value.Realm)
.AddUserName("Agent")
.AddUri("/")
@@ -268,7 +268,7 @@ public async Task InvokeAsync_ShouldAuthenticateWhenApplyingAuthorizationHeaderW
TestOutput.WriteLine(wwwAuthenticate);
- var db = new DigestAuthorizationHeaderBuilder(options.Value.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.Value.DigestAlgorithm)
.AddRealm(options.Value.Realm)
.AddUserName("Agent")
.AddUri("/")
diff --git a/test/Cuemon.AspNetCore.Authentication.Tests/DigestAuthenticationOptionsTest.cs b/test/Cuemon.AspNetCore.Authentication.Tests/DigestAuthenticationOptionsTest.cs
index c1a58f67c..0b6ae5ffd 100644
--- a/test/Cuemon.AspNetCore.Authentication.Tests/DigestAuthenticationOptionsTest.cs
+++ b/test/Cuemon.AspNetCore.Authentication.Tests/DigestAuthenticationOptionsTest.cs
@@ -179,7 +179,7 @@ public void DigestAuthenticationOptions_ShouldHaveDefaultValues()
{
var sut = new DigestAuthenticationOptions();
- Assert.Equal(UnkeyedCryptoAlgorithm.Sha256, sut.Algorithm);
+ Assert.Equal(DigestCryptoAlgorithm.Sha256, sut.DigestAlgorithm);
Assert.NotNull(sut.OpaqueGenerator);
Assert.NotNull(sut.NonceExpiredParser);
Assert.NotNull(sut.NonceGenerator);
diff --git a/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/AuthorizationResponseHandlerTest.cs b/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/AuthorizationResponseHandlerTest.cs
index 07e76299e..8cf7a34af 100644
--- a/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/AuthorizationResponseHandlerTest.cs
+++ b/test/Cuemon.Extensions.AspNetCore.Authentication.Tests/AuthorizationResponseHandlerTest.cs
@@ -546,7 +546,7 @@ public async Task AuthorizationResponseHandler_DigestScheme_ShouldAuthorizeWithT
var result = await client.GetAsync("/");
- var db = new DigestAuthorizationHeaderBuilder(options.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.DigestAlgorithm)
.AddRealm(options.Realm)
.AddUserName("Agent")
.AddUri("/")
@@ -594,7 +594,7 @@ public async Task AuthorizationResponseHandler_DigestScheme_ShouldAuthorizeWithT
password = null;
return null;
};
- o.Algorithm = UnkeyedCryptoAlgorithm.Sha512;
+ o.DigestAlgorithm = DigestCryptoAlgorithm.Sha512Slash256;
});
services.AddAuthorization(o =>
{
@@ -623,7 +623,7 @@ public async Task AuthorizationResponseHandler_DigestScheme_ShouldAuthorizeWithT
var result = await client.GetAsync("/");
- var db = new DigestAuthorizationHeaderBuilder(options.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.DigestAlgorithm)
.AddRealm(options.Realm)
.AddUserName("Agent")
.AddUri("/")
@@ -702,7 +702,7 @@ public async Task AuthorizationResponseHandler_DigestScheme_ShouldRenderResponse
var result = await client.GetAsync("/");
var options = startup.Host.Services.GetRequiredScopedService>().Get(DigestAuthorizationHeader.Scheme);
- var db = new DigestAuthorizationHeaderBuilder(options.Algorithm)
+ var db = new DigestAuthorizationHeaderBuilder(options.DigestAlgorithm)
.AddRealm(options.Realm)
.AddUserName("Agent")
.AddUri("/")
diff --git a/test/Cuemon.Security.Cryptography.Tests/UnkeyedHashFactoryTest.cs b/test/Cuemon.Security.Cryptography.Tests/UnkeyedHashFactoryTest.cs
index aa72f9f5a..ac8a7c532 100644
--- a/test/Cuemon.Security.Cryptography.Tests/UnkeyedHashFactoryTest.cs
+++ b/test/Cuemon.Security.Cryptography.Tests/UnkeyedHashFactoryTest.cs
@@ -11,6 +11,16 @@ public UnkeyedHashFactoryTest(ITestOutputHelper output) : base(output)
{
}
+ [Fact]
+ public void CreateCryptoSha512256_ShouldBeValidHashResult()
+ {
+ var h = UnkeyedHashFactory.CreateCryptoSha512Slash256();
+ Assert.Equal("cdf1cc0effe26ecc0c13758f7b4a48e000615df241284185c39eb05d355bb9c8", h.ComputeHash(Alphanumeric.LettersAndNumbers).ToHexadecimalString());
+ Assert.Equal("d48b2aa4a50d1c3e324a1a762d3b2165244661ef80e004dd3669a77e02c489d8", h.ComputeHash(Alphanumeric.Numbers).ToHexadecimalString());
+ Assert.Equal("e41c9660b04714cdf7249f0fd6e6c5556f54a7e04d299958b69a877e0fada2fb", h.ComputeHash(Guid.Empty.ToByteArray()).ToHexadecimalString());
+ Assert.Equal("0ac561fac838104e3f2e4ad107b4bee3e938bf15f2b15f009ccccd61a913f017", h.ComputeHash("hello world").ToHexadecimalString());
+ }
+
[Fact]
public void CreateCryptoSha512_ShouldBeValidHashResult()
{