Support Rfc3279 signature format for DSA and EcDSA (#1612)
authorKrzysztof Wicher <mordotymoja@gmail.com>
Wed, 18 Mar 2020 15:19:25 +0000 (08:19 -0700)
committerGitHub <noreply@github.com>
Wed, 18 Mar 2020 15:19:25 +0000 (08:19 -0700)
This change adds overloads for SignData and SignHash/ComputeSignature on the DSA and ECDsa classes to make it easier for callers that need to use RFC3279's signature format to work with the classes in .NET.

It also moves more temporary work from temporary buffers to stackallocs.

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
28 files changed:
src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Der.cs
src/libraries/Common/src/Internal/Cryptography/Helpers.cs
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs
src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs
src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs
src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs
src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.SignVerify.cs
src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs
src/libraries/Common/src/System/Security/Cryptography/ECDsaSecurityTransforms.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaSignatureFormatTests.cs [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecdsa.c
src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSASignatureFormat.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/ECDsaTests.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj
src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj
src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj
src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DSACryptoServiceProvider.Unix.cs
src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs
src/libraries/System.Security.Cryptography.Csp/tests/System.Security.Cryptography.Csp.Tests.csproj
src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj
src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj

index 868547a..40f035a 100644 (file)
@@ -4,8 +4,6 @@
 
 using System;
 using System.Diagnostics;
-using System.IO;
-using System.Numerics;
 using System.Security.Cryptography;
 using System.Security.Cryptography.Asn1;
 
@@ -21,44 +19,169 @@ namespace Internal.Cryptography
         /// </summary>
         public static byte[] ConvertIeee1363ToDer(ReadOnlySpan<byte> input)
         {
+            using (AsnWriter writer = WriteIeee1363ToDer(input))
+            {
+                return writer.Encode();
+            }
+        }
+
+        internal static bool TryConvertIeee1363ToDer(
+            ReadOnlySpan<byte> input,
+            Span<byte> destination,
+            out int bytesWritten)
+        {
+            using (AsnWriter writer = WriteIeee1363ToDer(input))
+            {
+                return writer.TryEncode(destination, out bytesWritten);
+            }
+        }
+
+        private static AsnWriter WriteIeee1363ToDer(ReadOnlySpan<byte> input)
+        {
             Debug.Assert(input.Length % 2 == 0);
             Debug.Assert(input.Length > 1);
 
             // Input is (r, s), each of them exactly half of the array.
-            // Output is the DER encoded value of CONSTRUCTEDSEQUENCE(INTEGER(r), INTEGER(s)).
+            // Output is the DER encoded value of SEQUENCE(INTEGER(r), INTEGER(s)).
             int halfLength = input.Length / 2;
 
-            using (AsnWriter writer = new AsnWriter(AsnEncodingRules.DER))
-            {
-                writer.PushSequence();
-                writer.WriteKeyParameterInteger(input.Slice(0, halfLength));
-                writer.WriteKeyParameterInteger(input.Slice(halfLength, halfLength));
-                writer.PopSequence();
-                return writer.Encode();
-            }
+            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+            writer.PushSequence();
+            writer.WriteKeyParameterInteger(input.Slice(0, halfLength));
+            writer.WriteKeyParameterInteger(input.Slice(halfLength, halfLength));
+            writer.PopSequence();
+            return writer;
         }
 
         /// <summary>
         /// Convert Der format of (r, s) to Ieee1363 format
         /// </summary>
-        public static byte[] ConvertDerToIeee1363(byte[] input, int inputOffset, int inputCount, int fieldSizeBits)
+        public static byte[] ConvertDerToIeee1363(ReadOnlySpan<byte> input, int fieldSizeBits)
         {
-            int size = BitsToBytes(fieldSizeBits);
+            int fieldSizeBytes = BitsToBytes(fieldSizeBits);
+            int encodedSize = 2 * fieldSizeBytes;
+            byte[] response = new byte[encodedSize];
 
-            AsnReader reader = new AsnReader(input.AsMemory(inputOffset, inputCount), AsnEncodingRules.DER);
-            AsnReader sequenceReader = reader.ReadSequence();
+            ConvertDerToIeee1363(input, fieldSizeBits, response);
+            return response;
+        }
+
+        internal static int ConvertDerToIeee1363(ReadOnlySpan<byte> input, int fieldSizeBits, Span<byte> destination)
+        {
+            int fieldSizeBytes = BitsToBytes(fieldSizeBits);
+            int encodedSize = 2 * fieldSizeBytes;
+
+            Debug.Assert(destination.Length >= encodedSize);
+
+            AsnValueReader reader = new AsnValueReader(input, AsnEncodingRules.DER);
+            AsnValueReader sequenceReader = reader.ReadSequence();
             reader.ThrowIfNotEmpty();
-            ReadOnlySpan<byte> rDer = sequenceReader.ReadIntegerBytes().Span;
-            ReadOnlySpan<byte> sDer = sequenceReader.ReadIntegerBytes().Span;
+            ReadOnlySpan<byte> rDer = sequenceReader.ReadIntegerBytes();
+            ReadOnlySpan<byte> sDer = sequenceReader.ReadIntegerBytes();
             sequenceReader.ThrowIfNotEmpty();
 
-            byte[] response = new byte[2 * size];
-            CopySignatureField(rDer, response.AsSpan(0, size));
-            CopySignatureField(sDer, response.AsSpan(size, size));
+            CopySignatureField(rDer, destination.Slice(0, fieldSizeBytes));
+            CopySignatureField(sDer, destination.Slice(fieldSizeBytes, fieldSizeBytes));
+            return encodedSize;
+        }
 
-            return response;
+        internal static int GetMaxDerSignatureSize(int fieldSizeBits)
+        {
+            // This encoding format is the DER-encoded representation of
+            // SEQUENCE(INTEGER(r), INTEGER(s)).
+            // Each of r and s are unsigned fieldSizeBits integers, and if byte-aligned
+            // then they may gain a padding byte to avoid being a negative number.
+            // The biggest single-byte length encoding for DER is 0x7F bytes, but we're
+            // symmetric, so 0x7E (126).
+            // 63 bytes per half allows for 61 content bytes (prefix 02 3D), which can
+            // encode up to a ((61 * 8) - 1)-bit integer.
+            // So, any fieldSizeBits <= 487 maximally needs 2 * fieldSizeBytes + 6 bytes,
+            // because all lengths fit in one byte. (30 7E 02 3D ... 02 3D ...)
+
+            // Add the padding bit because of unsigned -> signed.
+            int paddedFieldSizeBytes = BitsToBytes(fieldSizeBits + 1);
+
+            if (paddedFieldSizeBytes <= 61)
+            {
+                return 2 * paddedFieldSizeBytes + 6;
+            }
+
+            // Past this point the sequence length grows (30 81 xx) up until 0xFF payload.
+            // Per our symmetry, that happens when the integers themselves max out, which is
+            // when paddedFieldSizeBytes is 0x7F; which covers up to a 1015-bit (before padding) field.
+
+            if (paddedFieldSizeBytes <= 0x7F)
+            {
+                return 2 * paddedFieldSizeBytes + 7;
+            }
+
+            // Beyond here, we'll just do math.
+            int segmentSize = 2 + GetDerLengthLength(paddedFieldSizeBytes) + paddedFieldSizeBytes;
+            int payloadSize = 2 * segmentSize;
+            int sequenceSize = 2 + GetDerLengthLength(payloadSize) + payloadSize;
+            return sequenceSize;
+
+            static int GetDerLengthLength(int payloadLength)
+            {
+                Debug.Assert(payloadLength >= 0);
+
+                if (payloadLength <= 0x7F)
+                    return 0;
+
+                if (payloadLength <= 0xFF)
+                    return 1;
+
+                if (payloadLength <= 0xFFFF)
+                    return 2;
+
+                if (payloadLength <= 0xFFFFFF)
+                    return 3;
+
+                return 4;
+            }
+        }
+
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+        /// <summary>
+        /// Converts IeeeP1363 format to the specified signature format
+        /// </summary>
+        internal static byte[] ConvertFromIeeeP1363Signature(byte[] signature, DSASignatureFormat targetFormat)
+        {
+            switch (targetFormat)
+            {
+                case DSASignatureFormat.IeeeP1363FixedFieldConcatenation:
+                    return signature;
+                case DSASignatureFormat.Rfc3279DerSequence:
+                    return ConvertIeee1363ToDer(signature);
+                default:
+                    throw new CryptographicException(
+                        SR.Cryptography_UnknownSignatureFormat,
+                        targetFormat.ToString());
+            }
         }
 
+        /// <summary>
+        /// Converts signature in the specified signature format to IeeeP1363
+        /// </summary>
+        internal static byte[] ConvertSignatureToIeeeP1363(
+            DSASignatureFormat currentFormat,
+            ReadOnlySpan<byte> signature,
+            int fieldSizeBits)
+        {
+            switch (currentFormat)
+            {
+                case DSASignatureFormat.IeeeP1363FixedFieldConcatenation:
+                    return signature.ToArray();
+                case DSASignatureFormat.Rfc3279DerSequence:
+                    return ConvertDerToIeee1363(signature, fieldSizeBits);
+                default:
+                    throw new CryptographicException(
+                        SR.Cryptography_UnknownSignatureFormat,
+                        currentFormat.ToString());
+            }
+        }
+#endif
+
         public static int BitsToBytes(int bitLength)
         {
             int byteLength = (bitLength + 7) / 8;
@@ -69,10 +192,15 @@ namespace Internal.Cryptography
         {
             if (signatureField.Length > response.Length)
             {
-                // The only way this should be true is if the value required a zero-byte-pad.
-                Debug.Assert(signatureField.Length == response.Length + 1, "signatureField.Length == fieldLength + 1");
-                Debug.Assert(signatureField[0] == 0, "signatureField[0] == 0");
-                Debug.Assert(signatureField[1] > 0x7F, "signatureField[1] > 0x7F");
+                if (signatureField.Length != response.Length + 1 ||
+                    signatureField[0] != 0 ||
+                    signatureField[1] <= 0x7F)
+                {
+                    // The only way this should be true is if the value required a zero-byte-pad.
+                    Debug.Fail($"A signature field was longer ({signatureField.Length}) than expected ({response.Length})");
+                    throw new CryptographicException();
+                }
+
                 signatureField = signatureField.Slice(1);
             }
 
@@ -80,7 +208,59 @@ namespace Internal.Cryptography
             // with zeroes in the response.  Since the array was already
             // zeroed out, just figure out where we need to start copying.
             int writeOffset = response.Length - signatureField.Length;
+            response.Slice(0, writeOffset).Clear();
             signatureField.CopyTo(response.Slice(writeOffset));
         }
+
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+        internal static byte[]? ConvertSignatureToIeeeP1363(
+            this DSA dsa,
+            DSASignatureFormat currentFormat,
+            ReadOnlySpan<byte> signature,
+            int fieldSizeBits = 0)
+        {
+            try
+            {
+                if (fieldSizeBits == 0)
+                {
+                    DSAParameters pars = dsa.ExportParameters(false);
+                    fieldSizeBits = pars.Q!.Length * 8;
+                }
+
+                return ConvertSignatureToIeeeP1363(
+                    currentFormat,
+                    signature,
+                    fieldSizeBits);
+            }
+            catch (CryptographicException)
+            {
+                // This method is used only for verification where we want to return false when signature is
+                // incorrectly formatted.
+                // We do not want to bubble up the exception anywhere.
+                return null;
+            }
+        }
+
+        internal static byte[]? ConvertSignatureToIeeeP1363(
+            this ECDsa ecdsa,
+            DSASignatureFormat currentFormat,
+            ReadOnlySpan<byte> signature)
+        {
+            try
+            {
+                return ConvertSignatureToIeeeP1363(
+                    currentFormat,
+                    signature,
+                    ecdsa.KeySize);
+            }
+            catch (CryptographicException)
+            {
+                // This method is used only for verification where we want to return false when signature is
+                // incorrectly formatted.
+                // We do not want to bubble up the exception anywhere.
+                return null;
+            }
+        }
+#endif
     }
 }
index 0959b74..6e274fe 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 #nullable enable
+using System;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Internal.Cryptography
@@ -19,5 +20,17 @@ namespace Internal.Cryptography
 
             return (byte[])(src.Clone());
         }
+
+        internal static bool TryCopyToDestination(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
+        {
+            if (source.TryCopyTo(destination))
+            {
+                bytesWritten = source.Length;
+                return true;
+            }
+
+            bytesWritten = 0;
+            return false;
+        }
     }
 }
