diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 61646b4ee8a9c1..dfc4c17c453021 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -694,6 +694,12 @@ public static partial class CryptographicOperations public static System.Threading.Tasks.ValueTask HmacDataAsync(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlyMemory key, System.IO.Stream source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static bool TryHashData(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } public static bool TryHmacData(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } + public static bool VerifyHmac(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool VerifyHmac(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool VerifyHmac(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool VerifyHmac(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyHmacAsync(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyHmacAsync(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static void ZeroMemory(System.Span buffer) { } } public partial class CryptographicUnexpectedOperationException : System.Security.Cryptography.CryptographicException @@ -1548,6 +1554,18 @@ public override void Initialize() { } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class HMACSHA1 : System.Security.Cryptography.HMAC { @@ -1575,6 +1593,12 @@ protected override void HashCore(System.ReadOnlySpan source) { } public override void Initialize() { } public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class HMACSHA256 : System.Security.Cryptography.HMAC { @@ -1599,6 +1623,12 @@ protected override void HashCore(System.ReadOnlySpan source) { } public override void Initialize() { } public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class HMACSHA384 : System.Security.Cryptography.HMAC { @@ -1625,6 +1655,12 @@ protected override void HashCore(System.ReadOnlySpan source) { } public override void Initialize() { } public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class HMACSHA3_256 : System.Security.Cryptography.HMAC { @@ -1650,6 +1686,12 @@ protected override void HashCore(System.ReadOnlySpan source) { } public override void Initialize() { } public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class HMACSHA3_384 : System.Security.Cryptography.HMAC { @@ -1675,6 +1717,12 @@ protected override void HashCore(System.ReadOnlySpan source) { } public override void Initialize() { } public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class HMACSHA3_512 : System.Security.Cryptography.HMAC { @@ -1700,6 +1748,12 @@ protected override void HashCore(System.ReadOnlySpan source) { } public override void Initialize() { } public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class HMACSHA512 : System.Security.Cryptography.HMAC { @@ -1726,6 +1780,12 @@ protected override void HashCore(System.ReadOnlySpan source) { } public override void Initialize() { } public static bool TryHashData(System.ReadOnlySpan key, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } protected override bool TryHashFinal(System.Span destination, out int bytesWritten) { throw null; } + public static bool Verify(byte[] key, byte[] source, byte[] hash) { throw null; } + public static bool Verify(byte[] key, System.IO.Stream source, byte[] hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.IO.Stream source, System.ReadOnlySpan hash) { throw null; } + public static bool Verify(System.ReadOnlySpan key, System.ReadOnlySpan source, System.ReadOnlySpan hash) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(byte[] key, System.IO.Stream source, byte[] hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask VerifyAsync(System.ReadOnlyMemory key, System.IO.Stream source, System.ReadOnlyMemory hash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial interface ICryptoTransform : System.IDisposable { @@ -1761,6 +1821,10 @@ public void Dispose() { } public int GetHashAndReset(System.Span destination) { throw null; } public bool TryGetCurrentHash(System.Span destination, out int bytesWritten) { throw null; } public bool TryGetHashAndReset(System.Span destination, out int bytesWritten) { throw null; } + public bool VerifyCurrentHash(byte[] hash) { throw null; } + public bool VerifyCurrentHash(System.ReadOnlySpan hash) { throw null; } + public bool VerifyHashAndReset(byte[] hash) { throw null; } + public bool VerifyHashAndReset(System.ReadOnlySpan hash) { throw null; } } public abstract partial class KeyedHashAlgorithm : System.Security.Cryptography.HashAlgorithm { diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index c55a7454e0d140..3f769b74301483 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -605,6 +605,7 @@ + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptographicOperations.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptographicOperations.cs index e98c8d9356f109..b92c6ae809423c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptographicOperations.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptographicOperations.cs @@ -756,6 +756,293 @@ public static ValueTask HmacDataAsync( return LiteHashProvider.HmacStreamAsync(hashAlgorithm.Name, key.Span, source, cancellationToken); } + /// + /// Verifies the HMAC of data. + /// + /// The algorithm used to compute the HMAC. + /// The secret key. The key can be any length. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to the size of the HMAC output. + /// -or- + /// has a that is empty. + /// + /// + /// has a that is + /// . + /// + /// + /// specifies an unknown hash algorithm. + /// + /// + /// specifies a hash algorithm not supported by the current platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool VerifyHmac( + HashAlgorithmName hashAlgorithm, + ReadOnlySpan key, + ReadOnlySpan source, + ReadOnlySpan hash) + { + int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm); + + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, hashSizeInBytes), nameof(hash)); + } + + Debug.Assert(hashAlgorithm.Name is not null); + + const int MaxStackAlloc = 64; // SHA2-512 / SHA3-512 + Span macBuffer = stackalloc byte[MaxStackAlloc]; + + if (hashSizeInBytes > MaxStackAlloc) + { + Debug.Fail($"Validated hash algorithm '{hashAlgorithm.Name}' size ({hashSizeInBytes}) exceeds stack alloc."); + throw new CryptographicException(); + } + + int written = HashProviderDispenser.OneShotHashProvider.MacData(hashAlgorithm.Name, key, source, macBuffer); + Debug.Assert(written == hashSizeInBytes); + Span mac = macBuffer.Slice(0, written); + + bool result = FixedTimeEquals(mac, hash); + ZeroMemory(mac); + return result; + } + + /// + /// + /// , , or is . + /// + /// -or- + /// + /// has a that is + /// . + /// + /// + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(hash); + + return VerifyHmac( + hashAlgorithm, + new ReadOnlySpan(key), + new ReadOnlySpan(source), + new ReadOnlySpan(hash)); + } + + /// + /// Verifies the HMAC of a stream. + /// + /// The algorithm used to compute the HMAC. + /// The secret key. The key can be any length. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to the size of the HMAC output. + /// -or- + /// has a that is empty. + /// -or- + /// does not support reading. + /// + /// + /// + /// has a that is + /// . + /// + /// -or- + /// + /// is . + /// + /// + /// + /// specifies an unknown hash algorithm. + /// + /// + /// specifies a hash algorithm not supported by the current platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm); + + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, hashSizeInBytes), nameof(hash)); + } + + CheckStream(source); + Debug.Assert(hashAlgorithm.Name is not null); + + const int MaxStackAlloc = 64; // SHA2-512 / SHA3-512 + Span macBuffer = stackalloc byte[MaxStackAlloc]; + + if (hashSizeInBytes > MaxStackAlloc) + { + Debug.Fail($"Validated hash algorithm '{hashAlgorithm.Name}' size ({hashSizeInBytes}) exceeds stack alloc."); + throw new CryptographicException(); + } + + int written = LiteHashProvider.HmacStream(hashAlgorithm.Name, key, source, macBuffer); + Debug.Assert(written == hashSizeInBytes); + Span mac = macBuffer.Slice(0, written); + + bool result = FixedTimeEquals(mac, hash); + ZeroMemory(mac); + return result; + } + + /// + /// + /// has a that is + /// . + /// + /// -or- + /// + /// , , or is . + /// + /// + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(hash); + return VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + + /// + /// Asynchronously verifies the HMAC of a stream. + /// + /// The algorithm used to compute the HMAC. + /// The secret key. The key can be any length. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to the size of the HMAC output. + /// -or- + /// has a that is empty. + /// -or- + /// does not support reading. + /// + /// + /// + /// has a that is + /// . + /// + /// -or- + /// + /// is . + /// + /// + /// + /// specifies an unknown hash algorithm. + /// + /// + /// specifies a hash algorithm not supported by the current platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyHmacAsync( + HashAlgorithmName hashAlgorithm, + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm); + + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, hashSizeInBytes), nameof(hash)); + } + + CheckStream(source); + Debug.Assert(hashAlgorithm.Name is not null); + + return VerifyHmacAsyncInner(hashAlgorithm.Name, key, source, hash, cancellationToken); + + static async ValueTask VerifyHmacAsyncInner( + string hashAlgorithm, + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + byte[] mac = new byte[hash.Length]; + + using (PinAndClear.Track(mac)) + { + int written = await LiteHashProvider.HmacStreamAsync( + hashAlgorithm, + key.Span, + source, + mac, + cancellationToken).ConfigureAwait(false); + + Debug.Assert(written == mac.Length); + return CryptographicOperations.FixedTimeEquals(mac, hash.Span); + } + } + } + + /// + /// + /// has a that is + /// . + /// + /// -or- + /// + /// , , or is . + /// + /// + /// + public static ValueTask VerifyHmacAsync( + HashAlgorithmName hashAlgorithm, + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(hash); + + return VerifyHmacAsync( + hashAlgorithm, + new ReadOnlyMemory(key), + source, + new ReadOnlyMemory(hash), + cancellationToken); + } + private static void CheckStream([NotNull] Stream source) { ArgumentNullException.ThrowIfNull(source); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACMD5.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACMD5.cs index bf8a9f76e5b6a8..5ae5785c1f9a37 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACMD5.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACMD5.cs @@ -17,6 +17,12 @@ namespace System.Security.Cryptography public class HMACMD5 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.MD5; + } + /// /// The hash size produced by the HMAC MD5 algorithm, in bits. /// @@ -336,6 +342,127 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the MD5 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + [UnsupportedOSPlatform("browser")] + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + [UnsupportedOSPlatform("browser")] + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the MD5 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + [UnsupportedOSPlatform("browser")] + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + [UnsupportedOSPlatform("browser")] + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the MD5 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + [UnsupportedOSPlatform("browser")] + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + [UnsupportedOSPlatform("browser")] + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA1.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA1.cs index 95340b5d4d1127..4a5bad265ac88e 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA1.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA1.cs @@ -18,6 +18,12 @@ namespace System.Security.Cryptography public class HMACSHA1 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.SHA1; + } + /// /// The hash size produced by the HMAC SHA1 algorithm, in bits. /// @@ -332,6 +338,121 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the SHA1 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the SHA1 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the SHA1 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA256.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA256.cs index a158ca30fb6457..988cd130da59d8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA256.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA256.cs @@ -17,6 +17,12 @@ namespace System.Security.Cryptography public class HMACSHA256 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.SHA256; + } + /// /// The hash size produced by the HMAC SHA256 algorithm, in bits. /// @@ -324,6 +330,121 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the SHA256 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the SHA256 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the SHA256 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA384.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA384.cs index 3dfbb46c2a7b6d..a8b1f6a49b91ca 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA384.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA384.cs @@ -17,6 +17,12 @@ namespace System.Security.Cryptography public class HMACSHA384 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.SHA384; + } + /// /// The hash size produced by the HMAC SHA384 algorithm, in bits. /// @@ -341,6 +347,121 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the SHA384 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the SHA384 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the SHA384 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_256.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_256.cs index 82ada4881596f1..ce2a60efabb398 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_256.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_256.cs @@ -14,6 +14,12 @@ namespace System.Security.Cryptography /// public class HMACSHA3_256 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.SHA3_256; + } + private HMACCommon _hMacCommon; internal const int BlockSize = 136; // FIPS 202 Table 3. @@ -366,6 +372,136 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the SHA3-256 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// The SHA3-256 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the SHA3-256 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// The SHA3-256 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the SHA3-256 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// The SHA3-256 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + CheckSha3Support(); + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + CheckSha3Support(); + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + /// protected override void Dispose(bool disposing) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_384.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_384.cs index 9e903f97e632d0..8de956f00def3c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_384.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_384.cs @@ -14,6 +14,12 @@ namespace System.Security.Cryptography /// public class HMACSHA3_384 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.SHA3_384; + } + private HMACCommon _hMacCommon; internal const int BlockSize = 104; // FIPS 202 Table 3. @@ -366,6 +372,136 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the SHA3-384 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// The SHA3-384 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the SHA3-384 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// The SHA3-384 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the SHA3-384 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// The SHA3-384 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + CheckSha3Support(); + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + CheckSha3Support(); + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + /// protected override void Dispose(bool disposing) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_512.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_512.cs index a50034ebecb59c..ec1d3b037f5bbe 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_512.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA3_512.cs @@ -14,6 +14,12 @@ namespace System.Security.Cryptography /// public class HMACSHA3_512 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.SHA3_512; + } + private HMACCommon _hMacCommon; internal const int BlockSize = 72; // FIPS 202 Table 3. @@ -366,6 +372,136 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the SHA3-512 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// The SHA3-512 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the SHA3-512 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// The SHA3-512 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + CheckSha3Support(); + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the SHA3-512 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// The SHA3-512 algorithm is not supported on this platform. + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + CheckSha3Support(); + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + CheckSha3Support(); + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + /// protected override void Dispose(bool disposing) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA512.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA512.cs index 8d3113f525fd60..bf8d638c0f98d8 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA512.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACSHA512.cs @@ -17,6 +17,12 @@ namespace System.Security.Cryptography public class HMACSHA512 : HMAC { + private sealed class HMACTrait : IHMACStatic + { + static int IHMACStatic.HashSizeInBytes => HashSizeInBytes; + static string IHMACStatic.HashAlgorithmName => HashAlgorithmNames.SHA512; + } + /// /// The hash size produced by the HMAC SHA512 algorithm, in bits. /// @@ -338,6 +344,121 @@ public static ValueTask HashDataAsync( cancellationToken); } + /// + /// Verifies the HMAC of data using the SHA512 algorithm. + /// + /// The HMAC key. + /// The data to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// + /// , , or is . + /// + public static bool Verify(byte[] key, byte[] source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Verifies the HMAC of a stream using the SHA512 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// if the computed HMAC of is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// , , or is . + /// + /// + public static bool Verify(byte[] key, Stream source, byte[] hash) + { + return HMACStatic.Verify(key, source, hash); + } + + /// + /// Asynchronously verifies the HMAC of a stream using the SHA512 algorithm. + /// + /// The HMAC key. + /// The stream to HMAC. + /// The HMAC to compare against. + /// + /// The token to monitor for cancellation requests. + /// The default value is . + /// + /// + /// A task that, when awaited, produces if the computed HMAC of + /// is equal to ; otherwise . + /// + /// + /// has a length not equal to . + /// -or- + /// does not support reading. + /// + /// + /// is . + /// + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + + /// + /// , , or is . + /// + /// + public static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken = default) + { + return HMACStatic.VerifyAsync(key, source, hash, cancellationToken); + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACShared.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACShared.cs new file mode 100644 index 00000000000000..52c06bbe806827 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACShared.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal interface IHMACStatic + { + internal static abstract int HashSizeInBytes { get; } + internal static abstract string HashAlgorithmName { get; } + } + + // This class acts as a single implementation of the HMAC classes that the public APIs defer to. + // The public APIs call these methods directly, so they need to behave as if they were public, + // including parameter validation and async behavior. + internal static class HMACStatic where THMAC : IHMACStatic + { + internal static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + if (hash.Length != THMAC.HashSizeInBytes) + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, THMAC.HashSizeInBytes), nameof(hash)); + + Span mac = stackalloc byte[THMAC.HashSizeInBytes]; + int written = HashProviderDispenser.OneShotHashProvider.MacData(THMAC.HashAlgorithmName, key, source, mac); + Debug.Assert(written == THMAC.HashSizeInBytes); + + bool result = CryptographicOperations.FixedTimeEquals(mac, hash); + CryptographicOperations.ZeroMemory(mac); + return result; + } + + internal static bool Verify(byte[] key, byte[] source, byte[] hash) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(hash); + + return Verify(new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + + internal static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + ArgumentNullException.ThrowIfNull(source); + + if (hash.Length != THMAC.HashSizeInBytes) + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, THMAC.HashSizeInBytes), nameof(hash)); + + if (!source.CanRead) + throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source)); + + Span mac = stackalloc byte[THMAC.HashSizeInBytes]; + int written = LiteHashProvider.HmacStream(THMAC.HashAlgorithmName, key, source, mac); + Debug.Assert(written == THMAC.HashSizeInBytes); + + bool result = CryptographicOperations.FixedTimeEquals(mac, hash); + CryptographicOperations.ZeroMemory(mac); + return result; + } + + internal static bool Verify(byte[] key, Stream source, byte[] hash) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(hash); + // source parameter check is done in called overload. + + return Verify(new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + + internal static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(source); + + if (hash.Length != THMAC.HashSizeInBytes) + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, THMAC.HashSizeInBytes), nameof(hash)); + + if (!source.CanRead) + throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source)); + + return VerifyAsyncInner(key, source, hash, cancellationToken); + + static async ValueTask VerifyAsyncInner( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + byte[] mac = new byte[THMAC.HashSizeInBytes]; + + using (PinAndClear.Track(mac)) + { + int written = await LiteHashProvider.HmacStreamAsync( + THMAC.HashAlgorithmName, + key.Span, + source, + mac, + cancellationToken).ConfigureAwait(false); + + Debug.Assert(written == THMAC.HashSizeInBytes); + return CryptographicOperations.FixedTimeEquals(mac, hash.Span); + } + } + } + + internal static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(hash); + // source parameter check is done in called overload. + + return VerifyAsync(new ReadOnlyMemory(key), source, new ReadOnlyMemory(hash), cancellationToken); + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACStatic.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACStatic.cs new file mode 100644 index 00000000000000..52c06bbe806827 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HMACStatic.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + internal interface IHMACStatic + { + internal static abstract int HashSizeInBytes { get; } + internal static abstract string HashAlgorithmName { get; } + } + + // This class acts as a single implementation of the HMAC classes that the public APIs defer to. + // The public APIs call these methods directly, so they need to behave as if they were public, + // including parameter validation and async behavior. + internal static class HMACStatic where THMAC : IHMACStatic + { + internal static bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + if (hash.Length != THMAC.HashSizeInBytes) + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, THMAC.HashSizeInBytes), nameof(hash)); + + Span mac = stackalloc byte[THMAC.HashSizeInBytes]; + int written = HashProviderDispenser.OneShotHashProvider.MacData(THMAC.HashAlgorithmName, key, source, mac); + Debug.Assert(written == THMAC.HashSizeInBytes); + + bool result = CryptographicOperations.FixedTimeEquals(mac, hash); + CryptographicOperations.ZeroMemory(mac); + return result; + } + + internal static bool Verify(byte[] key, byte[] source, byte[] hash) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(hash); + + return Verify(new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + + internal static bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + ArgumentNullException.ThrowIfNull(source); + + if (hash.Length != THMAC.HashSizeInBytes) + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, THMAC.HashSizeInBytes), nameof(hash)); + + if (!source.CanRead) + throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source)); + + Span mac = stackalloc byte[THMAC.HashSizeInBytes]; + int written = LiteHashProvider.HmacStream(THMAC.HashAlgorithmName, key, source, mac); + Debug.Assert(written == THMAC.HashSizeInBytes); + + bool result = CryptographicOperations.FixedTimeEquals(mac, hash); + CryptographicOperations.ZeroMemory(mac); + return result; + } + + internal static bool Verify(byte[] key, Stream source, byte[] hash) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(hash); + // source parameter check is done in called overload. + + return Verify(new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + + internal static ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(source); + + if (hash.Length != THMAC.HashSizeInBytes) + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, THMAC.HashSizeInBytes), nameof(hash)); + + if (!source.CanRead) + throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source)); + + return VerifyAsyncInner(key, source, hash, cancellationToken); + + static async ValueTask VerifyAsyncInner( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + byte[] mac = new byte[THMAC.HashSizeInBytes]; + + using (PinAndClear.Track(mac)) + { + int written = await LiteHashProvider.HmacStreamAsync( + THMAC.HashAlgorithmName, + key.Span, + source, + mac, + cancellationToken).ConfigureAwait(false); + + Debug.Assert(written == THMAC.HashSizeInBytes); + return CryptographicOperations.FixedTimeEquals(mac, hash.Span); + } + } + } + + internal static ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(hash); + // source parameter check is done in called overload. + + return VerifyAsync(new ReadOnlyMemory(key), source, new ReadOnlyMemory(hash), cancellationToken); + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/IncrementalHash.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/IncrementalHash.cs index 211660b3432448..05f5d7092c575a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/IncrementalHash.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/IncrementalHash.cs @@ -286,6 +286,68 @@ public IncrementalHash Clone() new IncrementalHash(_algorithmName, _hmac!.Clone()); } + /// + /// Verifies the hash or Hash-based Message Authentication Code (HMAC) for the data accumulated from prior + /// calls to the AppendData methods, without resetting the object to its initial state. + /// + /// The hash or HMAC to compare against. + /// + /// if the computed hash or HMAC is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// An error occurred during the operation. + /// The object has already been disposed. + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public bool VerifyCurrentHash(ReadOnlySpan hash) => + VerifyCore(hash, this, static (IncrementalHash ih, Span buffer) => ih.GetCurrentHashCore(buffer)); + + /// + /// + /// is . + /// + public bool VerifyCurrentHash(byte[] hash) + { + ArgumentNullException.ThrowIfNull(hash); + return VerifyCurrentHash(new ReadOnlySpan(hash)); + } + + /// + /// Verifies the hash or Hash-based Message Authentication Code (HMAC) for the data accumulated from prior + /// calls to the AppendData methods, and resets the object to its initial state. + /// + /// The hash or HMAC to compare against. + /// + /// if the computed hash or HMAC is equal to + /// ; otherwise . + /// + /// + /// has a length not equal to . + /// + /// An error occurred during the operation. + /// The object has already been disposed. + /// + /// This API performs a fixed-time comparison of the derived HMAC against a known HMAC to prevent leaking + /// timing information. + /// + public bool VerifyHashAndReset(ReadOnlySpan hash) => + VerifyCore(hash, this, static (IncrementalHash ih, Span buffer) => ih.GetHashAndResetCore(buffer)); + + /// + /// + /// is . + /// + public bool VerifyHashAndReset(byte[] hash) + { + ArgumentNullException.ThrowIfNull(hash); + return VerifyHashAndReset(new ReadOnlySpan(hash)); + } + /// /// Release all resources used by the current instance of the /// class. @@ -405,5 +467,38 @@ private static void CheckSha3Support(string hashAlgorithmName) break; } } + + private static bool VerifyCore( + ReadOnlySpan hash, + IncrementalHash ih, + Func, int> getHashCallback) + { + if (hash.Length != ih.HashLengthInBytes) + throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, ih.HashLengthInBytes), nameof(hash)); + + ObjectDisposedException.ThrowIf(ih._disposed, ih); + + const int MaxStackAlloc = 64; // SHA3-512 / SHA2-512 + Span computedBuffer = stackalloc byte[MaxStackAlloc]; + + if (ih.HashLengthInBytes > MaxStackAlloc) + { + computedBuffer = new byte[ih.HashLengthInBytes]; + } + + unsafe + { + fixed (byte* pComputedBuffer = computedBuffer) // Fixed to prevent GC moves. + { + int written = getHashCallback(ih, computedBuffer); + Debug.Assert(written == ih.HashLengthInBytes); + Span computed = computedBuffer.Slice(0, written); + + bool result = CryptographicOperations.FixedTimeEquals(hash, computed); + CryptographicOperations.ZeroMemory(computed); + return result; + } + } + } } } diff --git a/src/libraries/System.Security.Cryptography/tests/HmacMD5Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacMD5Tests.cs index 740310b5b0aa28..64736d332106d3 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacMD5Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacMD5Tests.cs @@ -92,6 +92,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACMD5.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACMD5.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACMD5.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACMD5.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACMD5.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACMD5.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACMD5.VerifyAsync(key, source, hash, cancellationToken); + } + [Fact] public void HmacMD5_Rfc2202_1() { diff --git a/src/libraries/System.Security.Cryptography/tests/HmacSha1Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacSha1Tests.cs index fcb9022b64c1fa..d473eee78db770 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacSha1Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacSha1Tests.cs @@ -91,6 +91,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACSHA1.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACSHA1.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACSHA1.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACSHA1.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACSHA1.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACSHA1.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACSHA1.VerifyAsync(key, source, hash, cancellationToken); + } + [Fact] public void HmacSha1_Byte_Constructors() { diff --git a/src/libraries/System.Security.Cryptography/tests/HmacSha256Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacSha256Tests.cs index b7826bb5dda37c..e0c246290f3ae5 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacSha256Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacSha256Tests.cs @@ -62,6 +62,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACSHA256.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACSHA256.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACSHA256.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACSHA256.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACSHA256.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACSHA256.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACSHA256.VerifyAsync(key, source, hash, cancellationToken); + } + private static byte[][] s_testMacs4231 = { null, diff --git a/src/libraries/System.Security.Cryptography/tests/HmacSha384Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacSha384Tests.cs index 1d9df93fcb0026..552d9b11388920 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacSha384Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacSha384Tests.cs @@ -62,6 +62,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACSHA384.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACSHA384.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACSHA384.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACSHA384.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACSHA384.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACSHA384.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACSHA384.VerifyAsync(key, source, hash, cancellationToken); + } + private static byte[][] s_testMacs4231 = { null, diff --git a/src/libraries/System.Security.Cryptography/tests/HmacSha3_256Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacSha3_256Tests.cs index 6d769977604715..4d574ccbcd4eca 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacSha3_256Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacSha3_256Tests.cs @@ -63,6 +63,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACSHA3_256.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACSHA3_256.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACSHA3_256.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACSHA3_256.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACSHA3_256.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACSHA3_256.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACSHA3_256.VerifyAsync(key, source, hash, cancellationToken); + } + private static readonly byte[][] s_testKeys = new byte[][] { // From: https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/HMAC_SHA3-256.pdf diff --git a/src/libraries/System.Security.Cryptography/tests/HmacSha3_384Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacSha3_384Tests.cs index e02e1ab6e01034..831b4e5a2c5e4b 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacSha3_384Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacSha3_384Tests.cs @@ -63,6 +63,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACSHA3_384.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACSHA3_384.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACSHA3_384.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACSHA3_384.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACSHA3_384.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACSHA3_384.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACSHA3_384.VerifyAsync(key, source, hash, cancellationToken); + } + private static readonly byte[][] s_testKeys = new byte[][] { // From: https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/HMAC_SHA3-384.pdf diff --git a/src/libraries/System.Security.Cryptography/tests/HmacSha3_512Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacSha3_512Tests.cs index 43971c5a45983d..5e07e95c929d6e 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacSha3_512Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacSha3_512Tests.cs @@ -63,6 +63,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACSHA3_512.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACSHA3_512.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACSHA3_512.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACSHA3_512.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACSHA3_512.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACSHA3_512.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACSHA3_512.VerifyAsync(key, source, hash, cancellationToken); + } + private static readonly byte[][] s_testKeys = new byte[][] { // From: https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/HMAC_SHA3-512.pdf diff --git a/src/libraries/System.Security.Cryptography/tests/HmacSha512Tests.cs b/src/libraries/System.Security.Cryptography/tests/HmacSha512Tests.cs index f72fa25d2742ba..859aff358a6c52 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacSha512Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacSha512Tests.cs @@ -62,6 +62,34 @@ protected override ValueTask HashDataOneShotAsync( Stream source, CancellationToken cancellationToken) => HMACSHA512.HashDataAsync(key, source, cancellationToken); + protected override bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) => + HMACSHA512.Verify(key, source, hash); + + protected override bool Verify(byte[] key, byte[] source, byte[] hash) => HMACSHA512.Verify(key, source, hash); + + protected override bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash) => + HMACSHA512.Verify(key, source, hash); + + protected override bool Verify(byte[] key, Stream source, byte[] hash) => HMACSHA512.Verify(key, source, hash); + + protected override ValueTask VerifyAsync( + ReadOnlyMemory key, + Stream source, + ReadOnlyMemory hash, + CancellationToken cancellationToken) + { + return HMACSHA512.VerifyAsync(key, source, hash, cancellationToken); + } + + protected override ValueTask VerifyAsync( + byte[] key, + Stream source, + byte[] hash, + CancellationToken cancellationToken) + { + return HMACSHA512.VerifyAsync(key, source, hash, cancellationToken); + } + private static byte[][] s_testMacs4231 = { null, diff --git a/src/libraries/System.Security.Cryptography/tests/HmacTests.cs b/src/libraries/System.Security.Cryptography/tests/HmacTests.cs index a2d649fb3d960a..adec66e71ab777 100644 --- a/src/libraries/System.Security.Cryptography/tests/HmacTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/HmacTests.cs @@ -40,6 +40,14 @@ protected HmacTests(byte[][] testKeys, byte[][] testData, byte[][] testMacs) protected abstract byte[] HashDataOneShot(byte[] key, Stream source); protected abstract int HashDataOneShot(ReadOnlySpan key, Stream source, Span destination); + protected abstract bool Verify(ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash); + protected abstract bool Verify(byte[] key, byte[] source, byte[] hash); + protected abstract bool Verify(ReadOnlySpan key, Stream source, ReadOnlySpan hash); + protected abstract bool Verify(byte[] key, Stream source, byte[] hash); + + protected virtual ValueTask VerifyAsync(ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken) => throw new NotImplementedException(); + protected virtual ValueTask VerifyAsync(byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken) => throw new NotImplementedException(); + protected abstract ValueTask HashDataOneShotAsync( ReadOnlyMemory key, Stream source, @@ -837,6 +845,575 @@ public void HashData_Stream_Allocating_Cancelled() Assert.True(waitable.IsCanceled, nameof(waitable.IsCanceled)); } + [ConditionalTheory(nameof(IsSupported))] + [InlineData(-1)] + [InlineData(1)] + public void Verify_ArgValidation_WrongHashSize(int sizeOffset) + { + byte[] key = new byte[1]; + Assert.Throws("hash", () => + Verify(key, Array.Empty(), new byte[THmacTrait.HashSizeInBytes + sizeOffset])); + Assert.Throws("hash", () => + Verify(new ReadOnlySpan(key), ReadOnlySpan.Empty, new byte[THmacTrait.HashSizeInBytes + sizeOffset])); + + Assert.Throws("hash", () => + Verify(key, UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes + sizeOffset])); + Assert.Throws("hash", () => + Verify(new ReadOnlySpan(key), UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes + sizeOffset])); + + Assert.Throws("hash", () => + VerifyAsync(key, UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes + sizeOffset], default(CancellationToken))); + Assert.Throws("hash", () => + VerifyAsync(new ReadOnlyMemory(key), UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes + sizeOffset], default(CancellationToken))); + } + + [ConditionalTheory(nameof(IsSupported))] + [InlineData(-1)] + [InlineData(1)] + public void Verify_CryptographicOperations_ArgValidation_WrongHashSize(int sizeOffset) + { + byte[] key = new byte[1]; + Assert.Throws("hash", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + key, + Array.Empty(), + new byte[THmacTrait.HashSizeInBytes + sizeOffset])); + + Assert.Throws("hash", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + new ReadOnlySpan(key), + ReadOnlySpan.Empty, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes + sizeOffset]))); + + Assert.Throws("hash", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + key, + UntouchableStream.Instance, + new byte[THmacTrait.HashSizeInBytes + sizeOffset])); + + Assert.Throws("hash", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + new ReadOnlySpan(key), + UntouchableStream.Instance, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes + sizeOffset]))); + } + + [ConditionalFact(nameof(IsSupported))] + public void Verify_CryptographicOperations_ArgValidation_Null() + { + Assert.Throws("key", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + null, + Array.Empty(), + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("source", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + Array.Empty(), + (byte[])null, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("hash", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + Array.Empty(), + Array.Empty(), + null)); + + Assert.Throws("source", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + ReadOnlySpan.Empty, + (Stream)null, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("key", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + null, + UntouchableStream.Instance, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("source", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + Array.Empty(), + (Stream)null, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("hash", () => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + Array.Empty(), + UntouchableStream.Instance, + null)); + } + + [ConditionalFact(nameof(IsSupported))] + public void Verify_CryptographicOperations_ArgValidation_HashName_Invalid() + { + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + default(HashAlgorithmName), + Array.Empty(), + Array.Empty(), + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + default(HashAlgorithmName), + ReadOnlySpan.Empty, + ReadOnlySpan.Empty, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName(""), + Array.Empty(), + Array.Empty(), + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName(""), + ReadOnlySpan.Empty, + ReadOnlySpan.Empty, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + default(HashAlgorithmName), + Array.Empty(), + UntouchableStream.Instance, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + default(HashAlgorithmName), + ReadOnlySpan.Empty, + UntouchableStream.Instance, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName(""), + Array.Empty(), + UntouchableStream.Instance, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws("hashAlgorithm", () => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName(""), + ReadOnlySpan.Empty, + UntouchableStream.Instance, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + } + + [Fact] + public void Verify_CryptographicOperations_HashName_Unknown() + { + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName("POTATO256"), + Array.Empty(), + Array.Empty(), + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName("POTATO256"), + ReadOnlySpan.Empty, + ReadOnlySpan.Empty, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName("POTATO256"), + Array.Empty(), + UntouchableStream.Instance, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + new HashAlgorithmName("POTATO256"), + ReadOnlySpan.Empty, + UntouchableStream.Instance, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + } + + [ConditionalFact(nameof(IsNotSupported))] + public void Verify_CryptographicOperations_HashName_NotSupported() + { + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + Array.Empty(), + Array.Empty(), + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + ReadOnlySpan.Empty, + ReadOnlySpan.Empty, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + Array.Empty(), + UntouchableStream.Instance, + new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws(() => + CryptographicOperations.VerifyHmac( + HashAlgorithm, + ReadOnlySpan.Empty, + UntouchableStream.Instance, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + } + + [ConditionalFact(nameof(IsSupported))] + public void Verify_ArgValidation_Null() + { + byte[] key = new byte[1]; + Assert.Throws("key", () => + Verify(null, Array.Empty(), new byte[THmacTrait.HashSizeInBytes])); + Assert.Throws("source", () => + Verify(key, (byte[])null, new byte[THmacTrait.HashSizeInBytes])); + Assert.Throws("hash", () => + Verify(key, Array.Empty(), null)); + + Assert.Throws("key", () => + Verify(null, UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes])); + Assert.Throws("source", () => + Verify(key, (Stream)null, new byte[THmacTrait.HashSizeInBytes])); + Assert.Throws("hash", () => + Verify(key, UntouchableStream.Instance, null)); + + Assert.Throws("source", () => + Verify( + new ReadOnlySpan(key), + (Stream)null, + new ReadOnlySpan(new byte[THmacTrait.HashSizeInBytes]))); + + Assert.Throws("key", () => + VerifyAsync(null, UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes], default(CancellationToken))); + Assert.Throws("source", () => + VerifyAsync(key, (Stream)null, new byte[THmacTrait.HashSizeInBytes], default(CancellationToken))); + Assert.Throws("hash", () => + VerifyAsync(key, UntouchableStream.Instance, null, default(CancellationToken))); + + Assert.Throws("source", () => + VerifyAsync( + new ReadOnlyMemory(key), + (Stream)null, + new ReadOnlyMemory(new byte[THmacTrait.HashSizeInBytes]), default(CancellationToken))); + } + + [ConditionalFact(nameof(IsSupported))] + public void Verify_Match() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId]; + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + ReadOnlySpan keySpan = key; + ReadOnlySpan dataSpan = data; + ReadOnlySpan macSpan = mac; + + // Array + AssertExtensions.TrueExpression(Verify(key, data, mac)); + + // Span + AssertExtensions.TrueExpression(Verify(keySpan, dataSpan, macSpan)); + + // Stream, arrays + AssertExtensions.TrueExpression(Verify(key, new MemoryStream(data), mac)); + + // Stream, spans + AssertExtensions.TrueExpression(Verify(keySpan, new MemoryStream(data), macSpan)); + } + } + + [ConditionalFact(nameof(IsSupported))] + public async Task VerifyAsync_Match() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId]; + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + ReadOnlyMemory keyMemory = key; + ReadOnlyMemory macMemory = mac; + + // Array + AssertExtensions.TrueExpression( + await VerifyAsync(key, new MemoryStream(data), mac, default(CancellationToken))); + + // Memory + AssertExtensions.TrueExpression( + await VerifyAsync(keyMemory, new MemoryStream(data), macMemory, default(CancellationToken))); + } + } + + [ConditionalFact(nameof(IsSupported))] + public void Verify_Mismatch() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId].AsSpan().ToArray(); + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + FlipRandomBit(mac); + ReadOnlySpan keySpan = key; + ReadOnlySpan dataSpan = data; + ReadOnlySpan macSpan = mac; + + // Array + AssertExtensions.FalseExpression(Verify(key, data, mac)); + + // Span + AssertExtensions.FalseExpression(Verify(keySpan, dataSpan, macSpan)); + + // Stream, arrays + AssertExtensions.FalseExpression(Verify(key, new MemoryStream(data), mac)); + + // Stream, spans + AssertExtensions.FalseExpression(Verify(keySpan, new MemoryStream(data), macSpan)); + } + } + + [ConditionalFact(nameof(IsSupported))] + public async Task VerifyAsync_Mismatch() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId].AsSpan().ToArray(); + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + FlipRandomBit(mac); + ReadOnlyMemory keyMemory = key; + ReadOnlyMemory macMemory = mac; + + // Array + AssertExtensions.FalseExpression( + await VerifyAsync(key, new MemoryStream(data), mac, default(CancellationToken))); + + // Memory + AssertExtensions.FalseExpression( + await VerifyAsync(keyMemory, new MemoryStream(data), macMemory, default(CancellationToken))); + } + } + + [ConditionalFact(nameof(IsSupported))] + public async Task VerifyAsync_Cancelled() + { + CancellationToken cancelledToken = new(true); + byte[] hash = new byte[THmacTrait.HashSizeInBytes]; + + ValueTask arrayVerify = VerifyAsync(Array.Empty(), Stream.Null, hash, cancelledToken); + await Assert.ThrowsAnyAsync(async () => await arrayVerify); + + ValueTask memoryVerify = VerifyAsync( + ReadOnlyMemory.Empty, + Stream.Null, + new ReadOnlyMemory(hash), + cancelledToken); + await Assert.ThrowsAnyAsync(async () => await memoryVerify); + } + + [ConditionalFact(nameof(IsSupported))] + public async Task VerifyHmacAsync_CryptographicOperations_Cancelled() + { + CancellationToken cancelledToken = new(true); + byte[] hash = new byte[THmacTrait.HashSizeInBytes]; + + + ValueTask arrayVerify = CryptographicOperations.VerifyHmacAsync( + HashAlgorithm, + Array.Empty(), + Stream.Null, + hash, + cancelledToken); + await Assert.ThrowsAnyAsync(async () => await arrayVerify); + + ValueTask memoryVerify = CryptographicOperations.VerifyHmacAsync( + HashAlgorithm, + ReadOnlyMemory.Empty, + Stream.Null, + new ReadOnlyMemory(hash), + cancelledToken); + await Assert.ThrowsAnyAsync(async () => await memoryVerify); + } + + [ConditionalFact(nameof(IsSupported))] + public void Verify_CryptographicOperations_Match() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId]; + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + ReadOnlySpan keySpan = key; + ReadOnlySpan dataSpan = data; + ReadOnlySpan macSpan = mac; + + // Array + AssertExtensions.TrueExpression(CryptographicOperations.VerifyHmac(HashAlgorithm, key, data, mac)); + + // Span + AssertExtensions.TrueExpression(CryptographicOperations.VerifyHmac(HashAlgorithm, keySpan, dataSpan, macSpan)); + + // Stream, arrays + AssertExtensions.TrueExpression(Verify(key, new MemoryStream(data), mac)); + + // Stream, spans + AssertExtensions.TrueExpression(Verify(keySpan, new MemoryStream(data), macSpan)); + } + } + + [ConditionalFact(nameof(IsSupported))] + public void Verify_CryptographicOperations_Mismatch() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId].AsSpan().ToArray(); + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + FlipRandomBit(mac); + ReadOnlySpan keySpan = key; + ReadOnlySpan dataSpan = data; + ReadOnlySpan macSpan = mac; + + // Array + AssertExtensions.FalseExpression(CryptographicOperations.VerifyHmac(HashAlgorithm, key, data, mac)); + + // Span + AssertExtensions.FalseExpression(CryptographicOperations.VerifyHmac(HashAlgorithm, keySpan, dataSpan, macSpan)); + + // Stream, arrays + AssertExtensions.FalseExpression(Verify(key, new MemoryStream(data), mac)); + + // Stream, spans + AssertExtensions.FalseExpression(Verify(keySpan, new MemoryStream(data), macSpan)); + } + } + + [ConditionalFact(nameof(IsSupported))] + public async Task VerifyAsync_CryptographicOperations_Match() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId]; + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + ReadOnlyMemory keyMemory = key; + ReadOnlyMemory macMemory = mac; + + // Stream, arrays + AssertExtensions.TrueExpression( + await CryptographicOperations.VerifyHmacAsync(HashAlgorithm, key, new MemoryStream(data), mac)); + + // Stream, memory + AssertExtensions.TrueExpression( + await CryptographicOperations.VerifyHmacAsync(HashAlgorithm, keyMemory, new MemoryStream(data), macMemory)); + } + } + + [ConditionalFact(nameof(IsSupported))] + public async Task VerifyAsync_CryptographicOperations_Mismatch() + { + for (int caseId = 1; caseId < _testKeys.Length; caseId++) + { + byte[] key = _testKeys[caseId]; + byte[] data = _testData[caseId]; + byte[] mac = _testMacs[caseId].AsSpan().ToArray(); + + if (mac.Length != THmacTrait.HashSizeInBytes) + { + // Some test vectors are truncated MACs. Skip them since Verify does not support truncated values. + continue; + } + + FlipRandomBit(mac); + ReadOnlyMemory keyMemory = key; + ReadOnlyMemory macMemory = mac; + + // Stream, arrays + AssertExtensions.FalseExpression( + await CryptographicOperations.VerifyHmacAsync( + HashAlgorithm, + key, + new MemoryStream(data), + mac)); + + // Stream, spans + AssertExtensions.FalseExpression( + await CryptographicOperations.VerifyHmacAsync( + HashAlgorithm, + new ReadOnlyMemory(key), + new MemoryStream(data), + new ReadOnlyMemory(mac))); + } + } + [ConditionalFact(nameof(IsNotSupported))] public void Ctor_NotSupported() { @@ -884,6 +1461,37 @@ await Assert.ThrowsAsync(async () => Assert.Throws(() => CryptographicOperations.HmacDataAsync(HashAlgorithm, key, Stream.Null, buffer)); } + + [ConditionalFact(nameof(IsNotSupported))] + public void Verify_NotSupported() + { + byte[] key = new byte[1]; + Assert.Throws(() => + Verify(key, Array.Empty(), new byte[THmacTrait.HashSizeInBytes])); + Assert.Throws(() => + Verify(new ReadOnlySpan(key), ReadOnlySpan.Empty, new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws(() => + Verify(key, UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes])); + Assert.Throws(() => + Verify(new ReadOnlySpan(key), UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes])); + + Assert.Throws(() => + VerifyAsync(key, UntouchableStream.Instance, new byte[THmacTrait.HashSizeInBytes], default(CancellationToken))); + + Assert.Throws(() => + VerifyAsync( + new ReadOnlyMemory(key), + UntouchableStream.Instance, + new byte[THmacTrait.HashSizeInBytes], + default(CancellationToken))); + } + + private static void FlipRandomBit(Span input) + { + int index = Random.Shared.Next(0, input.Length); + input[index] = (byte)(input[index] ^ 0b_10000000); + } } public interface IHmacTrait diff --git a/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs b/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs index 80c50b599b89a9..df1973c3294506 100644 --- a/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/IncrementalHashTests.cs @@ -554,10 +554,11 @@ public static void VerifyTrivialHMAC_Span(HMAC referenceAlgorithm, HashAlgorithm public static void Dispose_HashAlgorithm_ThrowsException(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) { referenceAlgorithm.Dispose(); - var incrementalHash = IncrementalHash.CreateHash(hashAlgorithm); + IncrementalHash incrementalHash = IncrementalHash.CreateHash(hashAlgorithm); incrementalHash.Dispose(); byte[] tmpDest = new byte[1]; + byte[] hash = new byte[incrementalHash.HashLengthInBytes]; Assert.Throws(() => incrementalHash.AppendData(tmpDest)); Assert.Throws(() => incrementalHash.AppendData(tmpDest, 0, 0)); @@ -572,6 +573,11 @@ public static void Dispose_HashAlgorithm_ThrowsException(HashAlgorithm reference Assert.Throws(() => incrementalHash.TryGetCurrentHash(tmpDest, out int _)); Assert.Throws(() => incrementalHash.Clone()); + + Assert.Throws(() => incrementalHash.VerifyHashAndReset(hash)); + Assert.Throws(() => incrementalHash.VerifyHashAndReset(new ReadOnlySpan(hash))); + Assert.Throws(() => incrementalHash.VerifyCurrentHash(hash)); + Assert.Throws(() => incrementalHash.VerifyCurrentHash(new ReadOnlySpan(hash))); } [Theory] @@ -583,6 +589,7 @@ public static void Dispose_HMAC_ThrowsException(HMAC referenceAlgorithm, HashAlg incrementalHash.Dispose(); byte[] tmpDest = new byte[1]; + byte[] hash = new byte[incrementalHash.HashLengthInBytes]; Assert.Throws(() => incrementalHash.AppendData(tmpDest)); Assert.Throws(() => incrementalHash.AppendData(tmpDest, 0, 0)); @@ -597,6 +604,11 @@ public static void Dispose_HMAC_ThrowsException(HMAC referenceAlgorithm, HashAlg Assert.Throws(() => incrementalHash.TryGetCurrentHash(tmpDest, out int _)); Assert.Throws(() => incrementalHash.Clone()); + + Assert.Throws(() => incrementalHash.VerifyHashAndReset(hash)); + Assert.Throws(() => incrementalHash.VerifyHashAndReset(new ReadOnlySpan(hash))); + Assert.Throws(() => incrementalHash.VerifyCurrentHash(hash)); + Assert.Throws(() => incrementalHash.VerifyCurrentHash(new ReadOnlySpan(hash))); } [Theory] @@ -705,6 +717,247 @@ public static void VerifyBounds_GetHashAndReset_Hash(HashAlgorithm referenceAlgo } } + [Theory] + [MemberData(nameof(GetHashAlgorithms))] + public static void VerifyHashAndReset_ArgValidation_Hash(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + referenceAlgorithm.Dispose(); + using IncrementalHash incremental = IncrementalHash.CreateHash(hashAlgorithm); + byte[] tooShort = new byte[incremental.HashLengthInBytes - 1]; + byte[] tooLong = new byte[incremental.HashLengthInBytes + 1]; + Assert.Throws("hash", () => incremental.VerifyHashAndReset(tooShort)); + Assert.Throws("hash", () => incremental.VerifyHashAndReset(tooLong)); + Assert.Throws("hash", () => incremental.VerifyHashAndReset(new ReadOnlySpan(tooShort))); + Assert.Throws("hash", () => incremental.VerifyHashAndReset(new ReadOnlySpan(tooLong))); + + Assert.Throws("hash", () => incremental.VerifyHashAndReset((byte[])null)); + } + + [Theory] + [MemberData(nameof(GetHashAlgorithms))] + public static void VerifyCurrentHash_ArgValidation_Hash(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + referenceAlgorithm.Dispose(); + using IncrementalHash incremental = IncrementalHash.CreateHash(hashAlgorithm); + byte[] tooShort = new byte[incremental.HashLengthInBytes - 1]; + byte[] tooLong = new byte[incremental.HashLengthInBytes + 1]; + Assert.Throws("hash", () => incremental.VerifyCurrentHash(tooShort)); + Assert.Throws("hash", () => incremental.VerifyCurrentHash(tooLong)); + Assert.Throws("hash", () => incremental.VerifyCurrentHash(new ReadOnlySpan(tooShort))); + Assert.Throws("hash", () => incremental.VerifyCurrentHash(new ReadOnlySpan(tooLong))); + + Assert.Throws("hash", () => incremental.VerifyCurrentHash((byte[])null)); + } + + [Theory] + [MemberData(nameof(GetHashAlgorithms))] + public static void VerifyCurrentHash_Hash_Verifies(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHash(hashAlgorithm)) + using (referenceAlgorithm) + { + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + incremental.AppendData(s_inputBytes); + + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(referenceHash)); + // Verifies previous did not reset the state. + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(referenceHash)); + + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(new ReadOnlySpan(referenceHash))); + // Verifies previous did not reset the state. + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHMACs))] + [SkipOnPlatform(TestPlatforms.Android, "Android doesn't support cloning the current state for HMAC, so it doesn't support GetCurrentHash.")] + public static void VerifyCurrentHash_HMAC_Verifies(HMAC referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHMAC(hashAlgorithm, s_hmacKey)) + using (referenceAlgorithm) + { + referenceAlgorithm.Key = s_hmacKey; + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + incremental.AppendData(s_inputBytes); + + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(referenceHash)); + // Verifies previous did not reset the state. + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(referenceHash)); + + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(new ReadOnlySpan(referenceHash))); + // Verifies previous did not reset the state. + AssertExtensions.TrueExpression(incremental.VerifyCurrentHash(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHashAlgorithms))] + public static void VerifyHashAndReset_Hash_Verifies(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHash(hashAlgorithm)) + using (referenceAlgorithm) + { + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + + incremental.AppendData(s_inputBytes); + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); // Verifies previous call reset hash. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + + incremental.AppendData(s_inputBytes); // Verifies previous call reset hash. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHMACs))] + public static void VerifyHashAndReset_HMAC_Verifies(HMAC referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHMAC(hashAlgorithm, s_hmacKey)) + using (referenceAlgorithm) + { + referenceAlgorithm.Key = s_hmacKey; + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + + incremental.AppendData(s_inputBytes); + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); // Verifies previous call reset hash. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + + incremental.AppendData(s_inputBytes); // Verifies previous call reset hash. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHashAlgorithms))] + public static void VerifyCurrentHash_Hash_DoesNotVerify(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHash(hashAlgorithm)) + using (referenceAlgorithm) + { + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + FlipRandomBit(referenceHash); + + incremental.AppendData(s_inputBytes); + + AssertExtensions.FalseExpression(incremental.VerifyCurrentHash(referenceHash)); + AssertExtensions.FalseExpression(incremental.VerifyCurrentHash(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHMACs))] + [SkipOnPlatform(TestPlatforms.Android, "Android doesn't support cloning the current state for HMAC, so it doesn't support GetCurrentHash.")] + public static void VerifyCurrentHash_HMAC_DoesNotVerify(HMAC referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHMAC(hashAlgorithm, s_hmacKey)) + using (referenceAlgorithm) + { + referenceAlgorithm.Key = s_hmacKey; + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + FlipRandomBit(referenceHash); + + incremental.AppendData(s_inputBytes); + + AssertExtensions.FalseExpression(incremental.VerifyCurrentHash(referenceHash)); + AssertExtensions.FalseExpression(incremental.VerifyCurrentHash(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHashAlgorithms))] + public static void VerifyHashAndReset_Hash_DoesNotVerify(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHash(hashAlgorithm)) + using (referenceAlgorithm) + { + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + FlipRandomBit(referenceHash); + + incremental.AppendData(s_inputBytes); + AssertExtensions.FalseExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); + AssertExtensions.FalseExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHashAlgorithms))] + public static void VerifyHashAndReset_Hash_InvalidSizeDoesNotReset(HashAlgorithm referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHash(hashAlgorithm)) + using (referenceAlgorithm) + { + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + + incremental.AppendData(s_inputBytes); + Assert.Throws("hash", () => incremental.VerifyHashAndReset(Array.Empty())); + + // Verify previous throw should not have reset, so appended bytes should still verify. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); + Assert.Throws("hash", () => incremental.VerifyHashAndReset(ReadOnlySpan.Empty)); + + // Verify previous throw should not have reset, so appended bytes should still verify. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHMACs))] + public static void VerifyHashAndReset_HMAC_DoesNotVerify(HMAC referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHMAC(hashAlgorithm, s_hmacKey)) + using (referenceAlgorithm) + { + referenceAlgorithm.Key = s_hmacKey; + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + FlipRandomBit(referenceHash); + + incremental.AppendData(s_inputBytes); + AssertExtensions.FalseExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); + AssertExtensions.FalseExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + } + } + + [Theory] + [MemberData(nameof(GetHMACs))] + public static void VerifyHashAndReset_HMAC_InvalidSizeDoesNotReset(HMAC referenceAlgorithm, HashAlgorithmName hashAlgorithm) + { + using (IncrementalHash incremental = IncrementalHash.CreateHMAC(hashAlgorithm, s_hmacKey)) + using (referenceAlgorithm) + { + referenceAlgorithm.Key = s_hmacKey; + byte[] referenceHash = referenceAlgorithm.ComputeHash(s_inputBytes); + + incremental.AppendData(s_inputBytes); + Assert.Throws("hash", () => incremental.VerifyHashAndReset(Array.Empty())); + + // Verify previous throw should not have reset, so appended bytes should still verify. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(referenceHash)); + + incremental.AppendData(s_inputBytes); + Assert.Throws("hash", () => incremental.VerifyHashAndReset(ReadOnlySpan.Empty)); + + // Verify previous throw should not have reset, so appended bytes should still verify. + AssertExtensions.TrueExpression(incremental.VerifyHashAndReset(new ReadOnlySpan(referenceHash))); + } + } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public static void Hash_GetHashAndReset_ConcurrentUseDoesNotCrashProcess() { @@ -927,5 +1180,11 @@ private static void SequenceFill(Span span) span[i] = (byte)i; } } + + private static void FlipRandomBit(Span input) + { + int index = Random.Shared.Next(0, input.Length); + input[index] = (byte)(input[index] ^ 0b_10000000); + } } }