File: System\Security\Cryptography\LiteHash.Windows.cs
Web Access
Project: src\src\runtime\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.Diagnostics.CodeAnalysis;
using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;
using BCryptCreateHashFlags = Interop.BCrypt.BCryptCreateHashFlags;
using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags;
using NTSTATUS = Interop.BCrypt.NTSTATUS;

namespace System.Security.Cryptography
{
    internal static partial class LiteHashProvider
    {
        private static LiteHash CreateHash(string hashAlgorithmId)
        {
            return new LiteHash(hashAlgorithmId);
        }

        private static LiteHmac CreateHmac(string hashAlgorithmId, ReadOnlySpan<byte> key)
        {
            return new LiteHmac(hashAlgorithmId, key);
        }

        internal static LiteXof CreateXof(string hashAlgorithmId)
        {
            return new LiteXof(hashAlgorithmId);
        }

        internal static LiteKmac CreateKmac(string algorithmId, ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString, bool xof)
        {
            return new LiteKmac(algorithmId, key, customizationString, xof);
        }
    }

    internal readonly struct LiteKmac : ILiteHash
    {
        private readonly SafeBCryptHashHandle _hashHandle;
        private readonly int _finishFlags;

        private const string BCRYPT_CUSTOMIZATION_STRING = "CustomizationString";
        private const int BCRYPT_HASH_DONT_RESET_FLAG = 0x00000001;

        internal LiteKmac(string algorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> customizationString, bool xof)
        {
            _finishFlags = xof ? BCRYPT_HASH_DONT_RESET_FLAG : 0;
            nuint algorithmHandle = algorithm switch
            {
                HashAlgorithmNames.KMAC128 => (nuint)Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_KMAC128_ALG_HANDLE,
                HashAlgorithmNames.KMAC256 => (nuint)Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_KMAC256_ALG_HANDLE,
                _ => throw FailThrow(algorithm),
            };

            NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(
                algorithmHandle,
                out SafeBCryptHashHandle hashHandle,
                pbHashObject: IntPtr.Zero,
                cbHashObject: 0,
                key,
                key.Length,
                BCryptCreateHashFlags.None);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                hashHandle.Dispose();
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }

            if (!customizationString.IsEmpty)
            {
                ntStatus = Internal.NativeCrypto.Cng.Interop.BCryptSetProperty(
                    hashHandle,
                    BCRYPT_CUSTOMIZATION_STRING,
                    customizationString,
                    customizationString.Length,
                    0);

                if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                {
                    hashHandle.Dispose();
                    throw Interop.BCrypt.CreateCryptographicException(ntStatus);
                }
            }

            _hashHandle = hashHandle;

            static Exception FailThrow(string algorithm)
            {
                Debug.Fail($"Unexpected hash algorithm name '{algorithm}'.");
                return new CryptographicException();
            }
        }

        private LiteKmac(SafeBCryptHashHandle hashHandle, int finishFlags)
        {
            _hashHandle = hashHandle;
            _finishFlags = finishFlags;
        }

        public int HashSizeInBytes
        {
            get
            {
                Debug.Fail("Unexpectedly asked for the hash size of KMAC.");
                throw new CryptographicException();
            }
        }

        public unsafe void Reset()
        {
            // Reset only does something meaningful in XOF mode. In non-XOF mode, Finalize always
            // does a reset.
            if ((_finishFlags & BCRYPT_HASH_DONT_RESET_FLAG) == BCRYPT_HASH_DONT_RESET_FLAG)
            {
                Span<byte> buffer = stackalloc byte[1];
                CheckStatus(Interop.BCrypt.BCryptFinishHash(_hashHandle, buffer, 0, dwFlags: 0));
            }
        }

        public void Append(ReadOnlySpan<byte> data)
        {
            if (data.IsEmpty)
            {
                return;
            }

            CheckStatus(Interop.BCrypt.BCryptHashData(_hashHandle, data, data.Length, dwFlags: 0));
        }