index 5ada0a2..24cab8d 100644 (file)
@@ -10,12 +10,12 @@ internal static partial class Interop
 {
     internal static partial class Crypto
     {
-        internal static bool EcDsaSign(ReadOnlySpan<byte> dgst, Span<byte> sig, [In, Out] ref int siglen, SafeEcKeyHandle ecKey) =>
-            EcDsaSign(ref MemoryMarshal.GetReference(dgst), dgst.Length, ref MemoryMarshal.GetReference(sig), ref siglen, ecKey);
+        internal static bool EcDsaSign(ReadOnlySpan<byte> dgst, Span<byte> sig, out int siglen, SafeEcKeyHandle ecKey) =>
+            EcDsaSign(ref MemoryMarshal.GetReference(dgst), dgst.Length, ref MemoryMarshal.GetReference(sig), out siglen, ecKey);
 
         [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EcDsaSign")]
         [return: MarshalAs(UnmanagedType.Bool)]
-        private static extern bool EcDsaSign(ref byte dgst, int dlen, ref byte sig, [In, Out] ref int siglen, SafeEcKeyHandle ecKey);
+        private static extern bool EcDsaSign(ref byte dgst, int dlen, ref byte sig, out int siglen, SafeEcKeyHandle ecKey);
 
         internal static int EcDsaVerify(ReadOnlySpan<byte> dgst, ReadOnlySpan<byte> sigbuf, SafeEcKeyHandle ecKey)
         {
index 9dc07c4..406d287 100644 (file)
@@ -41,15 +41,49 @@ namespace System.Security.Cryptography
                 }
             }
 
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+            protected override unsafe bool TryCreateSignatureCore(
+                ReadOnlySpan<byte> hash,
+                Span<byte> destination,
+                DSASignatureFormat signatureFormat,
+                out int bytesWritten)
+#else
             public override unsafe bool TryCreateSignature(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
+#endif
             {
                 Span<byte> stackBuf = stackalloc byte[WindowsMaxQSize];
                 ReadOnlySpan<byte> source = AdjustHashSizeIfNecessary(hash, stackBuf);
 
                 using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
                 {
-                    return CngCommon.TrySignHash(keyHandle, source, destination, AsymmetricPaddingMode.None, null, out bytesWritten);
+                    if (!CngCommon.TrySignHash(keyHandle, source, destination, AsymmetricPaddingMode.None, null, out bytesWritten))
+                    {
+                        bytesWritten = 0;
+                        return false;
+                    }
                 }
+
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+                {
+                    return true;
+                }
+
+                if (signatureFormat != DSASignatureFormat.Rfc3279DerSequence)
+                {
+                    Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
+                    throw new CryptographicException(
+                        SR.Cryptography_UnknownSignatureFormat,
+                        signatureFormat.ToString());
+                }
+
+                return AsymmetricAlgorithmHelpers.TryConvertIeee1363ToDer(
+                    destination.Slice(0, bytesWritten),
+                    destination,
+                    out bytesWritten);
+#else
+                return true;
+#endif
             }
 
             public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature)
@@ -63,14 +97,41 @@ namespace System.Security.Cryptography
                     throw new ArgumentNullException(nameof(rgbSignature));
                 }
 
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                return VerifySignatureCore(rgbHash, rgbSignature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+#else
                 return VerifySignature((ReadOnlySpan<byte>)rgbHash, (ReadOnlySpan<byte>)rgbSignature);
+#endif
             }
 
-            public override bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+            protected override bool VerifySignatureCore(
+                ReadOnlySpan<byte> hash,
+                ReadOnlySpan<byte> signature,
+                DSASignatureFormat signatureFormat)
             {
                 Span<byte> stackBuf = stackalloc byte[WindowsMaxQSize];
                 ReadOnlySpan<byte> source = AdjustHashSizeIfNecessary(hash, stackBuf);
 
+                if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence)
+                {
+                    // source.Length is the field size, in bytes, so just convert to bits.
+                    int fieldSizeBits = source.Length * 8;
+                    signature = this.ConvertSignatureToIeeeP1363(signatureFormat, signature, fieldSizeBits);
+                }
+                else if (signatureFormat != DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+                {
+                    Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
+                    throw new CryptographicException(
+                        SR.Cryptography_UnknownSignatureFormat,
+                        signatureFormat.ToString());
+                }
+#else
+            public override bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
+            {
+                Span<byte> stackBuf = stackalloc byte[WindowsMaxQSize];
+                ReadOnlySpan<byte> source = AdjustHashSizeIfNecessary(hash, stackBuf);
+#endif
                 using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
                 {
                     unsafe
index b2d01c1..9a6f96f 100644 (file)
@@ -24,7 +24,16 @@ namespace System.Security.Cryptography
 #endif
         public sealed partial class DSAOpenSsl : DSA
         {
+            // The biggest key allowed by FIPS 186-4 has N=256 (bit), which
+            // maximally produces a 72-byte DER signature.
+            // If a future version of the standard continues to enhance DSA,
+            // we may want to bump this limit to allow the max-1 (expected size)
+            // TryCreateSignature to pass.
+            // Future updates seem unlikely, though, as FIPS 186-5 October 2019 draft has
+            // DSA as a no longer supported/updated algorithm.
+            private const int SignatureStackBufSize = 72;
             private const int BitsPerByte = 8;
+
             private Lazy<SafeDsaHandle> _key = null!;
 
             public DSAOpenSsl()
@@ -210,71 +219,118 @@ namespace System.Security.Cryptography
 
                 SafeDsaHandle key = GetKey();
                 int signatureSize = Interop.Crypto.DsaEncodedSignatureSize(key);
-                byte[] signature = CryptoPool.Rent(signatureSize);
-                try
-                {
-                    bool success = Interop.Crypto.DsaSign(key, rgbHash, new Span<byte>(signature, 0, signatureSize), out signatureSize);
-                    if (!success)
-                    {
-                        throw Interop.Crypto.CreateOpenSslCryptographicException();
-                    }
+                int signatureFieldSize = Interop.Crypto.DsaSignatureFieldSize(key) * BitsPerByte;
+                Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
 
-                    Debug.Assert(
-                        signatureSize <= signature.Length,
-                        "DSA_sign reported an unexpected signature size",
-                        "DSA_sign reported signatureSize was {0}, when <= {1} was expected",
-                        signatureSize,
-                        signature.Length);
+                ReadOnlySpan<byte> derSignature = SignHash(rgbHash, signDestination, signatureSize, key);
+                return AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, signatureFieldSize);
+            }
 
-                    int signatureFieldSize = Interop.Crypto.DsaSignatureFieldSize(key) * BitsPerByte;
-                    return AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(signature, 0, signatureSize, signatureFieldSize);
-                }
-                finally
-                {
-                    CryptoPool.Return(signature, signatureSize);
-                }
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+            public override bool TryCreateSignature(
+                ReadOnlySpan<byte> hash,
+                Span<byte> destination,
+                out int bytesWritten)
+            {
+                return TryCreateSignatureCore(
+                    hash,
+                    destination,
+                    DSASignatureFormat.IeeeP1363FixedFieldConcatenation,
+                    out bytesWritten);
             }
 
+            protected override bool TryCreateSignatureCore(
+                ReadOnlySpan<byte> hash,
+                Span<byte> destination,
+                DSASignatureFormat signatureFormat,
+                out int bytesWritten)
+#else
             public override bool TryCreateSignature(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
+#endif
             {
-                byte[] converted;
                 SafeDsaHandle key = GetKey();
-                int signatureSize = Interop.Crypto.DsaEncodedSignatureSize(key);
-                byte[] signature = CryptoPool.Rent(signatureSize);
-                try
+                int maxSignatureSize = Interop.Crypto.DsaEncodedSignatureSize(key);
+                Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
+
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+#endif
+                {
+                    int fieldSizeBytes = Interop.Crypto.DsaSignatureFieldSize(key);
+                    int p1363SignatureSize = 2 * fieldSizeBytes;
+
+                    if (destination.Length < p1363SignatureSize)
+                    {
+                        bytesWritten = 0;
+                        return false;
+                    }
+
+                    int fieldSizeBits = fieldSizeBytes * 8;
+
+                    ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, maxSignatureSize, key);
+                    bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, fieldSizeBits, destination);
+                    Debug.Assert(bytesWritten == p1363SignatureSize);
+                    return true;
+                }
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence)
                 {
-                    bool success = Interop.Crypto.DsaSign(key, hash, new Span<byte>(signature, 0, signatureSize), out signatureSize);
-                    if (!success)
+                    if (destination.Length >= maxSignatureSize)
                     {
-                        throw Interop.Crypto.CreateOpenSslCryptographicException();
+                        signDestination = destination;
                     }
+                    else if (maxSignatureSize > signDestination.Length)
+                    {
+                        Debug.Fail($"Stack-based signDestination is insufficient ({maxSignatureSize} needed)");
+                        bytesWritten = 0;
+                        return false;
+                    }
+
+                    ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, maxSignatureSize, key);
 
-                    Debug.Assert(
-                        signatureSize <= signature.Length,
-                        "DSA_sign reported an unexpected signature size",
-                        "DSA_sign reported signatureSize was {0}, when <= {1} was expected",
-                        signatureSize,
-                        signature.Length);
+                    if (destination == signDestination)
+                    {
+                        bytesWritten = derSignature.Length;
+                        return true;
+                    }
 
-                    int signatureFieldSize = Interop.Crypto.DsaSignatureFieldSize(key) * BitsPerByte;
-                    converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(signature, 0, signatureSize, signatureFieldSize);
+                    return Helpers.TryCopyToDestination(derSignature, destination, out bytesWritten);
                 }
-                finally
+                else
                 {
-                    CryptoPool.Return(signature, signatureSize);
+                    Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
+                    throw new CryptographicException(
+                        SR.Cryptography_UnknownSignatureFormat,
+                        signatureFormat.ToString());
                 }
+#endif
+            }
 
-                if (converted.Length <= destination.Length)
+            private static ReadOnlySpan<byte> SignHash(
+                ReadOnlySpan<byte> hash,
+                Span<byte> destination,
+                int signatureLength,
+                SafeDsaHandle key)
+            {
+                if (signatureLength > destination.Length)
                 {
-                    new ReadOnlySpan<byte>(converted).CopyTo(destination);
-                    bytesWritten = converted.Length;
-                    return true;
+                    Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)");
+                    destination = new byte[signatureLength];
                 }
-                else
+
+                if (!Interop.Crypto.DsaSign(key, hash, destination, out int actualLength))
                 {
-                    bytesWritten = 0;
-                    return false;
+                    throw Interop.Crypto.CreateOpenSslCryptographicException();
                 }
+
+                Debug.Assert(
+                    actualLength <= signatureLength,
+                    "DSA_sign reported an unexpected signature size",
+                    "DSA_sign reported signatureSize was {0}, when <= {1} was expected",
+                    actualLength,
+                    signatureLength);
+
+                return destination.Slice(0, actualLength);
             }
 
             public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature)
@@ -287,20 +343,44 @@ namespace System.Security.Cryptography
                 return VerifySignature((ReadOnlySpan<byte>)rgbHash, (ReadOnlySpan<byte>)rgbSignature);
             }
 
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+
+            public override bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
+                VerifySignatureCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+
+            protected override bool VerifySignatureCore(
+                ReadOnlySpan<byte> hash,
+                ReadOnlySpan<byte> signature,
+                DSASignatureFormat signatureFormat)
+#else
             public override bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
+#endif
             {
                 SafeDsaHandle key = GetKey();
 
-                int expectedSignatureBytes = Interop.Crypto.DsaSignatureFieldSize(key) * 2;
-                if (signature.Length != expectedSignatureBytes)
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
                 {
-                    // The input isn't of the right length (assuming no DER), so we can't sensibly re-encode it with DER.
-                    return false;
-                }
-
-                byte[] openSslFormat = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature);
+#endif
+                    int expectedSignatureBytes = Interop.Crypto.DsaSignatureFieldSize(key) * 2;
+                    if (signature.Length != expectedSignatureBytes)
+                    {
+                        // The input isn't of the right length (assuming no DER), so we can't sensibly re-encode it with DER.
+                        return false;
+                    }
 
-                return Interop.Crypto.DsaVerify(key, hash, openSslFormat);
+                    signature = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature);
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                }
+                else if (signatureFormat != DSASignatureFormat.Rfc3279DerSequence)
+                {
+                    Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
+                    throw new CryptographicException(
+                        SR.Cryptography_UnknownSignatureFormat,
+                        signatureFormat.ToString());
+                }
+#endif
+                return Interop.Crypto.DsaVerify(key, hash, signature);
             }
 
             private void ThrowIfDisposed()
index 6f4e111..0744b81 100644 (file)
@@ -276,9 +276,7 @@ namespace System.Security.Cryptography
                     // Since the AppleCrypto implementation is limited to FIPS 186-2, signature field sizes
                     // are always 160 bits / 20 bytes (the size of SHA-1, and the only legal length for Q).
                     byte[] ieeeFormatSignature = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(
-                        derFormatSignature,
-                        0,
-                        derFormatSignature.Length,
+                        derFormatSignature.AsSpan(0, derFormatSignature.Length),
                         fieldSizeBits: 160);
 
                     return ieeeFormatSignature;
index bc065df..277198b 100644 (file)
@@ -2,14 +2,12 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System;
 using System.Diagnostics;
 
 using Microsoft.Win32.SafeHandles;
 
 using Internal.Cryptography;
 
-using ErrorCode = Interop.NCrypt.ErrorCode;
 using AsymmetricPaddingMode = Interop.NCrypt.AsymmetricPaddingMode;
 
 namespace System.Security.Cryptography
@@ -48,12 +46,57 @@ namespace System.Security.Cryptography
             }
         }
 
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+        public override bool TrySignHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
+        {
+            return TrySignHashCore(
+                source,
+                destination,
+                DSASignatureFormat.IeeeP1363FixedFieldConcatenation,
+                out bytesWritten);
+        }
+
+        protected override unsafe bool TrySignHashCore(
+            ReadOnlySpan<byte> hash,
+            Span<byte> destination,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
+        {
+#else
         public override unsafe bool TrySignHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
         {
+            ReadOnlySpan<byte> hash = source;
+#endif
             using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
             {
-                return keyHandle.TrySignHash(source, destination, AsymmetricPaddingMode.None, null, out bytesWritten);
+                if (!keyHandle.TrySignHash(hash, destination, AsymmetricPaddingMode.None, null, out bytesWritten))
+                {
+                    bytesWritten = 0;
+                    return false;
+                }
+            }
+
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+            if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+            {
+                return true;
+            }
+
+            if (signatureFormat != DSASignatureFormat.Rfc3279DerSequence)
+            {
+                Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
+                throw new CryptographicException(
+                    SR.Cryptography_UnknownSignatureFormat,
+                    signatureFormat.ToString());
             }
+
+            return AsymmetricAlgorithmHelpers.TryConvertIeee1363ToDer(
+                destination.Slice(0, bytesWritten),
+                destination,
+                out bytesWritten);
+#else
+            return true;
+#endif
         }
 
         /// <summary>
@@ -66,14 +109,37 @@ namespace System.Security.Cryptography
             if (signature == null)
                 throw new ArgumentNullException(nameof(signature));
 
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+            return VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+#else
             return VerifyHash((ReadOnlySpan<byte>)hash, (ReadOnlySpan<byte>)signature);
+#endif
         }
 
-        public override unsafe bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+        public override bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
+            VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+
+        protected override bool VerifyHashCore(
+            ReadOnlySpan<byte> hash,
+            ReadOnlySpan<byte> signature,
+            DSASignatureFormat signatureFormat)
+#else
+        public override bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
+#endif
         {
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+            if (signatureFormat != DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+            {
+                signature = this.ConvertSignatureToIeeeP1363(signatureFormat, signature);
+            }
+#endif
             using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle())
             {
-                return keyHandle.VerifyHash(hash, signature, AsymmetricPaddingMode.None, null);
+                unsafe
+                {
+                    return keyHandle.VerifyHash(hash, signature, AsymmetricPaddingMode.None, null);
+                }
             }
         }
     }
index a95b3db..e6f2f21 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Diagnostics;
 using System.IO;
 using Internal.Cryptography;
 using Microsoft.Win32.SafeHandles;
@@ -14,6 +15,9 @@ namespace System.Security.Cryptography
 #endif
         public sealed partial class ECDsaOpenSsl : ECDsa
         {
+            // secp521r1 maxes out at 139 bytes, so 256 should always be enough
+            private const int SignatureStackBufSize = 256;
+
             private ECOpenSsl _key;
 
             /// <summary>
@@ -80,48 +84,112 @@ namespace System.Security.Cryptography
                 ThrowIfDisposed();
                 SafeEcKeyHandle key = _key.Value;
                 int signatureLength = Interop.Crypto.EcDsaSize(key);
-                byte[] signature = new byte[signatureLength];
-                if (!Interop.Crypto.EcDsaSign(hash, signature, ref signatureLength, key))
-                    throw Interop.Crypto.CreateOpenSslCryptographicException();
 
-                byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(signature, 0, signatureLength, KeySize);
+                Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
+                ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, signatureLength, key);
 
+                byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize);
                 return converted;
             }
 
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
             public override bool TrySignHash(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
             {
+                return TrySignHashCore(
+                    hash,
+                    destination,
+                    DSASignatureFormat.IeeeP1363FixedFieldConcatenation,
+                    out bytesWritten);
+            }
+
+            protected override bool TrySignHashCore(
+                ReadOnlySpan<byte> hash,
+                Span<byte> destination,
+                DSASignatureFormat signatureFormat,
+                out int bytesWritten)
+#else
+            public override bool TrySignHash(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
+#endif
+            {
                 ThrowIfDisposed();
                 SafeEcKeyHandle key = _key.Value;
 
-                byte[] converted;
                 int signatureLength = Interop.Crypto.EcDsaSize(key);
-                byte[] signature = CryptoPool.Rent(signatureLength);
-                try
+                Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
+
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
                 {
-                    if (!Interop.Crypto.EcDsaSign(hash, new Span<byte>(signature, 0, signatureLength), ref signatureLength, key))
+#endif
+                    int encodedSize = 2 * AsymmetricAlgorithmHelpers.BitsToBytes(KeySize);
+
+                    if (destination.Length < encodedSize)
                     {
-                        throw Interop.Crypto.CreateOpenSslCryptographicException();
+                        bytesWritten = 0;
+                        return false;
                     }
 
-                    converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(signature, 0, signatureLength, KeySize);
+                    ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, signatureLength, key);
+                    bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize, destination);
+                    Debug.Assert(bytesWritten == encodedSize);
+                    return true;
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
                 }
-                finally
+                else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence)
                 {
-                    CryptoPool.Return(signature, signatureLength);
+                    if (destination.Length >= signatureLength)
+                    {
+                        signDestination = destination;
+                    }
+                    else if (signatureLength > signDestination.Length)
+                    {
+                        Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)");
+                        bytesWritten = 0;
+                        return false;
+                    }
+
+                    ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, signatureLength, key);
+
+                    if (destination == signDestination)
+                    {
+                        bytesWritten = derSignature.Length;
+                        return true;
+                    }
+
+                    return Helpers.TryCopyToDestination(derSignature, destination, out bytesWritten);
                 }
