File: System\Security\Cryptography\KmacStatic.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 IKmacStatic
    {
        internal static abstract string HashAlgorithmName { get; }
        internal static abstract bool IsSupported { get; }
        internal static abstract bool IsXof { get; }
    }
 
    internal static class KmacStatic<TKmac> where TKmac : IKmacStatic
    {
        // KMAC-256 with a 512-bit capacity is the biggest "typical" use of KMAC (See 8.4.2 from SP-800-185)
        private const int MaxStackKmacSize = 64;
 
        internal static bool Verify(
            ReadOnlySpan<byte> key,
            ReadOnlySpan<byte> source,
            ReadOnlySpan<byte> hash,
            ReadOnlySpan<byte> customizationString)
        {
            ThrowIfEmptyHash(hash);
            ThrowIfNotSupported();
 
            return VerifyCore(
                key,
                source,
                hash,
                customizationString,
                static (key, source, customizationString, buffer) =>
                {
                    HashProviderDispenser.OneShotHashProvider.KmacData(
                        TKmac.HashAlgorithmName,
                        key,
                        source,
                        buffer,
                        customizationString,
                        TKmac.IsXof);
                });
        }
 
        internal static bool Verify(byte[] key, byte[] source, byte[] hash, byte[]? customizationString)
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(source);
            ArgumentNullException.ThrowIfNull(hash);
 
            return Verify(
                new ReadOnlySpan<byte>(key),
                new ReadOnlySpan<byte>(source),
                new ReadOnlySpan<byte>(hash),
                new ReadOnlySpan<byte>(customizationString)); // null to empty conversion is expected.
        }
 
        internal static bool Verify(
            ReadOnlySpan<byte> key,
            Stream source,
            ReadOnlySpan<byte> hash,
            ReadOnlySpan<byte> customizationString)
        {
            ArgumentNullException.ThrowIfNull(source);
            ThrowIfEmptyHash(hash);
            ThrowIfStreamUnreadable(source);
            ThrowIfNotSupported();
 
            return VerifyCore(
                key,
                source,
                hash,
                customizationString,
                static (key, source, customizationString, buffer) =>
                {
                    LiteHashProvider.KmacStream(
                        TKmac.HashAlgorithmName,
                        key,
                        customizationString,
                        source,
                        TKmac.IsXof,
                        buffer);
                });
        }
 
        internal static bool Verify(byte[] key, Stream source, byte[] hash, byte[]? customizationString)
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(hash);
 
            return Verify(
                new ReadOnlySpan<byte>(key),
                source,
                new ReadOnlySpan<byte>(hash),
                new ReadOnlySpan<byte>(customizationString)); // null to empty conversion is expected.
        }
 
        internal static ValueTask<bool> VerifyAsync(
            ReadOnlyMemory<byte> key,
            Stream source,
            ReadOnlyMemory<byte> hash,
            ReadOnlyMemory<byte> customizationString,
            CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(source);
            ThrowIfEmptyHash(hash);
            ThrowIfStreamUnreadable(source);
            ThrowIfNotSupported();
 
            return VerifyAsyncInner(key, source, hash, customizationString, cancellationToken);
 
            static async ValueTask<bool> VerifyAsyncInner(
                ReadOnlyMemory<byte> key,
                Stream source,
                ReadOnlyMemory<byte> hash,
                ReadOnlyMemory<byte> customizationString,
                CancellationToken cancellationToken)
            {
                byte[] mac = new byte[hash.Length];
 
                using (PinAndClear.Track(mac))
                {
                    await LiteHashProvider.KmacStreamAsync(
                        TKmac.HashAlgorithmName,
                        key.Span,
                        source,
                        TKmac.IsXof,
                        mac,
                        customizationString.Span,
                        cancellationToken).ConfigureAwait(false);
 
                    return CryptographicOperations.FixedTimeEquals(mac, hash.Span);
                }
            }
        }
 
        internal static ValueTask<bool> VerifyAsync(
            byte[] key,
            Stream source,
            byte[] hash,
            byte[]? customizationString,
            CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(hash);
 
            return VerifyAsync(
                new ReadOnlyMemory<byte>(key),
                source,
                new ReadOnlyMemory<byte>(hash),
                new ReadOnlyMemory<byte>(customizationString), // null to empty conversion is expected.
                cancellationToken);
        }
 
        private static bool VerifyCore<TSource>(
            ReadOnlySpan<byte> key,
            TSource source,
            ReadOnlySpan<byte> hash,
            ReadOnlySpan<byte> customizationString,
            Action<ReadOnlySpan<byte>, TSource, ReadOnlySpan<byte>, Span<byte>> callback)
            where TSource : allows ref struct
        {
            Span<byte> hashBuffer = stackalloc byte[MaxStackKmacSize];
 
            if (hash.Length > MaxStackKmacSize)
            {
                hashBuffer = new byte[hash.Length];
            }
            else
            {
                hashBuffer = hashBuffer.Slice(0, hash.Length);
            }
 
            unsafe
            {
                fixed (byte* pHashBuffer = hashBuffer)
                {
                    callback(key, source, customizationString, hashBuffer);
                    bool result = CryptographicOperations.FixedTimeEquals(hashBuffer, hash);
                    CryptographicOperations.ZeroMemory(hashBuffer);
                    return result;
                }
            }
        }
 
        private static void ThrowIfEmptyHash(ReadOnlySpan<byte> hash)
        {
            if (hash.IsEmpty)
                throw new ArgumentException(SR.Argument_HashEmpty, nameof(hash));
        }
 
        private static void ThrowIfEmptyHash(ReadOnlyMemory<byte> hash)
        {
            if (hash.IsEmpty)
                throw new ArgumentException(SR.Argument_HashEmpty, nameof(hash));
        }
 
        private static void ThrowIfStreamUnreadable(Stream source)
        {
            if (!source.CanRead)
                throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source));
        }
 
        private static void ThrowIfNotSupported()
        {
            if (!TKmac.IsSupported)
                throw new PlatformNotSupportedException();
        }
    }
}