File: System\Security\Cryptography\HMACStatic.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// 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; }
        internal static abstract bool IsSupported { 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<THMAC> where THMAC : IHMACStatic
    {
        internal static bool Verify(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, ReadOnlySpan<byte> hash)
        {
            if (hash.Length != THMAC.HashSizeInBytes)
                throw new ArgumentException(SR.Format(SR.Argument_HashImprecise, THMAC.HashSizeInBytes), nameof(hash));
 
            CheckPlatformSupport();
 
            Span<byte> 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 byte[] HashData(byte[] key, byte[] source)
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(source);
 
            return HashData(new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source));
        }
 
        internal static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source)
        {
            CheckPlatformSupport();
            byte[] buffer = new byte[THMAC.HashSizeInBytes];
 
            int written = HashData(key, source, buffer.AsSpan());
            Debug.Assert(written == buffer.Length);
 
            return buffer;
        }
 
        internal static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination)
        {
            if (!TryHashData(key, source, destination, out int bytesWritten))
            {
                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
            }
 
            return bytesWritten;
        }
 
        internal static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
        {
            CheckPlatformSupport();
 
            if (destination.Length < THMAC.HashSizeInBytes)
            {
                bytesWritten = 0;
                return false;
            }
 
            bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(THMAC.HashAlgorithmName, key, source, destination);
            Debug.Assert(bytesWritten == THMAC.HashSizeInBytes);
 
            return true;
        }
 
        internal static int HashData(ReadOnlySpan<byte> key, Stream source, Span<byte> destination)
        {
            ArgumentNullException.ThrowIfNull(source);
 
            if (destination.Length < THMAC.HashSizeInBytes)
                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
 
            if (!source.CanRead)
                throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source));
 
            CheckPlatformSupport();
            return LiteHashProvider.HmacStream(THMAC.HashAlgorithmName, key, source, destination);
        }
 
        internal static byte[] HashData(ReadOnlySpan<byte> key, Stream source)
        {
            ArgumentNullException.ThrowIfNull(source);
 
            if (!source.CanRead)
                throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source));
 
            CheckPlatformSupport();
            return LiteHashProvider.HmacStream(THMAC.HashAlgorithmName, THMAC.HashSizeInBytes, key, source);
        }
 
        internal static byte[] HashData(byte[] key, Stream source)
        {
            ArgumentNullException.ThrowIfNull(key);
 
            return HashData(new ReadOnlySpan<byte>(key), source);
        }
 
        internal static ValueTask<byte[]> HashDataAsync(
            ReadOnlyMemory<byte> key,
            Stream source,
            CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(source);
 
            if (!source.CanRead)
                throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source));
 
            CheckPlatformSupport();
            return LiteHashProvider.HmacStreamAsync(THMAC.HashAlgorithmName, key.Span, source, cancellationToken);
        }
 
        internal static ValueTask<byte[]> HashDataAsync(byte[] key, Stream source, CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(key);
 
            return HashDataAsync(new ReadOnlyMemory<byte>(key), source, cancellationToken);
        }
 
        internal static ValueTask<int> HashDataAsync(
            ReadOnlyMemory<byte> key,
            Stream source,
            Memory<byte> destination,
            CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(source);
 
            if (destination.Length < THMAC.HashSizeInBytes)
                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
 
            if (!source.CanRead)
                throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source));
 
            CheckPlatformSupport();
            return LiteHashProvider.HmacStreamAsync(
                THMAC.HashAlgorithmName,
                key.Span,
                source,
                destination,
                cancellationToken);
        }
 
        internal static bool Verify(byte[] key, byte[] source, byte[] hash)
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(source);
            ArgumentNullException.ThrowIfNull(hash);
 
            return Verify(new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source), new ReadOnlySpan<byte>(hash));
        }
 
        internal static bool Verify(ReadOnlySpan<byte> key, Stream source, ReadOnlySpan<byte> 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));
 
            CheckPlatformSupport();
            Span<byte> 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<byte>(key), source, new ReadOnlySpan<byte>(hash));
        }
 
        internal static ValueTask<bool> VerifyAsync(
            ReadOnlyMemory<byte> key,
            Stream source,
            ReadOnlyMemory<byte> 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));
 
            CheckPlatformSupport();
            return VerifyAsyncInner(key, source, hash, cancellationToken);
 
            static async ValueTask<bool> VerifyAsyncInner(
                ReadOnlyMemory<byte> key,
                Stream source,
                ReadOnlyMemory<byte> 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<bool> 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<byte>(key), source, new ReadOnlyMemory<byte>(hash), cancellationToken);
        }
 
        internal static void CheckPlatformSupport()
        {
            if (!THMAC.IsSupported)
                throw new PlatformNotSupportedException();
        }
    }
}