// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
    internal static partial class Crypto
    {
        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaUpRef")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static partial bool DsaUpRef(IntPtr dsa);

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaDestroy")]
        internal static partial void DsaDestroy(IntPtr dsa);

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaGenerateKey")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static partial bool DsaGenerateKey(out SafeDsaHandle dsa, int bits);

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaSizeSignature")]
        private static partial int DsaSizeSignature(SafeDsaHandle dsa);

        /// <summary>
        /// Return the maximum size of the DER-encoded key in bytes.
        /// </summary>
        internal static int DsaEncodedSignatureSize(SafeDsaHandle dsa)
        {
            int size = DsaSizeSignature(dsa);
            return size;
        }

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaSizeQ")]
        private static partial int DsaSizeQ(SafeDsaHandle dsa);

        /// <summary>
        /// Return the size of the 'r' or 's' signature fields in bytes.
        /// </summary>
        internal static int DsaSignatureFieldSize(SafeDsaHandle dsa)
        {
            int size = DsaSizeQ(dsa);
            Debug.Assert(size * 2 < DsaEncodedSignatureSize(dsa));
            return size;
        }

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaSizeP")]
        private static partial int DsaSizeP(SafeDsaHandle dsa);

        /// <summary>
        /// Return the size of the key in bytes.
        /// </summary>
        internal static int DsaKeySize(SafeDsaHandle dsa)
        {
            int keySize = DsaSizeP(dsa);

            // Assume an even multiple of 8 bytes \ 64 bits (OpenSsl also makes the same assumption)
            keySize = (keySize + 7) / 8 * 8;
            return keySize;
        }

        internal static bool DsaSign(SafeDsaHandle dsa, ReadOnlySpan<byte> hash, Span<byte> refSignature, out int outSignatureLength) =>
            DsaSign(dsa, ref MemoryMarshal.GetReference(hash), hash.Length, ref MemoryMarshal.GetReference(refSignature), out outSignatureLength);

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaSign")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static partial bool DsaSign(SafeDsaHandle dsa, ref byte hash, int hashLength, ref byte refSignature, out int outSignatureLength);

        internal static bool DsaVerify(SafeDsaHandle dsa, ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
        {
            bool ret = DsaVerify(
                dsa,
                ref MemoryMarshal.GetReference(hash),
                hash.Length,
                ref MemoryMarshal.GetReference(signature),
                signature.Length);

            // Error queue already cleaned on the native function.

            return ret;
        }

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaVerify")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static partial bool DsaVerify(SafeDsaHandle dsa, ref byte hash, int hashLength, ref byte signature, int signatureLength);

        internal static DSAParameters ExportDsaParameters(SafeDsaHandle key, bool includePrivateParameters)
        {
            Debug.Assert(
                key != null && !key.IsInvalid,
                "Callers should check the key is invalid and throw an exception with a message");

            if (key == null || key.IsInvalid)
            {
                throw new CryptographicException();
            }

            IntPtr p_bn, q_bn, g_bn, y_bn, x_bn; // these are not owned
            int    p_cb, q_cb, g_cb, y_cb, x_cb;

            bool refAdded = false;
            try
            {
                key.DangerousAddRef(ref refAdded); // Protect access to the *_bn variables

                if (!GetDsaParameters(key,
                    out p_bn, out p_cb,
                    out q_bn, out q_cb,
                    out g_bn, out g_cb,
                    out y_bn, out y_cb,
                    out x_bn, out x_cb))
                {
                    throw new CryptographicException();
                }

                // Match Windows semantics where p, g and y have same length
                int pgy_cb = GetMax(p_cb, g_cb, y_cb);

                // Match Windows semantics where q and x have same length
                int qx_cb = GetMax(q_cb, x_cb);

                DSAParameters dsaParameters = new DSAParameters
                {
                    P = Crypto.ExtractBignum(p_bn, pgy_cb)!,
                    Q = Crypto.ExtractBignum(q_bn, qx_cb)!,
                    G = Crypto.ExtractBignum(g_bn, pgy_cb)!,
                    Y = Crypto.ExtractBignum(y_bn, pgy_cb)!,
                };

                if (includePrivateParameters)
                {
                    dsaParameters.X = Crypto.ExtractBignum(x_bn, qx_cb);
                }

                return dsaParameters;
            }
            finally
            {
                if (refAdded)
                    key.DangerousRelease();
            }
        }

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetDsaParameters")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static partial bool GetDsaParameters(
            SafeDsaHandle key,
            out IntPtr p, out int p_cb,
            out IntPtr q, out int q_cb,
            out IntPtr g, out int g_cb,
            out IntPtr y, out int y_cb,
            out IntPtr x, out int x_cb);

        [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_DsaKeyCreateByExplicitParameters")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static partial bool DsaKeyCreateByExplicitParameters(
            out SafeDsaHandle dsa,
            byte[] p,
            int pLength,
            byte[] q,
            int qLength,
            byte[] g,
            int gLength,
            byte[] y,
            int yLength,
            byte[]? x,
            int xLength);

        /// <summary>
        /// Return the maximum value in the array; assumes non-negative values.
        /// </summary>
        private static int GetMax(int[] values)
        {
            int max = 0;

            foreach (var i in values)
            {
                Debug.Assert(i >= 0);
                if (i > max)
                    max = i;
            }

            return max;
        }

        /// <summary>
        /// Return the maximum value in the array; assumes non-negative values.
        /// </summary>
        private static int GetMax(int value1, int value2)
        {
            Debug.Assert(value1 >= 0);
            Debug.Assert(value2 >= 0);
            return (value1 > value2 ? value1 : value2);
        }

        /// <summary>
        /// Return the maximum value in the array; assumes non-negative values.
        /// </summary>
        private static int GetMax(int value1, int value2, int value3)
        {
            return GetMax(GetMax(value1, value2), value3);
        }
    }
}