        public unsafe int Current(Span<byte> destination)
        {
            fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination))
            {
                using (SafeBCryptHashHandle dup = Interop.BCrypt.BCryptDuplicateHash(_hashHandle))
                {
                    CheckStatus(Interop.BCrypt.BCryptFinishHash(dup, pDestination, destination.Length, _finishFlags));
                }
            }

            return destination.Length;
        }

        public unsafe int Finalize(Span<byte> destination)
        {
            fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination))
            {
                CheckStatus(Interop.BCrypt.BCryptFinishHash(_hashHandle, pDestination, destination.Length, _finishFlags));
            }

            return destination.Length;
        }

        public LiteKmac Clone()
        {
            SafeBCryptHashHandle clone = Interop.BCrypt.BCryptDuplicateHash(_hashHandle);
            return new LiteKmac(clone, _finishFlags);
        }

        public void Dispose()
        {
            _hashHandle.Dispose();
        }

        private static void CheckStatus(NTSTATUS status)
        {
            if (status != NTSTATUS.STATUS_SUCCESS)
            {
                throw Interop.BCrypt.CreateCryptographicException(status);
            }
        }
    }

    internal readonly struct LiteXof : ILiteHash
    {
        private const int BCRYPT_HASH_DONT_RESET_FLAG = 0x00000001;
        private readonly nuint _algorithm;
        private readonly SafeBCryptHashHandle _hashHandle;

        internal LiteXof(string algorithm)
        {
            _algorithm = algorithm switch
            {
                HashAlgorithmNames.CSHAKE128 => (nuint)Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_CSHAKE128_ALG_HANDLE,
                HashAlgorithmNames.CSHAKE256 => (nuint)Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_CSHAKE256_ALG_HANDLE,
                _ => throw FailThrow(algorithm),
            };

            SafeBCryptHashHandle hashHandle;

            NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(
                _algorithm,
                out hashHandle,
                pbHashObject: IntPtr.Zero,
                cbHashObject: 0,
                secret: ReadOnlySpan<byte>.Empty,
                cbSecret: 0,
                BCryptCreateHashFlags.None);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                hashHandle.Dispose();
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }

            _hashHandle = hashHandle;

            static Exception FailThrow(string algorithm)
            {
                Debug.Fail($"Unexpected hash algorithm name '{algorithm}'.");
                return new CryptographicException();
            }
        }

        private LiteXof(SafeBCryptHashHandle hashHandle, nuint algorithm)
        {
            _algorithm = algorithm;
            _hashHandle = hashHandle;
        }

        public int HashSizeInBytes
        {
            get
            {
                Debug.Fail("Unexpectedly asked for the hash size of a XOF.");
                throw new CryptographicException();
            }
        }

        public void Append(ReadOnlySpan<byte> data)
        {
            if (data.IsEmpty)
            {
                return;
            }

            NTSTATUS ntStatus = Interop.BCrypt.BCryptHashData(_hashHandle, data, data.Length, dwFlags: 0);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }
        }

        public unsafe int Finalize(Span<byte> destination)
        {
            fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination))
            {
                NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hashHandle, pDestination, destination.Length, dwFlags: 0);

                if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                {
                    throw Interop.BCrypt.CreateCryptographicException(ntStatus);
                }

                return destination.Length;
            }
        }

        // Windows's Finalize always does a reset.
        public int FinalizeAndReset(Span<byte> destination) => Finalize(destination);

        public void Reset() => Finalize(Span<byte>.Empty);

        public unsafe void Current(Span<byte> destination)
        {
            using (SafeBCryptHashHandle tmpHash = Interop.BCrypt.BCryptDuplicateHash(_hashHandle))
            fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination))
            {
                NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(tmpHash, pDestination, destination.Length, dwFlags: 0);

                if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                {
                    throw Interop.BCrypt.CreateCryptographicException(ntStatus);
                }
            }
        }

        public LiteXof Clone()
        {
            SafeBCryptHashHandle clone = Interop.BCrypt.BCryptDuplicateHash(_hashHandle);
            return new LiteXof(clone, _algorithm);
        }

        public unsafe void Read(Span<byte> destination)
        {
            fixed (byte* pDestination = &Helpers.GetNonNullPinnableReference(destination))
            {
                NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hashHandle, pDestination, destination.Length, dwFlags: BCRYPT_HASH_DONT_RESET_FLAG);

                if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                {
                    throw Interop.BCrypt.CreateCryptographicException(ntStatus);
                }
            }
        }

        public void Dispose()
        {
            _hashHandle.Dispose();
        }
    }

    internal readonly struct LiteHash : ILiteHash
    {
        private readonly SafeBCryptHashHandle _hashHandle;
        private readonly int _hashSizeInBytes;

        public int HashSizeInBytes => _hashSizeInBytes;

        internal LiteHash(string algorithm)
        {

            BCryptOpenAlgorithmProviderFlags algorithmFlags =
                BCryptOpenAlgorithmProviderFlags.None;

            // This is a shared handle, do not put this in a using.
            SafeBCryptAlgorithmHandle algorithmHandle = Interop.BCrypt.BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle(
                algorithm,
                algorithmFlags,
                out _hashSizeInBytes);

            SafeBCryptHashHandle hashHandle;

            NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(
                algorithmHandle,
                out hashHandle,
                pbHashObject: IntPtr.Zero,
                cbHashObject: 0,
                secret: ReadOnlySpan<byte>.Empty,
                cbSecret: 0,
                BCryptCreateHashFlags.None);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                hashHandle.Dispose();
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }

            _hashHandle = hashHandle;
        }

        public void Append(ReadOnlySpan<byte> data)
        {
            if (data.IsEmpty)
            {
                return;
            }

            NTSTATUS ntStatus = Interop.BCrypt.BCryptHashData(_hashHandle, data, data.Length, dwFlags: 0);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }
        }

        public int Finalize(Span<byte> destination)
        {
            Debug.Assert(destination.Length >= _hashSizeInBytes, $"{destination.Length} >= {_hashSizeInBytes}");

            NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hashHandle, destination, _hashSizeInBytes, dwFlags: 0);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }

            return _hashSizeInBytes;
        }

        public void Dispose()
        {
            _hashHandle.Dispose();
        }
    }

    internal readonly struct LiteHmac : ILiteHash
    {
        private readonly SafeBCryptHashHandle _hashHandle;
        private readonly int _hashSizeInBytes;

        public int HashSizeInBytes => _hashSizeInBytes;

        internal LiteHmac(string algorithm, ReadOnlySpan<byte> key)
        {
            BCryptOpenAlgorithmProviderFlags algorithmFlags =
                BCryptOpenAlgorithmProviderFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG;

            // This is a shared handle, do not put this in a using.
            SafeBCryptAlgorithmHandle algorithmHandle = Interop.BCrypt.BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle(
                algorithm,
                algorithmFlags,
                out _hashSizeInBytes);

            SafeBCryptHashHandle hashHandle;

            NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(
                algorithmHandle,
                out hashHandle,
                pbHashObject: IntPtr.Zero,
                cbHashObject: 0,
                key,
                key.Length,
                BCryptCreateHashFlags.None);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                hashHandle.Dispose();
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }

            _hashHandle = hashHandle;
        }

        public void Append(ReadOnlySpan<byte> data)
        {
            if (data.IsEmpty)
            {
                return;
            }

            NTSTATUS ntStatus = Interop.BCrypt.BCryptHashData(_hashHandle, data, data.Length, dwFlags: 0);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }
        }

        public int Finalize(Span<byte> destination)
        {
            Debug.Assert(destination.Length >= _hashSizeInBytes);

            NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hashHandle, destination, _hashSizeInBytes, dwFlags: 0);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }

            return _hashSizeInBytes;
        }

        public void Dispose()
        {
            _hashHandle.Dispose();
        }
    }
}