+                else
+                {
+                    throw new ArgumentOutOfRangeException(nameof(signatureFormat));
+                }
+#endif
+            }
 
-                if (converted.Length <= destination.Length)
+            private static ReadOnlySpan<byte> SignHash(
+                ReadOnlySpan<byte> hash,
+                Span<byte> destination,
+                int signatureLength,
+                SafeEcKeyHandle key)
+            {
+                if (signatureLength > destination.Length)
                 {
-                    new ReadOnlySpan<byte>(converted).CopyTo(destination);
-                    bytesWritten = converted.Length;
-                    return true;
+                    Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)");
+                    destination = new byte[signatureLength];
                 }
-                else
+
+                if (!Interop.Crypto.EcDsaSign(hash, destination, out int actualLength, key))
                 {
-                    bytesWritten = 0;
-                    return false;
+                    throw Interop.Crypto.CreateOpenSslCryptographicException();
                 }
+
+                Debug.Assert(
+                    actualLength <= signatureLength,
+                    "ECDSA_sign reported an unexpected signature size",
+                    "ECDSA_sign reported signatureSize was {0}, when <= {1} was expected",
+                    actualLength,
+                    signatureLength);
+
+                return destination.Slice(0, actualLength);
             }
 
             public override bool VerifyHash(byte[] hash, byte[] signature)
@@ -134,24 +202,62 @@ namespace System.Security.Cryptography
                 return VerifyHash((ReadOnlySpan<byte>)hash, (ReadOnlySpan<byte>)signature);
             }
 
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+            public override bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
+                VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+
+            protected override bool VerifyHashCore(
+                ReadOnlySpan<byte> hash,
+                ReadOnlySpan<byte> signature,
+                DSASignatureFormat signatureFormat)
+#else
             public override bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature)
+#endif
             {
                 ThrowIfDisposed();
 
-                // The signature format for .NET is r.Concat(s). Each of r and s are of length BitsToBytes(KeySize), even
-                // when they would have leading zeroes.  If it's the correct size, then we need to encode it from
-                // r.Concat(s) to SEQUENCE(INTEGER(r), INTEGER(s)), because that's the format that OpenSSL expects.
-                int expectedBytes = 2 * AsymmetricAlgorithmHelpers.BitsToBytes(KeySize);
-                if (signature.Length != expectedBytes)
+                Span<byte> derSignature = stackalloc byte[SignatureStackBufSize];
+                ReadOnlySpan<byte> toVerify = derSignature;
+
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
                 {
-                    // The input isn't of the right length, so we can't sensibly re-encode it.
-                    return false;
-                }
+#endif
+                    // The signature format for .NET is r.Concat(s). Each of r and s are of length BitsToBytes(KeySize), even
+                    // when they would have leading zeroes.  If it's the correct size, then we need to encode it from
+                    // r.Concat(s) to SEQUENCE(INTEGER(r), INTEGER(s)), because that's the format that OpenSSL expects.
+                    int expectedBytes = 2 * AsymmetricAlgorithmHelpers.BitsToBytes(KeySize);
+                    if (signature.Length != expectedBytes)
+                    {
+                        // The input isn't of the right length, so we can't sensibly re-encode it.
+                        return false;
+                    }
 
-                byte[] openSslFormat = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature);
+                    if (AsymmetricAlgorithmHelpers.TryConvertIeee1363ToDer(signature, derSignature, out int derSize))
+                    {
+                        toVerify = derSignature.Slice(0, derSize);
+                    }
+                    else
+                    {
+                        toVerify = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature);
+                    }
+#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS
+                }
+                else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence)
+                {
+                    toVerify = signature;
+                }
+                else
+                {
+                    Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
+                    throw new CryptographicException(
+                        SR.Cryptography_UnknownSignatureFormat,
+                        signatureFormat.ToString());
+                }
+#endif
 
                 SafeEcKeyHandle key = _key.Value;
-                int verifyResult = Interop.Crypto.EcDsaVerify(hash, openSslFormat, key);
+                int verifyResult = Interop.Crypto.EcDsaVerify(hash, toVerify, key);
                 return verifyResult == 1;
             }
 
index 42bf959..da68d61 100644 (file)
@@ -109,9 +109,7 @@ namespace System.Security.Cryptography
 
                     byte[] derFormatSignature = Interop.AppleCrypto.GenerateSignature(keys.PrivateKey, hash);
                     byte[] ieeeFormatSignature = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(
-                        derFormatSignature,
-                        0,
-                        derFormatSignature.Length,
+                        derFormatSignature.AsSpan(0, derFormatSignature.Length),
                         KeySize);
 
                     return ieeeFormatSignature;
@@ -127,9 +125,7 @@ namespace System.Security.Cryptography
 
                     byte[] derFormatSignature = Interop.AppleCrypto.GenerateSignature(keys.PrivateKey, source);
                     byte[] ieeeFormatSignature = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(
-                        derFormatSignature,
-                        0,
-                        derFormatSignature.Length,
+                        derFormatSignature.AsSpan(0, derFormatSignature.Length),
                         KeySize);
 
                     if (ieeeFormatSignature.Length <= destination.Length)
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs
new file mode 100644 (file)
index 0000000..af4c6da
--- /dev/null
@@ -0,0 +1,382 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.Algorithms.Tests;
+using Xunit;
+
+namespace System.Security.Cryptography.Dsa.Tests
+{
+    public abstract class DSASignatureFormatTests : DsaFamilySignatureFormatTests
+    {
+        private static readonly KeyDescription[] s_keys = LocalGenerateTestKeys().ToArray();
+
+        protected override KeyDescription[] GenerateTestKeys() => s_keys;
+        protected override bool SupportsSha2 => DSAFactory.SupportsFips186_3;
+        protected override string HashParameterName => "rgbHash";
+        protected override string SignatureParameterName => "rgbSignature";
+
+        private static KeyDescription CreateKey(int keySize)
+        {
+            DSA dsa = DSAFactory.Create(keySize);
+            int fieldSize;
+
+            if (keySize <= 1024)
+            {
+                fieldSize = 160;
+            }
+            else if (keySize == 3072)
+            {
+                fieldSize = 256;
+            }
+            else
+            {
+                fieldSize = dsa.ExportParameters(false).Q.Length * 8;
+            }
+
+            return new KeyDescription(
+                dsa,
+                $"{keySize}-bit random key",
+                fieldSize);
+        }
+
+        private static KeyDescription OpenKey(in DSAParameters dsaParameters)
+        {
+            return new KeyDescription(
+                DSAFactory.Create(dsaParameters),
+                $"{dsaParameters.Y.Length * 8}-bit static key",
+                dsaParameters.Q.Length * 8);
+        }
+
+        private static IEnumerable<KeyDescription> LocalGenerateTestKeys()
+        {
+            if (DSAFactory.SupportsKeyGeneration)
+            {
+                yield return CreateKey(1024);
+
+                if (DSAFactory.SupportsFips186_3)
+                {
+                    yield return CreateKey(2048);
+                }
+            }
+
+            if (DSAFactory.SupportsFips186_3)
+            {
+                yield return OpenKey(DSATestData.GetDSA2048Params());
+            }
+
+            yield return OpenKey(DSATestData.GetDSA1024Params());
+        }
+    }
+
+    public sealed class DsaArraySignatureFormatTests : DSASignatureFormatTests
+    {
+        protected override bool IsArrayBased => true;
+
+        protected override byte[] SignHash(
+            KeyDescription key,
+            byte[] hash,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((DSA)key.Key).CreateSignature(hash, signatureFormat);
+        }
+
+        protected override bool VerifyHash(
+            KeyDescription key,
+            byte[] hash,
+            byte[] signature,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((DSA)key.Key).VerifySignature(hash, signature, signatureFormat);
+        }
+
+        protected override byte[] SignData(
+            KeyDescription key,
+            byte[] data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((DSA)key.Key).SignData(data, hashAlgorithm, signatureFormat);
+        }
+
+        protected override bool VerifyData(
+            KeyDescription key,
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((DSA)key.Key).VerifyData(data, signature, hashAlgorithm, signatureFormat);
+        }
+    }
+
+    public sealed class DsaArrayOffsetSignatureFormatTests : DSASignatureFormatTests
+    {
+        protected override bool IsArrayBased => true;
+
+        protected override byte[] SignHash(
+            KeyDescription key,
+            byte[] hash,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((DSA)key.Key).CreateSignature(hash, signatureFormat);
+        }
+
+        protected override bool VerifyHash(
+            KeyDescription key,
+            byte[] hash,
+            byte[] signature,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((DSA)key.Key).VerifySignature(hash, signature, signatureFormat);
+        }
+
+        protected override byte[] SignData(
+            KeyDescription key,
+            byte[] data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            int offset = 0;
+            int count = 0;
+
+            if (data != null)
+            {
+                offset = 2;
+                count = data.Length;
+                byte[] bigger = new byte[count + 7];
+                Buffer.BlockCopy(data, 0, bigger, offset, count);
+                data = bigger;
+            }
+
+            return ((DSA)key.Key).SignData(data, offset, count, hashAlgorithm, signatureFormat);
+        }
+
+        protected override bool VerifyData(
+            KeyDescription key,
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            int offset = 0;
+            int count = 0;
+
+            if (data != null)
+            {
+                offset = 2;
+                count = data.Length;
+                byte[] bigger = new byte[count + 7];
+                Buffer.BlockCopy(data, 0, bigger, offset, count);
+                data = bigger;
+            }
+
+            return ((DSA)key.Key).VerifyData(data, offset, count, signature, hashAlgorithm, signatureFormat);
+        }
+
+        [Fact]
+        public void OffsetAndCountOutOfRange()
+        {
+            KeyDescription keyDescription = GetKey();
+            DSA key = (DSA)keyDescription.Key;
+
+            HashAlgorithmName hash = HashAlgorithmName.SHA1;
+            byte[] buffer = new byte[10];
+
+            foreach (DSASignatureFormat format in Enum.GetValues(typeof(DSASignatureFormat)))
+            {
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.SignData(buffer, -1, buffer.Length, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.SignData(buffer, buffer.Length + 1, 0, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.VerifyData(buffer, -1, buffer.Length, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.VerifyData(buffer, buffer.Length + 1, 0, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.SignData(buffer, 1, buffer.Length, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.SignData(buffer, 0, buffer.Length + 1, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.SignData(buffer, buffer.Length, 1, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.VerifyData(buffer, 1, buffer.Length, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.VerifyData(buffer, 0, buffer.Length + 1, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.VerifyData(buffer, buffer.Length, 1, buffer, hash, format));
+            }
+        }
+    }
+
+    public sealed class DsaSpanSignatureFormatTests : DSASignatureFormatTests
+    {
+        protected override bool IsArrayBased => false;
+
+        protected override byte[] SignHash(
+            KeyDescription key,
+            byte[] hash,
+            DSASignatureFormat signatureFormat)
+        {
+            DSA dsa = (DSA)key.Key;
+            byte[] predictedMax = new byte[dsa.GetMaxSignatureSize(signatureFormat)];
+
+            Assert.True(
+                dsa.TryCreateSignature(hash, predictedMax, signatureFormat, out int written),
+                "TryCreateSignature with a GetMaxSignatureSize buffer");
+
+            if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+            {
+                // GetMaxSignatureSize should be exactly accurate for P1363.
+                Assert.Equal(predictedMax.Length, written);
+            }
+
+            if (written == predictedMax.Length)
+            {
+                return predictedMax;
+            }
+
+            return predictedMax.AsSpan(0, written).ToArray();
+        }
+
+        protected override bool VerifyHash(
+            KeyDescription key,
+            byte[] hash,
+            byte[] signature,
+            DSASignatureFormat signatureFormat)
+        {
+            ReadOnlySpan<byte> readOnlyHash = hash;
+            ReadOnlySpan<byte> readOnlySignature = signature;
+            return ((DSA)key.Key).VerifySignature(readOnlyHash, readOnlySignature, signatureFormat);
+        }
+
+        protected override byte[] SignData(
+            KeyDescription key,
+            byte[] data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            DSA dsa = (DSA)key.Key;
+            byte[] predictedMax = new byte[dsa.GetMaxSignatureSize(signatureFormat)];
+
+            Assert.True(
+                dsa.TrySignData(data, predictedMax, hashAlgorithm, signatureFormat, out int written),
+                "TrySignData with a GetMaxSignatureSize buffer");
+
+            if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+            {
+                // GetMaxSignatureSize should be exactly accurate for P1363.
+                Assert.Equal(predictedMax.Length, written);
+            }
+
+            if (written == predictedMax.Length)
+            {
+                return predictedMax;
+            }
+
+            return predictedMax.AsSpan(0, written).ToArray();
+        }
+
+        protected override bool VerifyData(
+            KeyDescription key,
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            ReadOnlySpan<byte> readOnlyData = data;
+            ReadOnlySpan<byte> readOnlySignature = signature;
+
+            return ((DSA)key.Key).VerifyData(readOnlyData, readOnlySignature, hashAlgorithm, signatureFormat);
+        }
+
+        private static int GetExpectedSize(int fieldSizeInBits)
+        {
+            // Assuming FieldSizeInBits is byte-aligned, the odds of a padding byte being required is 50%.
+            // Two 50% things means that we expect one of them and not the other (on average).
+            // They only shrink a byte in 1/256, but gain it back 50% of the time, so they shrink in 1/512.
+            // That's low enough it isn't expected.
+            // So we expect each of (r) and (s) to be (fieldSizeInBits / 8) + 0.5, then add structure.
+            // Because DSA is limited to small field sizes (256 bit), the structure overhead is always 6 bytes.
+            // So, fieldSizeInBits / 4
+            return fieldSizeInBits / 4 + 7;
+        }
+
+        [Fact]
+        public void Rfc23279TrySignHashUnderMax()
+        {
+            KeyDescription keyDescription = GetKey();
+            const int RetryCount = 10;
+            DSA key = (DSA)keyDescription.Key;
+
+            const DSASignatureFormat SignatureFormat = DSASignatureFormat.Rfc3279DerSequence;
+            byte[] hash = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+            int expectedSize = GetExpectedSize(keyDescription.FieldSizeInBits);
+            int maxSize = key.GetMaxSignatureSize(DSASignatureFormat.Rfc3279DerSequence);
+            Assert.True(expectedSize < maxSize, "expectedSize < maxSize");
+            byte[] signature = new byte[expectedSize];
+
+            for (int i = 0; i < RetryCount; i++)
+            {
+                if (key.TryCreateSignature(hash, signature, SignatureFormat, out int written))
+                {
+                    return;
+                }
+
+                Assert.Equal(0, written);
+            }
+
+            Assert.True(false, $"TryCreateSignature eventually succeeds with a {expectedSize}/{maxSize}-byte destination");
+        }
+
+        [Fact]
+        public void Rfc23279TrySignDataUnderMax()
+        {
+            KeyDescription keyDescription = GetKey();
+            DSA key = (DSA)keyDescription.Key;
+
+            const DSASignatureFormat SignatureFormat = DSASignatureFormat.Rfc3279DerSequence;
+            const int RetryCount = 10;
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
+
+            int expectedSize = GetExpectedSize(keyDescription.FieldSizeInBits);
+            int maxSize = key.GetMaxSignatureSize(DSASignatureFormat.Rfc3279DerSequence);
+            Assert.True(expectedSize < maxSize, "expectedSize < maxSize");
+            byte[] signature = new byte[expectedSize];
+
+            for (int i = 0; i < RetryCount; i++)
+            {
+                if (key.TrySignData(Array.Empty<byte>(), signature, hashAlgorithm, SignatureFormat, out int written))
+                {
+                    return;
+                }
+
+                Assert.Equal(0, written);
+            }
+
+            Assert.True(false, $"TrySignData eventually succeeds with a {expectedSize}/{maxSize}-byte destination");
+        }
+    }
+}
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs
new file mode 100644 (file)
index 0000000..d062f7e
--- /dev/null
@@ -0,0 +1,403 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+namespace System.Security.Cryptography.Algorithms.Tests
+{
+    public abstract class DsaFamilySignatureFormatTests
+    {
+        protected readonly struct KeyDescription
+        {
+            public readonly AsymmetricAlgorithm Key;
+            public readonly string Description;
+            public readonly int FieldSizeInBits;
+
+            public KeyDescription(AsymmetricAlgorithm key, string description, int fieldSizeInBits)
+            {
+                Key = key;
+                Description = description;
+                FieldSizeInBits = fieldSizeInBits;
+            }
+
+            public override string ToString() => Description;
+        }
+
+        private readonly KeyDescription[] _testKeys;
+        private readonly byte[] _typeNameBytes;
+        private readonly int _typeDifferentiator;
+
+        protected DsaFamilySignatureFormatTests()
+        {
+            _testKeys = GenerateTestKeys();
+            string typeName = GetType().Name;
+            _typeDifferentiator = typeName.GetHashCode();
+            _typeNameBytes = System.Text.Encoding.UTF8.GetBytes(typeName);
+        }
+
+        public static IEnumerable<object[]> SignatureFormats { get; } =
+            new[]
+            {
+                new object[] { DSASignatureFormat.Rfc3279DerSequence },
+                new object[] { DSASignatureFormat.IeeeP1363FixedFieldConcatenation },
+            };
+
+        protected virtual string HashParameterName => "hash";
+        protected virtual string SignatureParameterName => "signature";
+        protected abstract bool SupportsSha2 { get; }
+        protected abstract bool IsArrayBased { get; }
+        protected bool IsNotArrayBased => !IsArrayBased;
+
+        protected abstract KeyDescription[] GenerateTestKeys();
+
+        protected abstract byte[] SignHash(
+            KeyDescription key,
+            byte[] hash,
+            DSASignatureFormat signatureFormat);
+        protected abstract bool VerifyHash(
+            KeyDescription key,
+            byte[] hash,
+            byte[] signature,
+            DSASignatureFormat signatureFormat);
+        protected abstract byte[] SignData(
+            KeyDescription key,
+            byte[] data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat);
+        protected abstract bool VerifyData(
+            KeyDescription key,
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat);
+
+        protected KeyDescription GetKey([CallerMemberName]string testMethodName = null)
+        {
+            int absoluteKeyId = Math.Abs(_typeDifferentiator + testMethodName.GetHashCode());
+            int localKeyId = absoluteKeyId % _testKeys.Length;
+            return _testKeys[localKeyId];
+        }
+
+        protected static int GetDerLengthLength(int payloadLength)
+        {
+            if (payloadLength < 0)
+                throw new ArgumentOutOfRangeException(nameof(payloadLength));
+
+            if (payloadLength <= 0x7F)
+                return 0;
+
+            if (payloadLength <= 0xFF)
+                return 1;
+
+            if (payloadLength <= 0xFFFF)
+                return 2;
+
+            if (payloadLength <= 0xFFFFFF)
+                return 3;
+
+            return 4;
+        }
+
+        private static void CheckLength(KeyDescription key, byte[] signature, DSASignatureFormat signatureFormat)
+        {
+            int fieldSizeBytes = (key.FieldSizeInBits + 7) / 8;
+
+            switch (signatureFormat)
+            {
+                case DSASignatureFormat.IeeeP1363FixedFieldConcatenation:
+                    
+                    Assert.Equal(2 * fieldSizeBytes, signature.Length);
+                    break;
+                case DSASignatureFormat.Rfc3279DerSequence:
+                {
+                    // SEQUENCE(INTEGER, INTEGER) has a minimum length of 8 (30 06 02 01 00 02 01 00)
+                    // The maximum length is a bit more complicated:
+                    int elemSize = fieldSizeBytes + 1;
+                    int integerMax = 2 + GetDerLengthLength(elemSize) + elemSize;
+                    int integersMax = 2 * integerMax;
+                    int sequenceMax = 2 + GetDerLengthLength(integersMax) + integersMax;
+
+                    Assert.InRange(signature.Length, 8, sequenceMax);
+                    break;
+                }
+                default:
+                    throw new InvalidOperationException($"No handler for format {signatureFormat}");
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(SignatureFormats))]
+        public void SignHashVerifyHash(DSASignatureFormat signatureFormat)
+        {
+            KeyDescription key = GetKey();
+            byte[] hash = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+            byte[] signature = SignHash(key, hash, signatureFormat);
+            CheckLength(key, signature, signatureFormat);
+            Assert.True(VerifyHash(key, hash, signature, signatureFormat));
+        }
+
+        [Theory]
+        [MemberData(nameof(SignatureFormats))]
+        public void SignDataVerifyData_SHA1(DSASignatureFormat signatureFormat)
+        {
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
+
+            KeyDescription key = GetKey();
+            byte[] signature = SignData(key, _typeNameBytes, hashAlgorithm, signatureFormat);
+            CheckLength(key, signature, signatureFormat);
+            Assert.True(VerifyData(key, _typeNameBytes, signature, hashAlgorithm, signatureFormat));
+        }
+
+        [Theory]
+        [MemberData(nameof(SignatureFormats))]
+        public void SignDataVerifyHash_SHA1(DSASignatureFormat signatureFormat)
+        {
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
+
+            KeyDescription key = GetKey();
+            byte[] signature = SignData(key, _typeNameBytes, hashAlgorithm, signatureFormat);
+            CheckLength(key, signature, signatureFormat);
+
+            using (IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm))
+            {
+                hash.AppendData(_typeNameBytes);
+                Assert.True(VerifyHash(key, hash.GetHashAndReset(), signature, signatureFormat));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(SignatureFormats))]
+        public void SignDataVerifyData_SHA256(DSASignatureFormat signatureFormat)
+        {
+            if (!SupportsSha2)
+                return;
+
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA256;
+
+            KeyDescription key = GetKey();
+            byte[] signature = SignData(key, _typeNameBytes, hashAlgorithm, signatureFormat);
+            CheckLength(key, signature, signatureFormat);
+            Assert.True(VerifyData(key, _typeNameBytes, signature, hashAlgorithm, signatureFormat));
+        }
+
+        [Theory]
+        [MemberData(nameof(SignatureFormats))]
+        public void SignDataVerifyHash_SHA256(DSASignatureFormat signatureFormat)
+        {
+            if (!SupportsSha2)
+                return;
+
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA256;
+
+            KeyDescription key = GetKey();
+            byte[] signature = SignData(key, _typeNameBytes, hashAlgorithm, signatureFormat);
+            CheckLength(key, signature, signatureFormat);
+
+            using (IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm))
+            {
+                hash.AppendData(_typeNameBytes);
+                Assert.True(VerifyHash(key, hash.GetHashAndReset(), signature, signatureFormat));
+            }
+        }
+
+        [Fact]
+        public void VerifyInvalidRfc3279Signature()
+        {
+            KeyDescription key = GetKey();
+            // This is SEQUENCE(INTEGER(1), INTEGER(0)), except the second integer uses
+            // a length value that exceeds the payload length.
+            // This ensures that we don't throw exceptions after finding out the leading bytes
+            // are valid sequence for the payload length.
+            byte[] invalidSignature = { 0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x04, 0x00 };
+
+            Assert.False(
+                VerifyData(
+                    key,
+                    _typeNameBytes,
+                    invalidSignature,
+                    HashAlgorithmName.SHA1,
+                    DSASignatureFormat.Rfc3279DerSequence),
+                "VerifyData with an illegal DER payload");
+
+            Assert.False(
+                VerifyHash(
+                    key,
+                    _typeNameBytes,
+                    invalidSignature,
+                    DSASignatureFormat.Rfc3279DerSequence),
+                "VerifyHash with an illegal DER payload");
+        }
+
+        [Fact]
+        public void Rfc3279SignatureValidatesLength()
+        {
+            KeyDescription key = GetKey();
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
+            const DSASignatureFormat SignatureFormat = DSASignatureFormat.Rfc3279DerSequence;
+
+            byte[] hash = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+            byte[] signature = SignHash(key, hash, SignatureFormat);
+            byte[] rightPadded = signature.Concat(Enumerable.Repeat((byte)0, 4)).ToArray();
+
+            Assert.True(
+                VerifyHash(key, hash, signature, SignatureFormat),
+                "VerifyHash with the unmodified signature");
+
+            Assert.False(
+                VerifyHash(key, hash, rightPadded, SignatureFormat),
+                "VerifyHash with the right-padded signature");
+
+            signature = SignData(key, hash, hashAlgorithm, SignatureFormat);
+            rightPadded = signature.Concat(Enumerable.Repeat((byte)0, 4)).ToArray();
+
+            Assert.True(
+                VerifyData(key, hash, signature, hashAlgorithm, SignatureFormat),
+                "VerifyData with the unmodified signature");
+
+            Assert.False(
+                VerifyData(key, hash, rightPadded, hashAlgorithm, SignatureFormat),
+                "VerifyData with the right-padded signature");
+        }
+
+        [Theory]
+        [InlineData(DSASignatureFormat.IeeeP1363FixedFieldConcatenation, DSASignatureFormat.Rfc3279DerSequence)]
+        [InlineData(DSASignatureFormat.Rfc3279DerSequence, DSASignatureFormat.IeeeP1363FixedFieldConcatenation)]
+        public void SignatureFormatsAreNotCompatible(DSASignatureFormat signFormat, DSASignatureFormat verifyFormat)
+        {
+            if (!SupportsSha2)
+                return;
+
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
+            const int RetryCount = 10;
+
+            KeyDescription key = GetKey();
+            byte[] hash;
+
+            using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithm))
+            {
+                hasher.AppendData(_typeNameBytes);
+                hash = hasher.GetHashAndReset();
+            }
+
+            for (int i = 0; i < RetryCount; i++)
+            {
+                byte[] signature = SignData(
+                    key,
+                    _typeNameBytes,
+                    hashAlgorithm,
+                    signFormat);
+
+                if (!VerifyData(key, _typeNameBytes, signature, hashAlgorithm, verifyFormat))
+                {
+                    Assert.False(
+                        VerifyHash(key, hash, signature, verifyFormat),
+                        $"VerifyHash({verifyFormat}) verifies after VerifyData({verifyFormat}) fails");
+
+                    return;
+                }
+            }
+
+            Assert.False(true, $"{RetryCount} {signFormat} signatures verified as {verifyFormat} signatures");
+        }
+
+        [Fact]
+        public void BadSignatureFormat()
+        {
+            KeyDescription key = GetKey();
+
+            const DSASignatureFormat SignatureFormat = (DSASignatureFormat)3;
+            byte[] empty = Array.Empty<byte>();
+
+            AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                "signatureFormat",
+                () => SignData(key, empty, HashAlgorithmName.SHA1, SignatureFormat));
+
+            AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                "signatureFormat",
+                () => VerifyData(key, empty, empty, HashAlgorithmName.SHA1, SignatureFormat));
+
+            AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                "signatureFormat",
+                () => SignHash(key, empty, SignatureFormat));
+
+            AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                "signatureFormat",
+                () => VerifyHash(key, empty, empty, SignatureFormat));
+        }
+
+        [Fact]
+        public void EmptyHashAlgorithm()
+        {
+            KeyDescription key = GetKey();
+            byte[] empty = Array.Empty<byte>();
+
+            foreach (DSASignatureFormat format in Enum.GetValues(typeof(DSASignatureFormat)))
+            {
+                AssertExtensions.Throws<ArgumentException>(
+                    "hashAlgorithm",
+                    () => SignData(key, empty, default, format));
+
+                AssertExtensions.Throws<ArgumentException>(
+                    "hashAlgorithm",
+                    () => VerifyData(key, empty, empty, default, format));
+            }
+        }
+
+        [Fact]
+        public void UnknownHashAlgorithm()
+        {
+            KeyDescription key = GetKey();
+            byte[] empty = Array.Empty<byte>();
+            HashAlgorithmName unknown = new HashAlgorithmName(nameof(UnknownHashAlgorithm));
+
+            foreach (DSASignatureFormat format in Enum.GetValues(typeof(DSASignatureFormat)))
+            {
+                Assert.ThrowsAny<CryptographicException>(
+                    () => SignData(key, empty, unknown, format));
+
+                Assert.ThrowsAny<CryptographicException>(
+                    () => VerifyData(key, empty, empty, unknown, format));
+            }
+        }
+
+        [Fact]
+        public void NullInputs()
+        {
+            if (IsNotArrayBased)
+                return;
+
+            KeyDescription key = GetKey();
+
+            foreach (DSASignatureFormat format in Enum.GetValues(typeof(DSASignatureFormat)))
+            {
+                AssertExtensions.Throws<ArgumentNullException>(
+                    "data",
+                    () => SignData(key, null, HashAlgorithmName.SHA1, format));
+
+                AssertExtensions.Throws<ArgumentNullException>(
+                    "data",
+                    () => VerifyData(key, null, Array.Empty<byte>(), HashAlgorithmName.SHA1, format));
+
+                AssertExtensions.Throws<ArgumentNullException>(
+                    "signature",
+                    () => VerifyData(key, Array.Empty<byte>(), null, HashAlgorithmName.SHA1, format));
+
+                AssertExtensions.Throws<ArgumentNullException>(
+                    HashParameterName,
+                    () => SignHash(key, null, format));
+
+                AssertExtensions.Throws<ArgumentNullException>(
+                    HashParameterName,
+                    () => VerifyHash(key, null, Array.Empty<byte>(), format));
+
+                AssertExtensions.Throws<ArgumentNullException>(
+                    SignatureParameterName,
+                    () => VerifyHash(key, Array.Empty<byte>(), null, format));
+            }
+        }
+    }
+}
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaSignatureFormatTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaSignatureFormatTests.cs
new file mode 100644 (file)
index 0000000..5451458
--- /dev/null
@@ -0,0 +1,383 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.Algorithms.Tests;
+using System.Security.Cryptography.Tests;
+using Xunit;
+
+namespace System.Security.Cryptography.EcDsa.Tests
+{
+    public abstract class ECDsaSignatureFormatTests : DsaFamilySignatureFormatTests
+    {
+        private static readonly KeyDescription[] s_keys = LocalGenerateTestKeys().ToArray();
+
+        protected override KeyDescription[] GenerateTestKeys() => s_keys;
+        protected override bool SupportsSha2 => true;
+
+        private static KeyDescription CreateKey(ECCurve curve)
+        {
+            ECDsa dsa = ECDsaFactory.Create(curve);
+
+            return new KeyDescription(
+                dsa,
+                $"{dsa.KeySize}-bit random key",
+                dsa.KeySize);
+        }
+
+        private static KeyDescription OpenKey(in ECParameters ecParameters)
+        {
+            ECDsa dsa = ECDsaFactory.Create();
+            dsa.ImportParameters(ecParameters);
+
+            return new KeyDescription(
+                dsa,
+                $"{dsa.KeySize}-bit static key",
+                dsa.KeySize);
+        }
+
+        private static IEnumerable<KeyDescription> LocalGenerateTestKeys()
+        {
+            if (ECDsaFactory.IsCurveValid(EccTestData.BrainpoolP160r1Key1.Curve.Oid))
+            {
+                yield return OpenKey(EccTestData.BrainpoolP160r1Key1);
+            }
+
+            yield return CreateKey(ECCurve.NamedCurves.nistP384);
+
+            yield return OpenKey(EccTestData.GetNistP521DiminishedCoordsParameters());
+
+            if (ECDsaFactory.ExplicitCurvesSupported)
+            {
+                yield return OpenKey(EccTestData.GetNistP256ReferenceKeyExplicit());
+            }
+        }
+    }
+
+    public sealed class ECDsaArraySignatureFormatTests : ECDsaSignatureFormatTests
+    {
+        protected override bool IsArrayBased => true;
+
+        protected override byte[] SignHash(
+            KeyDescription key,
+            byte[] hash,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((ECDsa)key.Key).SignHash(hash, signatureFormat);
+        }
+
+        protected override bool VerifyHash(
+            KeyDescription key,
+            byte[] hash,
+            byte[] signature,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((ECDsa)key.Key).VerifyHash(hash, signature, signatureFormat);
+        }
+
+        protected override byte[] SignData(
+            KeyDescription key,
+            byte[] data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((ECDsa)key.Key).SignData(data, hashAlgorithm, signatureFormat);
+        }
+
+        protected override bool VerifyData(
+            KeyDescription key,
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((ECDsa)key.Key).VerifyData(data, signature, hashAlgorithm, signatureFormat);
+        }
+    }
+
+    public sealed class ECDsaArrayOffsetSignatureFormatTests : ECDsaSignatureFormatTests
+    {
+        protected override bool IsArrayBased => true;
+
+        protected override byte[] SignHash(
+            KeyDescription key,
+            byte[] hash,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((ECDsa)key.Key).SignHash(hash, signatureFormat);
+        }
+
+        protected override bool VerifyHash(
+            KeyDescription key,
+            byte[] hash,
+            byte[] signature,
+            DSASignatureFormat signatureFormat)
+        {
+            return ((ECDsa)key.Key).VerifyHash(hash, signature, signatureFormat);
+        }
+
+        protected override byte[] SignData(
+            KeyDescription key,
+            byte[] data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            int offset = 0;
+            int count = 0;
+
+            if (data != null)
+            {
+                offset = 2;
+                count = data.Length;
+                byte[] bigger = new byte[count + 7];
+                Buffer.BlockCopy(data, 0, bigger, offset, count);
+                data = bigger;
+            }
+
+            return ((ECDsa)key.Key).SignData(data, offset, count, hashAlgorithm, signatureFormat);
+        }
+
+        protected override bool VerifyData(
+            KeyDescription key,
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            int offset = 0;
+            int count = 0;
+
+            if (data != null)
+            {
+                offset = 2;
+                count = data.Length;
+                byte[] bigger = new byte[count + 7];
+                Buffer.BlockCopy(data, 0, bigger, offset, count);
+                data = bigger;
+            }
+
+            return ((ECDsa)key.Key).VerifyData(data, offset, count, signature, hashAlgorithm, signatureFormat);
+        }
+
+        [Fact]
+        public void OffsetAndCountOutOfRange()
+        {
+            KeyDescription keyDescription = GetKey();
+            ECDsa key = (ECDsa)keyDescription.Key;
+
+            HashAlgorithmName hash = HashAlgorithmName.SHA256;
+            byte[] buffer = new byte[10];
+
+            foreach (DSASignatureFormat format in Enum.GetValues(typeof(DSASignatureFormat)))
+            {
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.SignData(buffer, -1, buffer.Length, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.SignData(buffer, buffer.Length + 1, 0, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.VerifyData(buffer, -1, buffer.Length, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "offset",
+                    () => key.VerifyData(buffer, buffer.Length + 1, 0, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.SignData(buffer, 1, buffer.Length, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.SignData(buffer, 0, buffer.Length + 1, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.SignData(buffer, buffer.Length, 1, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.VerifyData(buffer, 1, buffer.Length, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.VerifyData(buffer, 0, buffer.Length + 1, buffer, hash, format));
+
+                AssertExtensions.Throws<ArgumentOutOfRangeException>(
+                    "count",
+                    () => key.VerifyData(buffer, buffer.Length, 1, buffer, hash, format));
+            }
+        }
+    }
+
+    public sealed class ECDsaSpanSignatureFormatTests : ECDsaSignatureFormatTests
+    {
+        protected override bool IsArrayBased => false;
+
+        protected override byte[] SignHash(
+            KeyDescription key,
+            byte[] hash,
+            DSASignatureFormat signatureFormat)
+        {
+            ECDsa dsa = (ECDsa)key.Key;
+            byte[] predictedMax = new byte[dsa.GetMaxSignatureSize(signatureFormat)];
+
+            Assert.True(
+                dsa.TrySignHash(hash, predictedMax, signatureFormat, out int written),
+                "TrySignHash with a GetMaxSignatureSize buffer");
+
+            if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+            {
+                // GetMaxSignatureSize should be exactly accurate for P1363.
+                Assert.Equal(predictedMax.Length, written);
+            }
+
+            if (written == predictedMax.Length)
+            {
+                return predictedMax;
+            }
+
+            return predictedMax.AsSpan(0, written).ToArray();
+        }
+
+        protected override bool VerifyHash(
+            KeyDescription key,
+            byte[] hash,
+            byte[] signature,
+            DSASignatureFormat signatureFormat)
+        {
+            ReadOnlySpan<byte> readOnlyHash = hash;
+            ReadOnlySpan<byte> readOnlySignature = signature;
+            return ((ECDsa)key.Key).VerifyHash(readOnlyHash, readOnlySignature, signatureFormat);
+        }
+
+        protected override byte[] SignData(
+            KeyDescription key,
+            byte[] data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            ECDsa dsa = (ECDsa)key.Key;
+            byte[] predictedMax = new byte[dsa.GetMaxSignatureSize(signatureFormat)];
+
+            Assert.True(
+                dsa.TrySignData(data, predictedMax, hashAlgorithm, signatureFormat, out int written),
+                "TrySignData with a GetMaxSignatureSize buffer");
+
+            if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
+            {
+                // GetMaxSignatureSize should be exactly accurate for P1363.
+                Assert.Equal(predictedMax.Length, written);
+            }
+
+            if (written == predictedMax.Length)
+            {
+                return predictedMax;
+            }
+
+            return predictedMax.AsSpan(0, written).ToArray();
+        }
+
+        protected override bool VerifyData(
+            KeyDescription key,
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            ReadOnlySpan<byte> readOnlyData = data;
+            ReadOnlySpan<byte> readOnlySignature = signature;
+
+            return ((ECDsa)key.Key).VerifyData(readOnlyData, readOnlySignature, hashAlgorithm, signatureFormat);
+        }
+
+        private static int GetExpectedSize(int fieldSizeInBits)
+        {
+            // In ECDSA field sizes aren't always byte-aligned (e.g. secp521r1).
+            int fullBytes = Math.DivRem(fieldSizeInBits, 8, out int spareBits) + 1;
+            int wiggle = 0;
+
+            if (spareBits == 1)
+            {
+                // If there's only one spare bit (e.g. 521r1), we have a 50% chance to
+                // drop a byte (then a 50% chance to gain it back), predict a byte loss.
+                wiggle = -1;
+            }
+            else if (spareBits == 0)
+            {
+                // If we're byte aligned, then if the high bit is set (~50%) we gain a padding
+                // byte, so predict a byte gain.
+                wiggle = 1;
+
+                // Also, as byte aligned, reduce the +1 from the original calculation
+                fullBytes--;
+            }
+
+            int field1 = 2 + GetDerLengthLength(fullBytes) + fullBytes;
+            int field2 = 2 + GetDerLengthLength(fullBytes + wiggle) + fullBytes + wiggle;
+            int payload = field1 + field2;
+            return 2 + GetDerLengthLength(payload) + payload;
+        }
+
+        [Fact]
+        public void Rfc23279TrySignHashUnderMax()
+        {
+            KeyDescription keyDescription = GetKey();
+            ECDsa key = (ECDsa)keyDescription.Key;
+
+            const DSASignatureFormat SignatureFormat = DSASignatureFormat.Rfc3279DerSequence;
+            const int RetryCount = 10;
+            byte[] hash = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+
+            int expectedSize = GetExpectedSize(keyDescription.FieldSizeInBits);
+            int maxSize = key.GetMaxSignatureSize(DSASignatureFormat.Rfc3279DerSequence);
+            Assert.True(expectedSize < maxSize, "expectedSize < maxSize");
+            byte[] signature = new byte[expectedSize];
+
+            for (int i = 0; i < RetryCount; i++)
+            {
+                if (key.TrySignHash(hash, signature, SignatureFormat, out int written))
+                {
+                    return;
+                }
+
+                Assert.Equal(0, written);
+            }
+
+            Assert.True(false, $"TrySignHash eventually succeeds with a {expectedSize}/{maxSize}-byte destination");
+        }
+
+        [Fact]
+        public void Rfc23279TrySignDataUnderMax()
+        {
+            KeyDescription keyDescription = GetKey();
+            ECDsa key = (ECDsa)keyDescription.Key;
+
+            const DSASignatureFormat SignatureFormat = DSASignatureFormat.Rfc3279DerSequence;
+            const int RetryCount = 10;
+            HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA1;
+
+            int expectedSize = GetExpectedSize(keyDescription.FieldSizeInBits);
+            int maxSize = key.GetMaxSignatureSize(DSASignatureFormat.Rfc3279DerSequence);
+            Assert.True(expectedSize < maxSize, "expectedSize < maxSize");
+            byte[] signature = new byte[expectedSize];
+
+            for (int i = 0; i < RetryCount; i++)
+            {
+                if (key.TrySignData(Array.Empty<byte>(), signature, hashAlgorithm, SignatureFormat, out int written))
+                {
+                    return;
+                }
+
+                Assert.Equal(0, written);
+            }
+
+            Assert.True(false, $"TrySignData eventually succeeds with a {expectedSize}/{maxSize}-byte destination");
+        }
+    }
+}
index 27889bb..46d1abc 100644 (file)
@@ -13,7 +13,7 @@ CryptoNative_EcDsaSign(const uint8_t* dgst, int32_t dgstlen, uint8_t* sig, int32
         return 0;
     }
 
-    unsigned int unsignedSigLength = Int32ToUint32(*siglen);
+    unsigned int unsignedSigLength = 0;
     int ret = ECDSA_sign(0, dgst, dgstlen, sig, &unsignedSigLength, key);
     *siglen = Uint32ToInt32(unsignedSigLength);
     return ret;
index ead364c..fe1cafe 100644 (file)
@@ -126,8 +126,11 @@ namespace System.Security.Cryptography
         public static System.Security.Cryptography.DSA Create(System.Security.Cryptography.DSAParameters parameters) { throw null; }
         public static new System.Security.Cryptography.DSA? Create(string algName) { throw null; }
         public abstract byte[] CreateSignature(byte[] rgbHash);
+        public byte[] CreateSignature(byte[] rgbHash, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual byte[] CreateSignatureCore(System.ReadOnlySpan<byte> hash, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public abstract System.Security.Cryptography.DSAParameters ExportParameters(bool includePrivateParameters);
         public override void FromXmlString(string xmlString) { }
+        public int GetMaxSignatureSize(System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         protected virtual byte[] HashData(byte[] data, int offset, int count, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
         protected virtual byte[] HashData(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
         public override void ImportEncryptedPkcs8PrivateKey(System.ReadOnlySpan<byte> passwordBytes, System.ReadOnlySpan<byte> source, out int bytesRead) { throw null; }
@@ -136,22 +139,40 @@ namespace System.Security.Cryptography
         public override void ImportPkcs8PrivateKey(System.ReadOnlySpan<byte> source, out int bytesRead) { throw null; }
         public override void ImportSubjectPublicKeyInfo(System.ReadOnlySpan<byte> source, out int bytesRead) { throw null; }
         public virtual byte[] SignData(byte[] data, int offset, int count, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public byte[] SignData(byte[] data, int offset, int count, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public byte[] SignData(byte[] data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public byte[] SignData(byte[] data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual byte[] SignData(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public byte[] SignData(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual byte[] SignDataCore(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual byte[] SignDataCore(System.ReadOnlySpan<byte> data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public override string ToXmlString(bool includePrivateParameters) { throw null; }
         public virtual bool TryCreateSignature(System.ReadOnlySpan<byte> hash, System.Span<byte> destination, out int bytesWritten) { throw null; }
+        public bool TryCreateSignature(System.ReadOnlySpan<byte> hash, System.Span<byte> destination, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
+        protected virtual bool TryCreateSignatureCore(System.ReadOnlySpan<byte> hash, System.Span<byte> destination, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
         public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan<byte> passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span<byte> destination, out int bytesWritten) { throw null; }
         public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan<char> password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span<byte> destination, out int bytesWritten) { throw null; }
         public override bool TryExportPkcs8PrivateKey(System.Span<byte> destination, out int bytesWritten) { throw null; }
         public override bool TryExportSubjectPublicKeyInfo(System.Span<byte> destination, out int bytesWritten) { throw null; }
         protected virtual bool TryHashData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; }
         public virtual bool TrySignData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; }
+        public bool TrySignData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
+        protected virtual bool TrySignDataCore(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
         public bool VerifyData(byte[] data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(byte[] data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual bool VerifyData(byte[] data, int offset, int count, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(byte[] data, int offset, int count, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual bool VerifyData(System.IO.Stream data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(System.IO.Stream data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual bool VerifyData(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual bool VerifyDataCore(System.IO.Stream data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual bool VerifyDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public abstract bool VerifySignature(byte[] rgbHash, byte[] rgbSignature);
+        public bool VerifySignature(byte[] rgbHash, byte[] rgbSignature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual bool VerifySignature(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> signature) { throw null; }
+        public bool VerifySignature(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual bool VerifySignatureCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
     }
     public partial struct DSAParameters
     {
@@ -172,6 +193,11 @@ namespace System.Security.Cryptography
         public override void SetKey(System.Security.Cryptography.AsymmetricAlgorithm key) { }
         public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature) { throw null; }
     }
+    public enum DSASignatureFormat
+    {
+        IeeeP1363FixedFieldConcatenation = 0,
+        Rfc3279DerSequence = 1,
+    }
     public partial class DSASignatureFormatter : System.Security.Cryptography.AsymmetricSignatureFormatter
     {
         public DSASignatureFormatter() { }
@@ -292,6 +318,7 @@ namespace System.Security.Cryptography
         public virtual System.Security.Cryptography.ECParameters ExportParameters(bool includePrivateParameters) { throw null; }
         public override void FromXmlString(string xmlString) { }
         public virtual void GenerateKey(System.Security.Cryptography.ECCurve curve) { }
+        public int GetMaxSignatureSize(System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         protected virtual byte[] HashData(byte[] data, int offset, int count, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
         protected virtual byte[] HashData(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
         public virtual void ImportECPrivateKey(System.ReadOnlySpan<byte> source, out int bytesRead) { throw null; }
@@ -301,9 +328,16 @@ namespace System.Security.Cryptography
         public override void ImportPkcs8PrivateKey(System.ReadOnlySpan<byte> source, out int bytesRead) { throw null; }
         public override void ImportSubjectPublicKeyInfo(System.ReadOnlySpan<byte> source, out int bytesRead) { throw null; }
         public virtual byte[] SignData(byte[] data, int offset, int count, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public byte[] SignData(byte[] data, int offset, int count, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual byte[] SignData(byte[] data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public byte[] SignData(byte[] data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual byte[] SignData(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public byte[] SignData(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual byte[] SignDataCore(System.IO.Stream data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual byte[] SignDataCore(System.ReadOnlySpan<byte> data, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public abstract byte[] SignHash(byte[] hash);
+        public byte[] SignHash(byte[] hash, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual byte[] SignHashCore(System.ReadOnlySpan<byte> hash, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public override string ToXmlString(bool includePrivateParameters) { throw null; }
         public virtual bool TryExportECPrivateKey(System.Span<byte> destination, out int bytesWritten) { throw null; }
         public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan<byte> passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span<byte> destination, out int bytesWritten) { throw null; }
@@ -312,13 +346,26 @@ namespace System.Security.Cryptography
         public override bool TryExportSubjectPublicKeyInfo(System.Span<byte> destination, out int bytesWritten) { throw null; }
         protected virtual bool TryHashData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; }
         public virtual bool TrySignData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; }
+        public bool TrySignData(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
+        protected virtual bool TrySignDataCore(System.ReadOnlySpan<byte> data, System.Span<byte> destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
         public virtual bool TrySignHash(System.ReadOnlySpan<byte> hash, System.Span<byte> destination, out int bytesWritten) { throw null; }
+        public bool TrySignHash(System.ReadOnlySpan<byte> hash, System.Span<byte> destination, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
+        protected virtual bool TrySignHashCore(System.ReadOnlySpan<byte> hash, System.Span<byte> destination, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; }
         public bool VerifyData(byte[] data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(byte[] data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual bool VerifyData(byte[] data, int offset, int count, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(byte[] data, int offset, int count, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public bool VerifyData(System.IO.Stream data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(System.IO.Stream data, byte[] signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual bool VerifyData(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; }
+        public bool VerifyData(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual bool VerifyDataCore(System.IO.Stream data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual bool VerifyDataCore(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public abstract bool VerifyHash(byte[] hash, byte[] signature);
+        public bool VerifyHash(byte[] hash, byte[] signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
         public virtual bool VerifyHash(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> signature) { throw null; }
+        public bool VerifyHash(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
+        protected virtual bool VerifyHashCore(System.ReadOnlySpan<byte> hash, System.ReadOnlySpan<byte> signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; }
     }
     public partial struct ECParameters
     {
index 593d64c..8788568 100644 (file)
   <data name="Cryptography_UnknownPaddingMode" xml:space="preserve">
     <value>Unknown padding mode used.</value>
   </data>
+  <data name="Cryptography_UnknownSignatureFormat" xml:space="preserve">
+    <value>The signature format '{0}' is unknown.</value>
+  </data>
   <data name="Cryptography_UnexpectedTransformTruncation" xml:space="preserve">
     <value>CNG provider unexpectedly terminated encryption or decryption prematurely.</value>
   </data>
index 61c8b31..bc99913 100644 (file)
@@ -6,7 +6,7 @@
     <TargetFrameworks>$(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS</TargetFrameworks>
     <Nullable>enable</Nullable>
   </PropertyGroup>
-  <Import Project="$(CommonPath)System\Security\Cryptography\Asn1\AsnXml.targets"/>
+  <Import Project="$(CommonPath)System\Security\Cryptography\Asn1\AsnXml.targets" />
   <Import Project="$(CommonPath)System\Security\Cryptography\Asn1Reader\System.Security.Cryptography.Asn1Reader.Shared.projitems" />
   <ItemGroup>
     <Compile Include="Internal\Cryptography\AesImplementation.cs" />
@@ -30,6 +30,7 @@
     <Compile Include="System\Security\Cryptography\CryptoConfig.cs" />
     <Compile Include="System\Security\Cryptography\DeriveBytes.cs" />
     <Compile Include="System\Security\Cryptography\DES.cs" />
+    <Compile Include="System\Security\Cryptography\DSASignatureFormat.cs" />
     <Compile Include="System\Security\Cryptography\DSA.cs" />
     <Compile Include="System\Security\Cryptography\DSA.Xml.cs" />
     <Compile Include="System\Security\Cryptography\DSAParameters.cs" />
@@ -85,6 +86,9 @@
     <Compile Include="System\Security\Cryptography\SignatureDescription.cs" />
     <Compile Include="System\Security\Cryptography\TripleDES.cs" />
     <Compile Include="System\Security\Cryptography\XmlKeyHelper.cs" />
+    <Compile Include="$(CommonPath)Internal\Cryptography\AsymmetricAlgorithmHelpers.Der.cs">
+      <Link>Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Der.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Internal\Cryptography\BasicSymmetricCipher.cs">
       <Link>Internal\Cryptography\BasicSymmetricCipher.cs</Link>
     </Compile>
     <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs">
       <Link>Common\Interop\Unix\Interop.Libraries.cs</Link>
     </Compile>
-    <Compile Include="$(CommonPath)Internal\Cryptography\AsymmetricAlgorithmHelpers.Der.cs">
-      <Link>Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Der.cs</Link>
-    </Compile>
     <Compile Include="$(CommonPath)Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs">
       <Link>Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs</Link>
     </Compile>
index 23391a0..69b260c 100644 (file)
@@ -5,8 +5,6 @@
 using System.Buffers;
 using System.Diagnostics;
 using System.IO;
-using System.Numerics;
-using System.Runtime.InteropServices;
 using System.Security.Cryptography.Asn1;
 using Internal.Cryptography;
 
@@ -14,6 +12,14 @@ namespace System.Security.Cryptography
 {
     public abstract partial class DSA : AsymmetricAlgorithm
     {
+        // As of FIPS 186-4 the maximum Q size is 256 bits (32 bytes).
+        // The DER signature format thus maxes out at 2 + 3 + 33 + 3 + 33 => 74 bytes.
+        // So 128 should always work.
+        private const int SignatureStackSize = 128;
+        // The biggest supported hash algorithm is SHA-2-512, which is only 64 bytes.
+        // One power of two bigger should cover most unknown algorithms, too.
+        private const int HashBufferStackSize = 128;
+
         public abstract DSAParameters ExportParameters(bool includePrivateParameters);
 
         public abstract void ImportParameters(DSAParameters parameters);
@@ -76,32 +82,210 @@ namespace System.Security.Cryptography
         public byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm)
         {
             if (data == null)
-            {
                 throw new ArgumentNullException(nameof(data));
-            }
+            // hashAlgorithm is verified in the overload
+
             return SignData(data, 0, data.Length, hashAlgorithm);
         }
 
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The DSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        public byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm, DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return SignDataCore(data, hashAlgorithm, signatureFormat);
+        }
+
         public virtual byte[] SignData(byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm)
         {
-            if (data == null) { throw new ArgumentNullException(nameof(data)); }
-            if (offset < 0 || offset > data.Length) { throw new ArgumentOutOfRangeException(nameof(offset)); }
-            if (count < 0 || count > data.Length - offset) { throw new ArgumentOutOfRangeException(nameof(count)); }
-            if (string.IsNullOrEmpty(hashAlgorithm.Name)) { throw HashAlgorithmNameNullOrEmpty(); }
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (offset < 0 || offset > data.Length)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (count < 0 || count > data.Length - offset)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
 
             byte[] hash = HashData(data, offset, count, hashAlgorithm);
             return CreateSignature(hash);
         }
 
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="offset">The offset into <paramref name="data"/> at which to begin hashing.</param>
+        /// <param name="count">The number of bytes to read from <paramref name="data"/>.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The DSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="count" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> + <paramref name="count"/> - 1 results in an index that is
+        ///   beyond the upper bound of <paramref name="data"/>.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        public byte[] SignData(
+            byte[] data,
+            int offset,
+            int count,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (offset < 0 || offset > data.Length)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (count < 0 || count > data.Length - offset)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return SignDataCore(new ReadOnlySpan<byte>(data, offset, count), hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The DSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        protected virtual byte[] SignDataCore(
+            ReadOnlySpan<byte> data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            Span<byte> signature = stackalloc byte[SignatureStackSize];
+
+            if (TrySignDataCore(data, signature, hashAlgorithm, signatureFormat, out int bytesWritten))
+            {
+                return signature.Slice(0, bytesWritten).ToArray();
+            }
+
+            // If that didn't work, fall back on older approaches.
+
+            byte[] hash = HashSpanToArray(data, hashAlgorithm);
+            byte[] sig = CreateSignature(hash);
+            return AsymmetricAlgorithmHelpers.ConvertFromIeeeP1363Signature(sig, signatureFormat);
+        }
+
         public virtual byte[] SignData(Stream data, HashAlgorithmName hashAlgorithm)
         {
-            if (data == null) { throw new ArgumentNullException(nameof(data)); }
-            if (string.IsNullOrEmpty(hashAlgorithm.Name)) { throw HashAlgorithmNameNullOrEmpty(); }
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
 
             byte[] hash = HashData(data, hashAlgorithm);
             return CreateSignature(hash);
         }
 
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The DSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        public byte[] SignData(Stream data, HashAlgorithmName hashAlgorithm, DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return SignDataCore(data, hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The DSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        protected virtual byte[] SignDataCore(Stream data, HashAlgorithmName hashAlgorithm, DSASignatureFormat signatureFormat)
+        {
+            byte[] hash = HashData(data, hashAlgorithm);
+            return CreateSignatureCore(hash, signatureFormat);
+        }
+
         public bool VerifyData(byte[] data, byte[] signature, HashAlgorithmName hashAlgorithm)
         {
             if (data == null)
@@ -114,75 +298,238 @@ namespace System.Security.Cryptography
 
         public virtual bool VerifyData(byte[] data, int offset, int count, byte[] signature, HashAlgorithmName hashAlgorithm)
         {
-            if (data == null) { throw new ArgumentNullException(nameof(data)); }
-            if (offset < 0 || offset > data.Length) { throw new ArgumentOutOfRangeException(nameof(offset)); }
-            if (count < 0 || count > data.Length - offset) { throw new ArgumentOutOfRangeException(nameof(count)); }
-            if (signature == null) { throw new ArgumentNullException(nameof(signature)); }
-            if (string.IsNullOrEmpty(hashAlgorithm.Name)) { throw HashAlgorithmNameNullOrEmpty(); }
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (offset < 0 || offset > data.Length)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (count < 0 || count > data.Length - offset)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
 
             byte[] hash = HashData(data, offset, count, hashAlgorithm);
             return VerifySignature(hash, signature);
         }
 
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">An array that contains the signed data.</param>
+        /// <param name="offset">The starting index of the signed portion of <paramref name="data"/>.</param>
+        /// <param name="count">The number of bytes in <paramref name="data"/> that were signed.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> or <paramref name="signature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="count" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> + <paramref name="count"/> - 1 results in an index that is
+        ///   beyond the upper bound of <paramref name="data"/>.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(
+            byte[] data,
+            int offset,
+            int count,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (offset < 0 || offset > data.Length)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (count < 0 || count > data.Length - offset)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(new ReadOnlySpan<byte>(data, offset, count), signature, hashAlgorithm, signatureFormat);
+        }
+
         public virtual bool VerifyData(Stream data, byte[] signature, HashAlgorithmName hashAlgorithm)
         {
-            if (data == null) { throw new ArgumentNullException(nameof(data)); }
-            if (signature == null) { throw new ArgumentNullException(nameof(signature)); }
-            if (string.IsNullOrEmpty(hashAlgorithm.Name)) { throw HashAlgorithmNameNullOrEmpty(); }
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
 
             byte[] hash = HashData(data, hashAlgorithm);
             return VerifySignature(hash, signature);
         }
 
-        public virtual bool TryCreateSignature(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
+        /// <summary>
+        ///   Creates the DSA signature for the specified hash value in the indicated format.
+        /// </summary>
+        /// <param name="rgbHash">The hash value to sign.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The DSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="rgbHash"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        public byte[] CreateSignature(byte[] rgbHash, DSASignatureFormat signatureFormat)
         {
-            byte[] sig = CreateSignature(hash.ToArray());
-            if (sig.Length <= destination.Length)
-            {
-                new ReadOnlySpan<byte>(sig).CopyTo(destination);
-                bytesWritten = sig.Length;
-                return true;
-            }
-            else
-            {
-                bytesWritten = 0;
-                return false;
-            }
+            if (rgbHash == null)
+                throw new ArgumentNullException(nameof(rgbHash));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return CreateSignatureCore(rgbHash, signatureFormat);
         }
 
-        protected virtual bool TryHashData(ReadOnlySpan<byte> data, Span<byte> destination, HashAlgorithmName hashAlgorithm, out int bytesWritten)
+        /// <summary>
+        ///   Creates the DSA signature for the specified hash value in the indicated format.
+        /// </summary>
+        /// <param name="hash">The hash value to sign.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The DSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        protected virtual byte[] CreateSignatureCore(ReadOnlySpan<byte> hash, DSASignatureFormat signatureFormat)
         {
-            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
-            byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
-            try
+            Span<byte> signature = stackalloc byte[SignatureStackSize];
+
+            if (TryCreateSignatureCore(hash, signature, signatureFormat, out int bytesWritten))
             {
-                data.CopyTo(array);
-                byte[] hash = HashData(array, 0, data.Length, hashAlgorithm);
-                if (destination.Length >= hash.Length)
-                {
-                    new ReadOnlySpan<byte>(hash).CopyTo(destination);
-                    bytesWritten = hash.Length;
-                    return true;
-                }
-                else
-                {
-                    bytesWritten = 0;
-                    return false;
-                }
+                return signature.Slice(0, bytesWritten).ToArray();
             }
-            finally
+
+            // If that didn't work, fall back to the older overload and convert formats.
+            byte[] sig = CreateSignature(hash.ToArray());
+            return AsymmetricAlgorithmHelpers.ConvertFromIeeeP1363Signature(sig, signatureFormat);
+        }
+
+        public virtual bool TryCreateSignature(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
+            => TryCreateSignatureCore(hash, destination, DSASignatureFormat.IeeeP1363FixedFieldConcatenation, out bytesWritten);
+
+        /// <summary>
+        ///   Attempts to create the DSA signature for the specified hash value in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="hash">The hash value to sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        public bool TryCreateSignature(
+            ReadOnlySpan<byte> hash,
+            Span<byte> destination,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
+        {
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return TryCreateSignatureCore(hash, destination, signatureFormat, out bytesWritten);
+        }
+
+        /// <summary>
+        ///   Attempts to create the DSA signature for the specified hash value in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="hash">The hash value to sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        protected virtual bool TryCreateSignatureCore(
+            ReadOnlySpan<byte> hash,
+            Span<byte> destination,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
+        {
+            // This method is expected to be overriden with better implementation
+
+            // The only available implementation here is abstract method, use it
+            byte[] sig = CreateSignature(hash.ToArray());
+
+            if (signatureFormat != DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
             {
-                Array.Clear(array, 0, data.Length);
-                ArrayPool<byte>.Shared.Return(array);
+                sig = AsymmetricAlgorithmHelpers.ConvertFromIeeeP1363Signature(sig, signatureFormat);
             }
+
+            return Helpers.TryCopyToDestination(sig, destination, out bytesWritten);
+        }
+
+        protected virtual bool TryHashData(
+            ReadOnlySpan<byte> data,
+            Span<byte> destination,
+            HashAlgorithmName hashAlgorithm,
+            out int bytesWritten)
+        {
+            byte[] hash = HashSpanToArray(data, hashAlgorithm);
+            return Helpers.TryCopyToDestination(hash, destination, out bytesWritten);
         }
 
-        public virtual bool TrySignData(ReadOnlySpan<byte> data, Span<byte> destination, HashAlgorithmName hashAlgorithm, out int bytesWritten)
+        public virtual bool TrySignData(
+            ReadOnlySpan<byte> data,
+            Span<byte> destination,
+            HashAlgorithmName hashAlgorithm,
+            out int bytesWritten)
         {
             if (string.IsNullOrEmpty(hashAlgorithm.Name))
-            {
                 throw HashAlgorithmNameNullOrEmpty();
-            }
 
             if (TryHashData(data, destination, hashAlgorithm, out int hashLength) &&
                 TryCreateSignature(destination.Slice(0, hashLength), destination, out bytesWritten))
@@ -194,46 +541,379 @@ namespace System.Security.Cryptography
             return false;
         }
 
-        public virtual bool VerifyData(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature, HashAlgorithmName hashAlgorithm)
+        /// <summary>
+        ///   Attempts to create the DSA signature for the specified data in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="data">The data to hash and sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        public bool TrySignData(
+            ReadOnlySpan<byte> data,
+            Span<byte> destination,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
         {
             if (string.IsNullOrEmpty(hashAlgorithm.Name))
-            {
                 throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return TrySignDataCore(data, destination, hashAlgorithm, signatureFormat, out bytesWritten);
+        }
+
+        /// <summary>
+        ///   Attempts to create the DSA signature for the specified data in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="data">The data to hash and sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        protected virtual bool TrySignDataCore(
+            ReadOnlySpan<byte> data,
+            Span<byte> destination,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
+        {
+            Span<byte> tmp = stackalloc byte[HashBufferStackSize];
+            ReadOnlySpan<byte> hash = HashSpanToTmp(data, hashAlgorithm, tmp);
+
+            return TryCreateSignatureCore(hash, destination, signatureFormat, out bytesWritten);
+        }
+
+        public virtual bool VerifyData(
+            ReadOnlySpan<byte> data,
+            ReadOnlySpan<byte> signature,
+            HashAlgorithmName hashAlgorithm)
+        {
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+
+            return VerifyDataCore(data, signature, hashAlgorithm, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> or <paramref name="signature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(
+            byte[] data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(data, signature, hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> or <paramref name="signature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(
+            Stream data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(data, signature, hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        protected virtual bool VerifyDataCore(
+            Stream data,
+            ReadOnlySpan<byte> signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            byte[] hash = HashData(data, hashAlgorithm);
+            return VerifySignatureCore(hash, signature, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(
+            ReadOnlySpan<byte> data,
+            ReadOnlySpan<byte> signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw HashAlgorithmNameNullOrEmpty();
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(data, signature, hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        protected virtual bool VerifyDataCore(
+            ReadOnlySpan<byte> data,
+            ReadOnlySpan<byte> signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            Span<byte> tmp = stackalloc byte[HashBufferStackSize];
+            ReadOnlySpan<byte> hash = HashSpanToTmp(data, hashAlgorithm, tmp);
+
+            return VerifySignatureCore(hash, signature, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided hash.
+        /// </summary>
+        /// <param name="rgbHash">The signed hash.</param>
+        /// <param name="rgbSignature">The signature to verify.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="rgbSignature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="rgbHash"/> or <paramref name="rgbSignature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the verification operation.
+        /// </exception>
+        public bool VerifySignature(byte[] rgbHash, byte[] rgbSignature, DSASignatureFormat signatureFormat)
+        {
+            if (rgbHash == null)
+                throw new ArgumentNullException(nameof(rgbHash));
+            if (rgbSignature == null)
+                throw new ArgumentNullException(nameof(rgbSignature));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifySignatureCore(rgbHash, rgbSignature, signatureFormat);
+        }
+
+        public virtual bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
+            VerifySignature(hash.ToArray(), signature.ToArray());
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided hash.
+        /// </summary>
+        /// <param name="hash">The signed hash.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the verification operation.
+        /// </exception>
+        public bool VerifySignature(
+            ReadOnlySpan<byte> hash,
+            ReadOnlySpan<byte> signature,
+            DSASignatureFormat signatureFormat)
+        {
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifySignatureCore(hash, signature, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided hash.
+        /// </summary>
+        /// <param name="hash">The signed hash.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the verification operation.
+        /// </exception>
+        protected virtual bool VerifySignatureCore(
+            ReadOnlySpan<byte> hash,
+            ReadOnlySpan<byte> signature,
+            DSASignatureFormat signatureFormat)
+        {
+            // This method is expected to be overriden with better implementation
+
+            byte[]? sig = this.ConvertSignatureToIeeeP1363(signatureFormat, signature);
+
+            // If the signature failed normalization to IEEE P1363 then it
+            // obviously doesn't verify.
+            if (sig == null)
+            {
+                return false;
+            }
+
+            // The only available implementation here is abstract method, use it.
+            // Since it requires an exactly-sized array, skip pooled arrays.
+            return VerifySignature(hash, sig);
+        }
+
+        private ReadOnlySpan<byte> HashSpanToTmp(
+            ReadOnlySpan<byte> data,
+            HashAlgorithmName hashAlgorithm,
+            Span<byte> tmp)
+        {
+            Debug.Assert(tmp.Length == HashBufferStackSize);
+
+            if (TryHashData(data, tmp, hashAlgorithm, out int hashSize))
+            {
+                return tmp.Slice(0, hashSize);
             }
 
-            // The biggest hash algorithm supported is SHA512, which is only 64 bytes (512 bits).
-            // So this should realistically never hit the fallback
-            // (it'd require a derived type to add support for a different hash algorithm, and that
-            // algorithm to have a large output.)
-            Span<byte> buf = stackalloc byte[128];
-            ReadOnlySpan<byte> hash = stackalloc byte[0];
+            // This is not expected, but a poor virtual implementation of TryHashData,
+            // or an exotic new algorithm, will hit this fallback.
+            return HashSpanToArray(data, hashAlgorithm);
+        }
 
-            if (TryHashData(data, buf, hashAlgorithm, out int hashLength))
+        private byte[] HashSpanToArray(ReadOnlySpan<byte> data, HashAlgorithmName hashAlgorithm)
+        {
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
+            byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
+            bool returnArray = false;
+            try
             {
-                hash = buf.Slice(0, hashLength);
+                data.CopyTo(array);
+                byte[] ret = HashData(array, 0, data.Length, hashAlgorithm);
+                returnArray = true;
+                return ret;
             }
-            else
+            finally
             {
-                // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
-                byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
-                try
-                {
-                    data.CopyTo(array);
-                    hash = HashData(array, 0, data.Length, hashAlgorithm);
-                }
-                finally
+                Array.Clear(array, 0, data.Length);
+
+                if (returnArray)
                 {
-                    Array.Clear(array, 0, data.Length);
                     ArrayPool<byte>.Shared.Return(array);
                 }
             }
-
-            return VerifySignature(hash, signature);
         }
 
-        public virtual bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
-            VerifySignature(hash.ToArray(), signature.ToArray());
-
         private static Exception DerivedClassMustOverride() =>
             new NotImplementedException(SR.NotSupported_SubclassOverride);
 
@@ -419,5 +1099,31 @@ namespace System.Security.Cryptography
             ImportParameters(key);
             bytesRead = localRead;
         }
+
+        /// <summary>
+        ///   Gets the largest size, in bytes, for a signature produced by this key in the indicated format.
+        /// </summary>
+        /// <param name="signatureFormat">The encoding format for a signature.</param>
+        /// <returns>
+        ///   The largest size, in bytes, for a signature produced by this key in the indicated format.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        public int GetMaxSignatureSize(DSASignatureFormat signatureFormat)
+        {
+            DSAParameters dsaParameters = ExportParameters(false);
+            int qLength = dsaParameters.Q!.Length;
+
+            switch (signatureFormat)
+            {
+                case DSASignatureFormat.IeeeP1363FixedFieldConcatenation:
+                    return qLength * 2;
+                case DSASignatureFormat.Rfc3279DerSequence:
+                    return AsymmetricAlgorithmHelpers.GetMaxDerSignatureSize(fieldSizeBits: qLength * 8);
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(signatureFormat));
+            }
+        }
     }
 }
diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSASignatureFormat.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSASignatureFormat.cs
new file mode 100644 (file)
index 0000000..3b1f156
--- /dev/null
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Security.Cryptography
+{
+    /// <summary>
+    /// Specifies the data format for signatures with the DSA family of algorithms.
+    /// </summary>
+    public enum DSASignatureFormat
+    {
+        /// <summary>
+        ///   The signature format from IEEE P1363, which produces a fixed size signature for a given key.
+        /// </summary>
+        /// <remarks>
+        ///   This signature format encodes the `(r, s)` tuple as the concatenation of the
+        ///   big-endian representation of `r` and the big-endian representation of `s`, each
+        ///   value encoding using the number of bytes required for encoding the maximum integer
+        ///   value in the key's mathematical field. For example, an ECDSA signature from the curve
+        ///   `secp521r1`, a 521-bit field, will encode each of `r` and `s` as 66 bytes, producing
+        ///   a signature output of 132 bytes.
+        /// </remarks>
+        IeeeP1363FixedFieldConcatenation,
+
+        /// <summary>
+        ///   The signature format from IETF RFC 3279, which produces a variably-sized signature.
+        /// </summary>
+        /// <remarks>
+        ///   This signature format encodes the `(r, s)` tuple as the DER encoding of
+        ///   `SEQUENCE(INTEGER(r), INTEGER(s))`. Because the length of a DER INTEGER encoding
+        ///   varies according to the value being encoded, this signature format does not produce
+        ///   a consistent signature length. Signatures in this format always start with `0x30`,
+        ///   and on average are 7 bytes longer than signatures in the
+        ///   <see cref="IeeeP1363FixedFieldConcatenation"/> format.
+        /// </remarks>
+        Rfc3279DerSequence,
+    }
+
+    internal static class DSASignatureFormatHelpers
+    {
+        internal static bool IsKnownValue(this DSASignatureFormat signatureFormat) =>
+            signatureFormat >= DSASignatureFormat.IeeeP1363FixedFieldConcatenation &&
+            signatureFormat <= DSASignatureFormat.Rfc3279DerSequence;
+
+        internal static Exception CreateUnknownValueException(DSASignatureFormat signatureFormat) =>
+            new ArgumentOutOfRangeException(
+                nameof(signatureFormat),
+                SR.Format(SR.Cryptography_UnknownSignatureFormat, signatureFormat));
+    }
+}
index 9d04547..01d1109 100644 (file)
@@ -2,7 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using Internal.Cryptography;
 using System.Buffers;
+using System.Diagnostics;
 using System.IO;
 using System.Security.Cryptography.Asn1;
 
@@ -10,6 +12,12 @@ namespace System.Security.Cryptography
 {
     public abstract partial class ECDsa : AsymmetricAlgorithm
     {
+        // secp521r1 maxes out at 139 bytes in the DER format, so 256 should always be enough
+        private const int SignatureStackBufSize = 256;
+        // The biggest supported hash algorithm is SHA-2-512, which is only 64 bytes.
+        // One power of two bigger should cover most unknown algorithms, too.
+        private const int HashBufferStackSize = 128;
+
         private static readonly string[] s_validOids =
         {
             Oids.EcPublicKey,
@@ -71,6 +79,7 @@ namespace System.Security.Cryptography
         {
             if (data == null)
                 throw new ArgumentNullException(nameof(data));
+            // hashAlgorithm is verified in the overload
 
             return SignData(data, 0, data.Length, hashAlgorithm);
         }
@@ -90,21 +99,369 @@ namespace System.Security.Cryptography
             return SignHash(hash);
         }
 
-        public virtual bool TrySignData(ReadOnlySpan<byte> data, Span<byte> destination, HashAlgorithmName hashAlgorithm, out int bytesWritten)
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="offset">The offset into <paramref name="data"/> at which to begin hashing.</param>
+        /// <param name="count">The number of bytes to read from <paramref name="data"/>.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The ECDSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="count" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> + <paramref name="count"/> - 1 results in an index that is
+        ///   beyond the upper bound of <paramref name="data"/>.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        public byte[] SignData(
+            byte[] data,
+            int offset,
+            int count,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
         {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (offset < 0 || offset > data.Length)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (count < 0 || count > data.Length - offset)
+                throw new ArgumentOutOfRangeException(nameof(count));
             if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return SignDataCore(new ReadOnlySpan<byte>(data, offset, count), hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The ECDSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        protected virtual byte[] SignDataCore(
+            ReadOnlySpan<byte> data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            Span<byte> signature = stackalloc byte[SignatureStackBufSize];
+            int maxSignatureSize = GetMaxSignatureSize(signatureFormat);
+            byte[]? rented = null;
+            bool returnArray = false;
+            int bytesWritten = 0;
+
+            if (maxSignatureSize > signature.Length)
             {
+                // Use the shared pool because the buffer is passed out.
+                rented = ArrayPool<byte>.Shared.Rent(maxSignatureSize);
+                signature = rented;
+            }
+
+            try
+            {
+                if (!TrySignDataCore(data, signature, hashAlgorithm, signatureFormat, out bytesWritten))
+                {
+                    Debug.Fail($"GetMaxSignatureSize returned insufficient size for format {signatureFormat}");
+                    throw new CryptographicException();
+                }
+
+                byte[] ret = signature.Slice(0, bytesWritten).ToArray();
+                returnArray = true;
+                return ret;
+            }
+            finally
+            {
+                if (rented != null)
+                {
+                    CryptographicOperations.ZeroMemory(rented.AsSpan(0, bytesWritten));
+
+                    if (returnArray)
+                    {
+                        ArrayPool<byte>.Shared.Return(rented);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The ECDSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        public byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm, DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
                 throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return SignDataCore(data, hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The ECDSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        public byte[] SignData(Stream data, HashAlgorithmName hashAlgorithm, DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return SignDataCore(data, hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Computes the hash value of the specified data and signs it using the specified signature format.
+        /// </summary>
+        /// <param name="data">The data to sign.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The ECDSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or signing operation.
+        /// </exception>
+        protected virtual byte[] SignDataCore(
+            Stream data,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            byte[] hash = HashData(data, hashAlgorithm);
+            return SignHashCore(hash, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Computes the ECDSA signature for the specified hash value in the indicated format.
+        /// </summary>
+        /// <param name="hash">The hash value to sign.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The ECDSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="hash"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        public byte[] SignHash(byte[] hash, DSASignatureFormat signatureFormat)
+        {
+            if (hash == null)
+                throw new ArgumentNullException(nameof(hash));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return SignHashCore(hash, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Computes the ECDSA signature for the specified hash value in the indicated format.
+        /// </summary>
+        /// <param name="hash">The hash value to sign.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <returns>
+        ///   The ECDSA signature for the specified data.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        protected virtual byte[] SignHashCore(ReadOnlySpan<byte> hash, DSASignatureFormat signatureFormat)
+        {
+            Span<byte> signature = stackalloc byte[SignatureStackBufSize];
+            int maxSignatureSize = GetMaxSignatureSize(signatureFormat);
+            byte[]? rented = null;
+            bool returnArray = false;
+            int bytesWritten = 0;
+
+            if (maxSignatureSize > signature.Length)
+            {
+                // Use the shared pool because the buffer is passed out.
+                rented = ArrayPool<byte>.Shared.Rent(maxSignatureSize);
+                signature = rented;
             }
 
-            if (TryHashData(data, destination, hashAlgorithm, out int hashLength) &&
-                TrySignHash(destination.Slice(0, hashLength), destination, out bytesWritten))
+            try
             {
-                return true;
+                if (!TrySignHashCore(hash, signature, signatureFormat, out bytesWritten))
+                {
+                    Debug.Fail($"GetMaxSignatureSize returned insufficient size for format {signatureFormat}");
+                    throw new CryptographicException();
+                }
+
+                byte[] ret = signature.Slice(0, bytesWritten).ToArray();
+                returnArray = true;
+                return ret;
             }
+            finally
+            {
+                if (rented != null)
+                {
+                    CryptographicOperations.ZeroMemory(rented.AsSpan(0, bytesWritten));
 
-            bytesWritten = 0;
-            return false;
+                    if (returnArray)
+                    {
+                        ArrayPool<byte>.Shared.Return(rented);
+                    }
+                }
+            }
+        }
+
+        public virtual bool TrySignData(
+            ReadOnlySpan<byte> data,
+            Span<byte> destination,
+            HashAlgorithmName hashAlgorithm,
+            out int bytesWritten)
+        {
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+
+            Span<byte> hashTmp = stackalloc byte[HashBufferStackSize];
+            ReadOnlySpan<byte> hash = HashSpanToTmp(data, hashAlgorithm, hashTmp);
+            return TrySignHash(hash, destination, out bytesWritten);
+        }
+
+        /// <summary>
+        ///   Attempts to create the ECDSA signature for the specified data in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="data">The data to hash and sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        public bool TrySignData(
+            ReadOnlySpan<byte> data,
+            Span<byte> destination,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
+        {
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return TrySignDataCore(data, destination, hashAlgorithm, signatureFormat, out bytesWritten);
+        }
+
+        /// <summary>
+        ///   Attempts to create the ECDSA signature for the specified data in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="data">The data to hash and sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="hashAlgorithm">The hash algorithm to use to create the hash value.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        protected virtual bool TrySignDataCore(
+            ReadOnlySpan<byte> data,
+            Span<byte> destination,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
+        {
+            Span<byte> hashTmp = stackalloc byte[HashBufferStackSize];
+            ReadOnlySpan<byte> hash = HashSpanToTmp(data, hashAlgorithm, hashTmp);
+
+            return TrySignHashCore(hash, destination, signatureFormat, out bytesWritten);
         }
 
         public virtual byte[] SignData(Stream data, HashAlgorithmName hashAlgorithm)
@@ -143,41 +500,181 @@ namespace System.Security.Cryptography
             return VerifyHash(hash, signature);
         }
 
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">An array that contains the signed data.</param>
+        /// <param name="offset">The starting index of the signed portion of <paramref name="data"/>.</param>
+        /// <param name="count">The number of bytes in <paramref name="data"/> that were signed.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> or <paramref name="signature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="count" /> is less than zero.
+        ///
+        ///   -or-
+        ///
+        ///   <paramref name="offset" /> + <paramref name="count"/> - 1 results in an index that is
+        ///   beyond the upper bound of <paramref name="data"/>.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(
+            byte[] data,
+            int offset,
+            int count,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (offset < 0 || offset > data.Length)
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            if (count < 0 || count > data.Length - offset)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(
+                new ReadOnlySpan<byte>(data, offset, count),
+                signature,
+                hashAlgorithm,
+                signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> or <paramref name="signature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(byte[] data, byte[] signature, HashAlgorithmName hashAlgorithm, DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(data, signature, hashAlgorithm, signatureFormat);
+        }
+
         public virtual bool VerifyData(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature, HashAlgorithmName hashAlgorithm)
         {
             if (string.IsNullOrEmpty(hashAlgorithm.Name))
-            {
                 throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
-            }
 
-            // The biggest hash algorithm supported is SHA512, which is only 64 bytes (512 bits).
-            // So this should realistically never hit the fallback
-            // (it'd require a derived type to add support for a different hash algorithm, and that
-            // algorithm to have a large output.)
-            Span<byte> buf = stackalloc byte[128];
-            ReadOnlySpan<byte> hash = stackalloc byte[0];
+            Span<byte> hashTmp = stackalloc byte[HashBufferStackSize];
+            ReadOnlySpan<byte> hash = HashSpanToTmp(data, hashAlgorithm, hashTmp);
+            return VerifyHash(hash, signature);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(
+            ReadOnlySpan<byte> data,
+            ReadOnlySpan<byte> signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(data, signature, hashAlgorithm, signatureFormat);
+        }
 
-            if (TryHashData(data, buf, hashAlgorithm, out int hashLength))
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        protected virtual bool VerifyDataCore(
+            ReadOnlySpan<byte> data,
+            ReadOnlySpan<byte> signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            // SHA-2-512 is the biggest hash we know about.
+            Span<byte> hashSpan = stackalloc byte[512 / 8];
+
+            if (TryHashData(data, hashSpan, hashAlgorithm, out int bytesWritten))
             {
-                hash = buf.Slice(0, hashLength);
+                hashSpan = hashSpan.Slice(0, bytesWritten);
             }
             else
             {
-                // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
-                byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
-                try
-                {
-                    data.CopyTo(array);
-                    hash = HashData(array, 0, data.Length, hashAlgorithm);
-                }
-                finally
-                {
-                    Array.Clear(array, 0, data.Length);
-                    ArrayPool<byte>.Shared.Return(array);
-                }
+                // TryHashData didn't work, the algorithm must be exotic,
+                // call the array-returning variant.
+                hashSpan = HashData(data.ToArray(), 0, data.Length, hashAlgorithm);
             }
 
-            return VerifyHash(hash, signature);
+            return VerifyHashCore(hashSpan, signature, signatureFormat);
         }
 
         public bool VerifyData(Stream data, byte[] signature, HashAlgorithmName hashAlgorithm)
@@ -193,6 +690,69 @@ namespace System.Security.Cryptography
             return VerifyHash(hash, signature);
         }
 
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="data"/> or <paramref name="signature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm"/> has a <see langword="null"/> or empty <see cref="HashAlgorithmName.Name"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        public bool VerifyData(
+            Stream data,
+            byte[] signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (string.IsNullOrEmpty(hashAlgorithm.Name))
+                throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyDataCore(data, signature, hashAlgorithm, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided data.
+        /// </summary>
+        /// <param name="data">The signed data.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="hashAlgorithm">The hash algorithm used to hash the data for the verification process.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the hashing or verification operation.
+        /// </exception>
+        protected virtual bool VerifyDataCore(
+            Stream data,
+            ReadOnlySpan<byte> signature,
+            HashAlgorithmName hashAlgorithm,
+            DSASignatureFormat signatureFormat)
+        {
+            byte[] hash = HashData(data, hashAlgorithm);
+            return VerifyHashCore(hash, signature, signatureFormat);
+        }
+
         public abstract byte[] SignHash(byte[] hash);
         public abstract bool VerifyHash(byte[] hash, byte[] signature);
 
@@ -213,10 +773,14 @@ namespace System.Security.Cryptography
         {
             // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
             byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
+            bool returnArray = false;
+
             try
             {
                 data.CopyTo(array);
                 byte[] hash = HashData(array, 0, data.Length, hashAlgorithm);
+                returnArray = true;
+
                 if (hash.Length <= destination.Length)
                 {
                     new ReadOnlySpan<byte>(hash).CopyTo(destination);
@@ -232,28 +796,211 @@ namespace System.Security.Cryptography
             finally
             {
                 Array.Clear(array, 0, data.Length);
-                ArrayPool<byte>.Shared.Return(array);
+
+                if (returnArray)
+                {
+                    ArrayPool<byte>.Shared.Return(array);
+                }
             }
         }
 
         public virtual bool TrySignHash(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
+            => TrySignHashCore(hash, destination, DSASignatureFormat.IeeeP1363FixedFieldConcatenation, out bytesWritten);
+
+        /// <summary>
+        ///   Attempts to create the ECDSA signature for the specified hash value in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="hash">The hash value to sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        public bool TrySignHash(
+            ReadOnlySpan<byte> hash,
+            Span<byte> destination,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
+        {
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return TrySignHashCore(hash, destination, signatureFormat, out bytesWritten);
+        }
+
+        /// <summary>
+        ///   Attempts to create the ECDSA signature for the specified hash value in the indicated format
+        ///   into the provided buffer.
+        /// </summary>
+        /// <param name="hash">The hash value to sign.</param>
+        /// <param name="destination">The buffer to receive the signature.</param>
+        /// <param name="signatureFormat">The encoding format to use for the signature.</param>
+        /// <param name="bytesWritten">
+        ///   When this method returns, contains a value that indicates the number of bytes written to
+        ///   <paramref name="destination"/>. This parameter is treated as uninitialized.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if <paramref name="destination"/> is big enough to receive the signature;
+        ///   otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the signing operation.
+        /// </exception>
+        protected virtual bool TrySignHashCore(
+            ReadOnlySpan<byte> hash,
+            Span<byte> destination,
+            DSASignatureFormat signatureFormat,
+            out int bytesWritten)
         {
+            // This method is expected to be overriden with better implementation
+
+            // The only available implementation here is abstract method, use it
             byte[] result = SignHash(hash.ToArray());
-            if (result.Length <= destination.Length)
+            byte[] converted = AsymmetricAlgorithmHelpers.ConvertFromIeeeP1363Signature(result, signatureFormat);
+            return Helpers.TryCopyToDestination(converted, destination, out bytesWritten);
+        }
+
+        public virtual bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
+            VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided hash.
+        /// </summary>
+        /// <param name="hash">The signed hash.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="hash"/> or <paramref name="signature"/> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the verification operation.
+        /// </exception>
+        public bool VerifyHash(byte[] hash, byte[] signature, DSASignatureFormat signatureFormat)
+        {
+            if (hash == null)
+                throw new ArgumentNullException(nameof(hash));
+            if (signature == null)
+                throw new ArgumentNullException(nameof(signature));
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyHashCore(hash, signature, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided hash.
+        /// </summary>
+        /// <param name="hash">The signed hash.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the verification operation.
+        /// </exception>
+        public bool VerifyHash(
+            ReadOnlySpan<byte> hash,
+            ReadOnlySpan<byte> signature,
+            DSASignatureFormat signatureFormat)
+        {
+            if (!signatureFormat.IsKnownValue())
+                throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat);
+
+            return VerifyHashCore(hash, signature, signatureFormat);
+        }
+
+        /// <summary>
+        ///   Verifies that a digital signature is valid for the provided hash.
+        /// </summary>
+        /// <param name="hash">The signed hash.</param>
+        /// <param name="signature">The signature to verify.</param>
+        /// <param name="signatureFormat">The encoding format for <paramref name="signature"/>.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the digital signature is valid for the provided data; otherwise, <see langword="false"/>.
+        /// </returns>
+        /// <exception cref="CryptographicException">
+        ///   An error occurred in the verification operation.
+        /// </exception>
+        protected virtual bool VerifyHashCore(
+            ReadOnlySpan<byte> hash,
+            ReadOnlySpan<byte> signature,
+            DSASignatureFormat signatureFormat)
+        {
+            // This method is expected to be overriden with better implementation
+
+            byte[]? sig = this.ConvertSignatureToIeeeP1363(signatureFormat, signature);
+
+            // If the signature failed normalization to P1363, it obviously doesn't verify.
+            if (sig == null)
             {
-                new ReadOnlySpan<byte>(result).CopyTo(destination);
-                bytesWritten = result.Length;
-                return true;
+                return false;
             }
-            else
+
+            // The only available implementation here is abstract method, use it
+            return VerifyHash(hash.ToArray(), sig);
+        }
+
+        private ReadOnlySpan<byte> HashSpanToTmp(
+            ReadOnlySpan<byte> data,
+            HashAlgorithmName hashAlgorithm,
+            Span<byte> tmp)
+        {
+            Debug.Assert(tmp.Length == HashBufferStackSize);
+
+            if (TryHashData(data, tmp, hashAlgorithm, out int hashSize))
             {
-                bytesWritten = 0;
-                return false;
+                return tmp.Slice(0, hashSize);
             }
+
+            // This is not expected, but a poor virtual implementation of TryHashData,
+            // or an exotic new algorithm, will hit this fallback.
+            return HashSpanToArray(data, hashAlgorithm);
         }
 
-        public virtual bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
-            VerifyHash(hash.ToArray(), signature.ToArray());
+        private byte[] HashSpanToArray(ReadOnlySpan<byte> data, HashAlgorithmName hashAlgorithm)
+        {
+            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
+            byte[] array = ArrayPool<byte>.Shared.Rent(data.Length);
+            bool returnArray = false;
+            try
+            {
+                data.CopyTo(array);
+                byte[] ret = HashData(array, 0, data.Length, hashAlgorithm);
+                returnArray = true;
+                return ret;
+            }
+            finally
+            {
+                Array.Clear(array, 0, data.Length);
+
+                if (returnArray)
+                {
+                    ArrayPool<byte>.Shared.Return(array);
+                }
+            }
+        }
 
         public override unsafe bool TryExportEncryptedPkcs8PrivateKey(
             ReadOnlySpan<byte> passwordBytes,
@@ -512,5 +1259,44 @@ namespace System.Security.Cryptography
                 }
             }
         }
+
+        /// <summary>
+        ///   Gets the largest size, in bytes, for a signature produced by this key in the indicated format.
+        /// </summary>
+        /// <param name="signatureFormat">The encoding format for a signature.</param>
+        /// <returns>
+        ///   The largest size, in bytes, for a signature produced by this key in the indicated format.
+        /// </returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="signatureFormat"/> is not a known format.
+        /// </exception>
+        public int GetMaxSignatureSize(DSASignatureFormat signatureFormat)
+        {
+            int fieldSizeBits = KeySize;
+
+            if (fieldSizeBits == 0)
+            {
+                // Coerce the key/key-size into existence
+                ExportParameters(false);
+
+                fieldSizeBits = KeySize;
+
+                // This implementation of ECDsa doesn't set KeySize, we can't
+                if (fieldSizeBits == 0)
+                {
+                    throw new NotSupportedException(SR.Cryptography_InvalidKeySize);
+                }
+            }
+
+            switch (signatureFormat)
+            {
+                case DSASignatureFormat.IeeeP1363FixedFieldConcatenation:
+                    return AsymmetricAlgorithmHelpers.BitsToBytes(fieldSizeBits) * 2;
+                case DSASignatureFormat.Rfc3279DerSequence:
+                    return AsymmetricAlgorithmHelpers.GetMaxDerSignatureSize(fieldSizeBits);
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(signatureFormat));
+            }
+        }
     }
 }
index 2174677..ad178b4 100644 (file)
@@ -138,6 +138,12 @@ namespace System.Security.Cryptography.Algorithms.Tests
 
             public OverrideAbstractECDsa(ECDsa ecdsa) => _ecdsa = ecdsa;
 
+            public override int KeySize
+            {
+                get => _ecdsa.KeySize;
+                set => _ecdsa.KeySize = value;
+            }
+
             public override byte[] SignHash(byte[] hash) => _ecdsa.SignHash(hash);
 
             public override bool VerifyHash(byte[] hash, byte[] signature) => _ecdsa.VerifyHash(hash, signature);
index e02f232..aa25b8f 100644 (file)
@@ -51,6 +51,9 @@
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaImportExport.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaImportExport.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaStub.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaStub.cs</Link>
     </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs</Link>
     </Compile>
index 530ecb1..658286d 100644 (file)
     <Compile Include="$(CommonPath)System\Security\Cryptography\ECDsaCng.SignVerify.cs">
       <Link>Common\System\Security\Cryptography\ECDsaCng.SignVerify.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)System\Security\Cryptography\KeyBlobHelpers.cs">
+      <Link>Common\System\Security\Cryptography\KeyBlobHelpers.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)System\Security\Cryptography\KeyFormatHelper.cs">
       <Link>Common\System\Security\Cryptography\KeyFormatHelper.cs</Link>
     </Compile>
     <Reference Include="System.Text.Encoding.Extensions" />
     <Reference Include="System.Threading" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index 7136058..b78e98c 100644 (file)
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs</Link>
     </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs</Link>
     </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaKeyFileTests.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaKeyFileTests.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaXml.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaXml.cs</Link>
     </Compile>
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Xml.cs</Link>
     </Compile>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index 8edcbd5..63dc763 100644 (file)
@@ -68,6 +68,13 @@ namespace System.Security.Cryptography.Csp.Tests
                 "TryExportSubjectPublicKeyInfo",
                 "TryExportPkcs8PrivateKey",
                 "TryExportEncryptedPkcs8PrivateKey",
+                // DSASignatureFormat methods defer to older methods.
+                "CreateSignatureCore",
+                "SignDataCore",
+                "TryCreateSignatureCore",
+                "TrySignDataCore",
+                "VerifyDataCore",
+                "VerifySignatureCore",
             };
 
             IEnumerable<MethodInfo> baseMethods = shimType.
index beaae7d..df8ab9e 100644 (file)
@@ -66,6 +66,9 @@
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs</Link>
     </Compile>
@@ -78,6 +81,9 @@
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs</Link>
     </Compile>
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\RSA\SignVerify.netcoreapp.cs</Link>
     </Compile>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index 2575a54..6f9f04c 100644 (file)
@@ -24,6 +24,9 @@
     <Compile Include="$(CommonPath)Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs">
       <Link>Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)Internal\Cryptography\Helpers.cs">
+      <Link>Internal\Cryptography\Helpers.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs">
       <Link>Common\Interop\Unix\Interop.Libraries.cs</Link>
     </Compile>
index 7a4b8af..147d63c 100644 (file)
@@ -51,6 +51,9 @@
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaImportExport.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaImportExport.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaStub.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaStub.cs</Link>
     </Compile>
@@ -96,6 +99,9 @@
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs</Link>
     </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs">
+      <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs">
       <Link>CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs</Link>
     </Compile>