From: Krzysztof Wicher Date: Wed, 18 Mar 2020 15:19:25 +0000 (-0700) Subject: Support Rfc3279 signature format for DSA and EcDSA (#1612) X-Git-Tag: submit/tizen/20210909.063632~9100 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=960b9a8df83de4efb03758a18fafbc4c6bab3b00;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Support Rfc3279 signature format for DSA and EcDSA (#1612) 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 --- diff --git a/src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Der.cs b/src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Der.cs index 868547a..40f035a 100644 --- a/src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Der.cs +++ b/src/libraries/Common/src/Internal/Cryptography/AsymmetricAlgorithmHelpers.Der.cs @@ -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 /// public static byte[] ConvertIeee1363ToDer(ReadOnlySpan input) { + using (AsnWriter writer = WriteIeee1363ToDer(input)) + { + return writer.Encode(); + } + } + + internal static bool TryConvertIeee1363ToDer( + ReadOnlySpan input, + Span destination, + out int bytesWritten) + { + using (AsnWriter writer = WriteIeee1363ToDer(input)) + { + return writer.TryEncode(destination, out bytesWritten); + } + } + + private static AsnWriter WriteIeee1363ToDer(ReadOnlySpan 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; } /// /// Convert Der format of (r, s) to Ieee1363 format /// - public static byte[] ConvertDerToIeee1363(byte[] input, int inputOffset, int inputCount, int fieldSizeBits) + public static byte[] ConvertDerToIeee1363(ReadOnlySpan 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 input, int fieldSizeBits, Span 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 rDer = sequenceReader.ReadIntegerBytes().Span; - ReadOnlySpan sDer = sequenceReader.ReadIntegerBytes().Span; + ReadOnlySpan rDer = sequenceReader.ReadIntegerBytes(); + ReadOnlySpan 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 + /// + /// Converts IeeeP1363 format to the specified signature format + /// + 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()); + } } + /// + /// Converts signature in the specified signature format to IeeeP1363 + /// + internal static byte[] ConvertSignatureToIeeeP1363( + DSASignatureFormat currentFormat, + ReadOnlySpan 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 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 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 } } diff --git a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs index 0959b74..6e274fe 100644 --- a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs @@ -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 source, Span destination, out int bytesWritten) + { + if (source.TryCopyTo(destination)) + { + bytesWritten = source.Length; + return true; + } + + bytesWritten = 0; + return false; + } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs index 5ada0a2..24cab8d 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EcDsa.cs @@ -10,12 +10,12 @@ internal static partial class Interop { internal static partial class Crypto { - internal static bool EcDsaSign(ReadOnlySpan dgst, Span 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 dgst, Span 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 dgst, ReadOnlySpan sigbuf, SafeEcKeyHandle ecKey) { diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs b/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs index 9dc07c4..406d287 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs @@ -41,15 +41,49 @@ namespace System.Security.Cryptography } } +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + protected override unsafe bool TryCreateSignatureCore( + ReadOnlySpan hash, + Span destination, + DSASignatureFormat signatureFormat, + out int bytesWritten) +#else public override unsafe bool TryCreateSignature(ReadOnlySpan hash, Span destination, out int bytesWritten) +#endif { Span stackBuf = stackalloc byte[WindowsMaxQSize]; ReadOnlySpan 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)rgbHash, (ReadOnlySpan)rgbSignature); +#endif } - public override bool VerifySignature(ReadOnlySpan hash, ReadOnlySpan signature) +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + protected override bool VerifySignatureCore( + ReadOnlySpan hash, + ReadOnlySpan signature, + DSASignatureFormat signatureFormat) { Span stackBuf = stackalloc byte[WindowsMaxQSize]; ReadOnlySpan 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 hash, ReadOnlySpan signature) + { + Span stackBuf = stackalloc byte[WindowsMaxQSize]; + ReadOnlySpan source = AdjustHashSizeIfNecessary(hash, stackBuf); +#endif using (SafeNCryptKeyHandle keyHandle = GetDuplicatedKeyHandle()) { unsafe diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs index b2d01c1..9a6f96f 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/DSAOpenSsl.cs @@ -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 _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(signature, 0, signatureSize), out signatureSize); - if (!success) - { - throw Interop.Crypto.CreateOpenSslCryptographicException(); - } + int signatureFieldSize = Interop.Crypto.DsaSignatureFieldSize(key) * BitsPerByte; + Span 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 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 hash, + Span destination, + out int bytesWritten) + { + return TryCreateSignatureCore( + hash, + destination, + DSASignatureFormat.IeeeP1363FixedFieldConcatenation, + out bytesWritten); } + protected override bool TryCreateSignatureCore( + ReadOnlySpan hash, + Span destination, + DSASignatureFormat signatureFormat, + out int bytesWritten) +#else public override bool TryCreateSignature(ReadOnlySpan hash, Span 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 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 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(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 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 SignHash( + ReadOnlySpan hash, + Span destination, + int signatureLength, + SafeDsaHandle key) + { + if (signatureLength > destination.Length) { - new ReadOnlySpan(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)rgbHash, (ReadOnlySpan)rgbSignature); } +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + + public override bool VerifySignature(ReadOnlySpan hash, ReadOnlySpan signature) => + VerifySignatureCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation); + + protected override bool VerifySignatureCore( + ReadOnlySpan hash, + ReadOnlySpan signature, + DSASignatureFormat signatureFormat) +#else public override bool VerifySignature(ReadOnlySpan hash, ReadOnlySpan 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() diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs index 6f4e111..0744b81 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/DSASecurityTransforms.cs @@ -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; diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.SignVerify.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.SignVerify.cs index bc065dfe..277198b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.SignVerify.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaCng.SignVerify.cs @@ -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 source, Span destination, out int bytesWritten) + { + return TrySignHashCore( + source, + destination, + DSASignatureFormat.IeeeP1363FixedFieldConcatenation, + out bytesWritten); + } + + protected override unsafe bool TrySignHashCore( + ReadOnlySpan hash, + Span destination, + DSASignatureFormat signatureFormat, + out int bytesWritten) + { +#else public override unsafe bool TrySignHash(ReadOnlySpan source, Span destination, out int bytesWritten) { + ReadOnlySpan 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 } /// @@ -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)hash, (ReadOnlySpan)signature); +#endif } - public override unsafe bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan signature) +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan signature) => + VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation); + + protected override bool VerifyHashCore( + ReadOnlySpan hash, + ReadOnlySpan signature, + DSASignatureFormat signatureFormat) +#else + public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan 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); + } } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs index a95b3db..e6f2f21 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaOpenSsl.cs @@ -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; /// @@ -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 signDestination = stackalloc byte[SignatureStackBufSize]; + ReadOnlySpan derSignature = SignHash(hash, signDestination, signatureLength, key); + byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize); return converted; } +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS public override bool TrySignHash(ReadOnlySpan hash, Span destination, out int bytesWritten) { + return TrySignHashCore( + hash, + destination, + DSASignatureFormat.IeeeP1363FixedFieldConcatenation, + out bytesWritten); + } + + protected override bool TrySignHashCore( + ReadOnlySpan hash, + Span destination, + DSASignatureFormat signatureFormat, + out int bytesWritten) +#else + public override bool TrySignHash(ReadOnlySpan hash, Span 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 signDestination = stackalloc byte[SignatureStackBufSize]; + +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation) { - if (!Interop.Crypto.EcDsaSign(hash, new Span(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 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 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 SignHash( + ReadOnlySpan hash, + Span destination, + int signatureLength, + SafeEcKeyHandle key) + { + if (signatureLength > destination.Length) { - new ReadOnlySpan(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)hash, (ReadOnlySpan)signature); } +#if INTERNAL_ASYMMETRIC_IMPLEMENTATIONS + public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan signature) => + VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation); + + protected override bool VerifyHashCore( + ReadOnlySpan hash, + ReadOnlySpan signature, + DSASignatureFormat signatureFormat) +#else public override bool VerifyHash(ReadOnlySpan hash, ReadOnlySpan 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 derSignature = stackalloc byte[SignatureStackBufSize]; + ReadOnlySpan 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; } diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECDsaSecurityTransforms.cs b/src/libraries/Common/src/System/Security/Cryptography/ECDsaSecurityTransforms.cs index 42bf959..da68d61 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECDsaSecurityTransforms.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECDsaSecurityTransforms.cs @@ -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 index 0000000..af4c6da --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSASignatureFormatTests.cs @@ -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 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( + "offset", + () => key.SignData(buffer, -1, buffer.Length, hash, format)); + + AssertExtensions.Throws( + "offset", + () => key.SignData(buffer, buffer.Length + 1, 0, hash, format)); + + AssertExtensions.Throws( + "offset", + () => key.VerifyData(buffer, -1, buffer.Length, buffer, hash, format)); + + AssertExtensions.Throws( + "offset", + () => key.VerifyData(buffer, buffer.Length + 1, 0, buffer, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.SignData(buffer, 1, buffer.Length, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.SignData(buffer, 0, buffer.Length + 1, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.SignData(buffer, buffer.Length, 1, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.VerifyData(buffer, 1, buffer.Length, buffer, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.VerifyData(buffer, 0, buffer.Length + 1, buffer, hash, format)); + + AssertExtensions.Throws( + "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 readOnlyHash = hash; + ReadOnlySpan 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 readOnlyData = data; + ReadOnlySpan 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(), 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 index 0000000..d062f7e --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DsaFamilySignatureFormatTests.cs @@ -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 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(); + + AssertExtensions.Throws( + "signatureFormat", + () => SignData(key, empty, HashAlgorithmName.SHA1, SignatureFormat)); + + AssertExtensions.Throws( + "signatureFormat", + () => VerifyData(key, empty, empty, HashAlgorithmName.SHA1, SignatureFormat)); + + AssertExtensions.Throws( + "signatureFormat", + () => SignHash(key, empty, SignatureFormat)); + + AssertExtensions.Throws( + "signatureFormat", + () => VerifyHash(key, empty, empty, SignatureFormat)); + } + + [Fact] + public void EmptyHashAlgorithm() + { + KeyDescription key = GetKey(); + byte[] empty = Array.Empty(); + + foreach (DSASignatureFormat format in Enum.GetValues(typeof(DSASignatureFormat))) + { + AssertExtensions.Throws( + "hashAlgorithm", + () => SignData(key, empty, default, format)); + + AssertExtensions.Throws( + "hashAlgorithm", + () => VerifyData(key, empty, empty, default, format)); + } + } + + [Fact] + public void UnknownHashAlgorithm() + { + KeyDescription key = GetKey(); + byte[] empty = Array.Empty(); + HashAlgorithmName unknown = new HashAlgorithmName(nameof(UnknownHashAlgorithm)); + + foreach (DSASignatureFormat format in Enum.GetValues(typeof(DSASignatureFormat))) + { + Assert.ThrowsAny( + () => SignData(key, empty, unknown, format)); + + Assert.ThrowsAny( + () => 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( + "data", + () => SignData(key, null, HashAlgorithmName.SHA1, format)); + + AssertExtensions.Throws( + "data", + () => VerifyData(key, null, Array.Empty(), HashAlgorithmName.SHA1, format)); + + AssertExtensions.Throws( + "signature", + () => VerifyData(key, Array.Empty(), null, HashAlgorithmName.SHA1, format)); + + AssertExtensions.Throws( + HashParameterName, + () => SignHash(key, null, format)); + + AssertExtensions.Throws( + HashParameterName, + () => VerifyHash(key, null, Array.Empty(), format)); + + AssertExtensions.Throws( + SignatureParameterName, + () => VerifyHash(key, Array.Empty(), 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 index 0000000..5451458 --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDsa/ECDsaSignatureFormatTests.cs @@ -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 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( + "offset", + () => key.SignData(buffer, -1, buffer.Length, hash, format)); + + AssertExtensions.Throws( + "offset", + () => key.SignData(buffer, buffer.Length + 1, 0, hash, format)); + + AssertExtensions.Throws( + "offset", + () => key.VerifyData(buffer, -1, buffer.Length, buffer, hash, format)); + + AssertExtensions.Throws( + "offset", + () => key.VerifyData(buffer, buffer.Length + 1, 0, buffer, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.SignData(buffer, 1, buffer.Length, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.SignData(buffer, 0, buffer.Length + 1, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.SignData(buffer, buffer.Length, 1, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.VerifyData(buffer, 1, buffer.Length, buffer, hash, format)); + + AssertExtensions.Throws( + "count", + () => key.VerifyData(buffer, 0, buffer.Length + 1, buffer, hash, format)); + + AssertExtensions.Throws( + "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 readOnlyHash = hash; + ReadOnlySpan 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 readOnlyData = data; + ReadOnlySpan 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(), 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/Native/Unix/System.Security.Cryptography.Native/pal_ecdsa.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecdsa.c index 27889bb..46d1abc 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecdsa.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_ecdsa.c @@ -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; diff --git a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs index ead364c..fe1cafe 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs @@ -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 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 passwordBytes, System.ReadOnlySpan source, out int bytesRead) { throw null; } @@ -136,22 +139,40 @@ namespace System.Security.Cryptography public override void ImportPkcs8PrivateKey(System.ReadOnlySpan source, out int bytesRead) { throw null; } public override void ImportSubjectPublicKeyInfo(System.ReadOnlySpan 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 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 hash, System.Span destination, out int bytesWritten) { throw null; } + public bool TryCreateSignature(System.ReadOnlySpan hash, System.Span destination, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; } + protected virtual bool TryCreateSignatureCore(System.ReadOnlySpan hash, System.Span destination, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportPkcs8PrivateKey(System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportSubjectPublicKeyInfo(System.Span destination, out int bytesWritten) { throw null; } protected virtual bool TryHashData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; } public virtual bool TrySignData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; } + public bool TrySignData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; } + protected virtual bool TrySignDataCore(System.ReadOnlySpan data, System.Span 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 data, System.ReadOnlySpan signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; } + public bool VerifyData(System.ReadOnlySpan data, System.ReadOnlySpan signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } + protected virtual bool VerifyDataCore(System.IO.Stream data, System.ReadOnlySpan signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } + protected virtual bool VerifyDataCore(System.ReadOnlySpan data, System.ReadOnlySpan 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 hash, System.ReadOnlySpan signature) { throw null; } + public bool VerifySignature(System.ReadOnlySpan hash, System.ReadOnlySpan signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } + protected virtual bool VerifySignatureCore(System.ReadOnlySpan hash, System.ReadOnlySpan 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 source, out int bytesRead) { throw null; } @@ -301,9 +328,16 @@ namespace System.Security.Cryptography public override void ImportPkcs8PrivateKey(System.ReadOnlySpan source, out int bytesRead) { throw null; } public override void ImportSubjectPublicKeyInfo(System.ReadOnlySpan 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 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 hash, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } public override string ToXmlString(bool includePrivateParameters) { throw null; } public virtual bool TryExportECPrivateKey(System.Span destination, out int bytesWritten) { throw null; } public override bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } @@ -312,13 +346,26 @@ namespace System.Security.Cryptography public override bool TryExportSubjectPublicKeyInfo(System.Span destination, out int bytesWritten) { throw null; } protected virtual bool TryHashData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; } public virtual bool TrySignData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, out int bytesWritten) { throw null; } + public bool TrySignData(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; } + protected virtual bool TrySignDataCore(System.ReadOnlySpan data, System.Span destination, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; } public virtual bool TrySignHash(System.ReadOnlySpan hash, System.Span destination, out int bytesWritten) { throw null; } + public bool TrySignHash(System.ReadOnlySpan hash, System.Span destination, System.Security.Cryptography.DSASignatureFormat signatureFormat, out int bytesWritten) { throw null; } + protected virtual bool TrySignHashCore(System.ReadOnlySpan hash, System.Span 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 data, System.ReadOnlySpan signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; } + public bool VerifyData(System.ReadOnlySpan data, System.ReadOnlySpan signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } + protected virtual bool VerifyDataCore(System.IO.Stream data, System.ReadOnlySpan signature, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } + protected virtual bool VerifyDataCore(System.ReadOnlySpan data, System.ReadOnlySpan 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 hash, System.ReadOnlySpan signature) { throw null; } + public bool VerifyHash(System.ReadOnlySpan hash, System.ReadOnlySpan signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } + protected virtual bool VerifyHashCore(System.ReadOnlySpan hash, System.ReadOnlySpan signature, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } } public partial struct ECParameters { diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Algorithms/src/Resources/Strings.resx index 593d64c..8788568 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Resources/Strings.resx @@ -306,6 +306,9 @@ Unknown padding mode used. + + The signature format '{0}' is unknown. + CNG provider unexpectedly terminated encryption or decryption prematurely. diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index 61c8b31..bc99913 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -6,7 +6,7 @@ $(NetCoreAppCurrent)-Windows_NT;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS enable - + @@ -30,6 +30,7 @@ + @@ -85,6 +86,9 @@ + + Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Der.cs + Internal\Cryptography\BasicSymmetricCipher.cs @@ -697,9 +701,6 @@ Common\Interop\Unix\Interop.Libraries.cs - - Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Der.cs - Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs index 23391a0..69b260c 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs @@ -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); } + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The DSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or signing operation. + /// + 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); } + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The offset into at which to begin hashing. + /// The number of bytes to read from . + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The DSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// + - 1 results in an index that is + /// beyond the upper bound of . + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or signing operation. + /// + 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(data, offset, count), hashAlgorithm, signatureFormat); + } + + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The DSA signature for the specified data. + /// + /// + /// An error occurred in the hashing or signing operation. + /// + protected virtual byte[] SignDataCore( + ReadOnlySpan data, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat) + { + Span 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); } + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The DSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or signing operation. + /// + 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); + } + + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The DSA signature for the specified data. + /// + /// + /// An error occurred in the hashing or signing operation. + /// + 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); } + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// An array that contains the signed data. + /// The starting index of the signed portion of . + /// The number of bytes in that were signed. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// + - 1 results in an index that is + /// beyond the upper bound of . + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + 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(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 hash, Span destination, out int bytesWritten) + /// + /// Creates the DSA signature for the specified hash value in the indicated format. + /// + /// The hash value to sign. + /// The encoding format to use for the signature. + /// + /// The DSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the signing operation. + /// + public byte[] CreateSignature(byte[] rgbHash, DSASignatureFormat signatureFormat) { - byte[] sig = CreateSignature(hash.ToArray()); - if (sig.Length <= destination.Length) - { - new ReadOnlySpan(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 data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) + /// + /// Creates the DSA signature for the specified hash value in the indicated format. + /// + /// The hash value to sign. + /// The encoding format to use for the signature. + /// + /// The DSA signature for the specified data. + /// + /// + /// An error occurred in the signing operation. + /// + protected virtual byte[] CreateSignatureCore(ReadOnlySpan hash, DSASignatureFormat signatureFormat) { - // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. - byte[] array = ArrayPool.Shared.Rent(data.Length); - try + Span 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(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 hash, Span destination, out int bytesWritten) + => TryCreateSignatureCore(hash, destination, DSASignatureFormat.IeeeP1363FixedFieldConcatenation, out bytesWritten); + + /// + /// Attempts to create the DSA signature for the specified hash value in the indicated format + /// into the provided buffer. + /// + /// The hash value to sign. + /// The buffer to receive the signature. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the signing operation. + /// + public bool TryCreateSignature( + ReadOnlySpan hash, + Span destination, + DSASignatureFormat signatureFormat, + out int bytesWritten) + { + if (!signatureFormat.IsKnownValue()) + throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat); + + return TryCreateSignatureCore(hash, destination, signatureFormat, out bytesWritten); + } + + /// + /// Attempts to create the DSA signature for the specified hash value in the indicated format + /// into the provided buffer. + /// + /// The hash value to sign. + /// The buffer to receive the signature. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// An error occurred in the signing operation. + /// + protected virtual bool TryCreateSignatureCore( + ReadOnlySpan hash, + Span 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.Shared.Return(array); + sig = AsymmetricAlgorithmHelpers.ConvertFromIeeeP1363Signature(sig, signatureFormat); } + + return Helpers.TryCopyToDestination(sig, destination, out bytesWritten); + } + + protected virtual bool TryHashData( + ReadOnlySpan data, + Span destination, + HashAlgorithmName hashAlgorithm, + out int bytesWritten) + { + byte[] hash = HashSpanToArray(data, hashAlgorithm); + return Helpers.TryCopyToDestination(hash, destination, out bytesWritten); } - public virtual bool TrySignData(ReadOnlySpan data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) + public virtual bool TrySignData( + ReadOnlySpan data, + Span 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 data, ReadOnlySpan signature, HashAlgorithmName hashAlgorithm) + /// + /// Attempts to create the DSA signature for the specified data in the indicated format + /// into the provided buffer. + /// + /// The data to hash and sign. + /// The buffer to receive the signature. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the signing operation. + /// + public bool TrySignData( + ReadOnlySpan data, + Span 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); + } + + /// + /// Attempts to create the DSA signature for the specified data in the indicated format + /// into the provided buffer. + /// + /// The data to hash and sign. + /// The buffer to receive the signature. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// An error occurred in the signing operation. + /// + protected virtual bool TrySignDataCore( + ReadOnlySpan data, + Span destination, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat, + out int bytesWritten) + { + Span tmp = stackalloc byte[HashBufferStackSize]; + ReadOnlySpan hash = HashSpanToTmp(data, hashAlgorithm, tmp); + + return TryCreateSignatureCore(hash, destination, signatureFormat, out bytesWritten); + } + + public virtual bool VerifyData( + ReadOnlySpan data, + ReadOnlySpan signature, + HashAlgorithmName hashAlgorithm) + { + if (string.IsNullOrEmpty(hashAlgorithm.Name)) + throw HashAlgorithmNameNullOrEmpty(); + + return VerifyDataCore(data, signature, hashAlgorithm, DSASignatureFormat.IeeeP1363FixedFieldConcatenation); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + 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); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + 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); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + protected virtual bool VerifyDataCore( + Stream data, + ReadOnlySpan signature, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat) + { + byte[] hash = HashData(data, hashAlgorithm); + return VerifySignatureCore(hash, signature, signatureFormat); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the hashing or verification operation. + /// + public bool VerifyData( + ReadOnlySpan data, + ReadOnlySpan 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); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + protected virtual bool VerifyDataCore( + ReadOnlySpan data, + ReadOnlySpan signature, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat) + { + Span tmp = stackalloc byte[HashBufferStackSize]; + ReadOnlySpan hash = HashSpanToTmp(data, hashAlgorithm, tmp); + + return VerifySignatureCore(hash, signature, signatureFormat); + } + + /// + /// Verifies that a digital signature is valid for the provided hash. + /// + /// The signed hash. + /// The signature to verify. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the verification operation. + /// + 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 hash, ReadOnlySpan signature) => + VerifySignature(hash.ToArray(), signature.ToArray()); + + /// + /// Verifies that a digital signature is valid for the provided hash. + /// + /// The signed hash. + /// The signature to verify. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the verification operation. + /// + public bool VerifySignature( + ReadOnlySpan hash, + ReadOnlySpan signature, + DSASignatureFormat signatureFormat) + { + if (!signatureFormat.IsKnownValue()) + throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat); + + return VerifySignatureCore(hash, signature, signatureFormat); + } + + /// + /// Verifies that a digital signature is valid for the provided hash. + /// + /// The signed hash. + /// The signature to verify. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// An error occurred in the verification operation. + /// + protected virtual bool VerifySignatureCore( + ReadOnlySpan hash, + ReadOnlySpan 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 HashSpanToTmp( + ReadOnlySpan data, + HashAlgorithmName hashAlgorithm, + Span 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 buf = stackalloc byte[128]; - ReadOnlySpan 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 data, HashAlgorithmName hashAlgorithm) + { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. + byte[] array = ArrayPool.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.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.Shared.Return(array); } } - - return VerifySignature(hash, signature); } - public virtual bool VerifySignature(ReadOnlySpan hash, ReadOnlySpan 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; } + + /// + /// Gets the largest size, in bytes, for a signature produced by this key in the indicated format. + /// + /// The encoding format for a signature. + /// + /// The largest size, in bytes, for a signature produced by this key in the indicated format. + /// + /// + /// is not a known format. + /// + 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 index 0000000..3b1f156 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSASignatureFormat.cs @@ -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 +{ + /// + /// Specifies the data format for signatures with the DSA family of algorithms. + /// + public enum DSASignatureFormat + { + /// + /// The signature format from IEEE P1363, which produces a fixed size signature for a given key. + /// + /// + /// 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. + /// + IeeeP1363FixedFieldConcatenation, + + /// + /// The signature format from IETF RFC 3279, which produces a variably-sized signature. + /// + /// + /// 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 + /// format. + /// + 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)); + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs index 9d04547..01d1109 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs @@ -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 data, Span destination, HashAlgorithmName hashAlgorithm, out int bytesWritten) + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The offset into at which to begin hashing. + /// The number of bytes to read from . + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The ECDSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// + - 1 results in an index that is + /// beyond the upper bound of . + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or signing operation. + /// + 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(data, offset, count), hashAlgorithm, signatureFormat); + } + + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The ECDSA signature for the specified data. + /// + /// + /// An error occurred in the hashing or signing operation. + /// + protected virtual byte[] SignDataCore( + ReadOnlySpan data, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat) + { + Span 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.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.Shared.Return(rented); + } + } + } + } + + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The ECDSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or signing operation. + /// + 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); + } + + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The ECDSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or signing operation. + /// + 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); + } + + /// + /// Computes the hash value of the specified data and signs it using the specified signature format. + /// + /// The data to sign. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// The ECDSA signature for the specified data. + /// + /// + /// An error occurred in the hashing or signing operation. + /// + protected virtual byte[] SignDataCore( + Stream data, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat) + { + byte[] hash = HashData(data, hashAlgorithm); + return SignHashCore(hash, signatureFormat); + } + + /// + /// Computes the ECDSA signature for the specified hash value in the indicated format. + /// + /// The hash value to sign. + /// The encoding format to use for the signature. + /// + /// The ECDSA signature for the specified data. + /// + /// + /// is . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the signing operation. + /// + 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); + } + + /// + /// Computes the ECDSA signature for the specified hash value in the indicated format. + /// + /// The hash value to sign. + /// The encoding format to use for the signature. + /// + /// The ECDSA signature for the specified data. + /// + /// + /// An error occurred in the signing operation. + /// + protected virtual byte[] SignHashCore(ReadOnlySpan hash, DSASignatureFormat signatureFormat) + { + Span 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.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.Shared.Return(rented); + } + } + } + } + + public virtual bool TrySignData( + ReadOnlySpan data, + Span destination, + HashAlgorithmName hashAlgorithm, + out int bytesWritten) + { + if (string.IsNullOrEmpty(hashAlgorithm.Name)) + throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm)); + + Span hashTmp = stackalloc byte[HashBufferStackSize]; + ReadOnlySpan hash = HashSpanToTmp(data, hashAlgorithm, hashTmp); + return TrySignHash(hash, destination, out bytesWritten); + } + + /// + /// Attempts to create the ECDSA signature for the specified data in the indicated format + /// into the provided buffer. + /// + /// The data to hash and sign. + /// The buffer to receive the signature. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the signing operation. + /// + public bool TrySignData( + ReadOnlySpan data, + Span 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); + } + + /// + /// Attempts to create the ECDSA signature for the specified data in the indicated format + /// into the provided buffer. + /// + /// The data to hash and sign. + /// The buffer to receive the signature. + /// The hash algorithm to use to create the hash value. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// An error occurred in the signing operation. + /// + protected virtual bool TrySignDataCore( + ReadOnlySpan data, + Span destination, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat, + out int bytesWritten) + { + Span hashTmp = stackalloc byte[HashBufferStackSize]; + ReadOnlySpan 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); } + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// An array that contains the signed data. + /// The starting index of the signed portion of . + /// The number of bytes in that were signed. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// is less than zero. + /// + /// -or- + /// + /// + - 1 results in an index that is + /// beyond the upper bound of . + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + 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(data, offset, count), + signature, + hashAlgorithm, + signatureFormat); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + 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 data, ReadOnlySpan 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 buf = stackalloc byte[128]; - ReadOnlySpan hash = stackalloc byte[0]; + Span hashTmp = stackalloc byte[HashBufferStackSize]; + ReadOnlySpan hash = HashSpanToTmp(data, hashAlgorithm, hashTmp); + return VerifyHash(hash, signature); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the hashing or verification operation. + /// + public bool VerifyData( + ReadOnlySpan data, + ReadOnlySpan 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)) + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + protected virtual bool VerifyDataCore( + ReadOnlySpan data, + ReadOnlySpan signature, + HashAlgorithmName hashAlgorithm, + DSASignatureFormat signatureFormat) + { + // SHA-2-512 is the biggest hash we know about. + Span 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.Shared.Rent(data.Length); - try - { - data.CopyTo(array); - hash = HashData(array, 0, data.Length, hashAlgorithm); - } - finally - { - Array.Clear(array, 0, data.Length); - ArrayPool.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); } + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// + /// has a or empty . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + 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); + } + + /// + /// Verifies that a digital signature is valid for the provided data. + /// + /// The signed data. + /// The signature to verify. + /// The hash algorithm used to hash the data for the verification process. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// An error occurred in the hashing or verification operation. + /// + protected virtual bool VerifyDataCore( + Stream data, + ReadOnlySpan 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.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(hash).CopyTo(destination); @@ -232,28 +796,211 @@ namespace System.Security.Cryptography finally { Array.Clear(array, 0, data.Length); - ArrayPool.Shared.Return(array); + + if (returnArray) + { + ArrayPool.Shared.Return(array); + } } } public virtual bool TrySignHash(ReadOnlySpan hash, Span destination, out int bytesWritten) + => TrySignHashCore(hash, destination, DSASignatureFormat.IeeeP1363FixedFieldConcatenation, out bytesWritten); + + /// + /// Attempts to create the ECDSA signature for the specified hash value in the indicated format + /// into the provided buffer. + /// + /// The hash value to sign. + /// The buffer to receive the signature. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the signing operation. + /// + public bool TrySignHash( + ReadOnlySpan hash, + Span destination, + DSASignatureFormat signatureFormat, + out int bytesWritten) + { + if (!signatureFormat.IsKnownValue()) + throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat); + + return TrySignHashCore(hash, destination, signatureFormat, out bytesWritten); + } + + /// + /// Attempts to create the ECDSA signature for the specified hash value in the indicated format + /// into the provided buffer. + /// + /// The hash value to sign. + /// The buffer to receive the signature. + /// The encoding format to use for the signature. + /// + /// When this method returns, contains a value that indicates the number of bytes written to + /// . This parameter is treated as uninitialized. + /// + /// + /// if is big enough to receive the signature; + /// otherwise, . + /// + /// + /// An error occurred in the signing operation. + /// + protected virtual bool TrySignHashCore( + ReadOnlySpan hash, + Span 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 hash, ReadOnlySpan signature) => + VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation); + + /// + /// Verifies that a digital signature is valid for the provided hash. + /// + /// The signed hash. + /// The signature to verify. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// or is . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the verification operation. + /// + 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); + } + + /// + /// Verifies that a digital signature is valid for the provided hash. + /// + /// The signed hash. + /// The signature to verify. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// is not a known format. + /// + /// + /// An error occurred in the verification operation. + /// + public bool VerifyHash( + ReadOnlySpan hash, + ReadOnlySpan signature, + DSASignatureFormat signatureFormat) + { + if (!signatureFormat.IsKnownValue()) + throw DSASignatureFormatHelpers.CreateUnknownValueException(signatureFormat); + + return VerifyHashCore(hash, signature, signatureFormat); + } + + /// + /// Verifies that a digital signature is valid for the provided hash. + /// + /// The signed hash. + /// The signature to verify. + /// The encoding format for . + /// + /// if the digital signature is valid for the provided data; otherwise, . + /// + /// + /// An error occurred in the verification operation. + /// + protected virtual bool VerifyHashCore( + ReadOnlySpan hash, + ReadOnlySpan 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(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 HashSpanToTmp( + ReadOnlySpan data, + HashAlgorithmName hashAlgorithm, + Span 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 hash, ReadOnlySpan signature) => - VerifyHash(hash.ToArray(), signature.ToArray()); + private byte[] HashSpanToArray(ReadOnlySpan data, HashAlgorithmName hashAlgorithm) + { + // Use ArrayPool.Shared instead of CryptoPool because the array is passed out. + byte[] array = ArrayPool.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.Shared.Return(array); + } + } + } public override unsafe bool TryExportEncryptedPkcs8PrivateKey( ReadOnlySpan passwordBytes, @@ -512,5 +1259,44 @@ namespace System.Security.Cryptography } } } + + /// + /// Gets the largest size, in bytes, for a signature produced by this key in the indicated format. + /// + /// The encoding format for a signature. + /// + /// The largest size, in bytes, for a signature produced by this key in the indicated format. + /// + /// + /// is not a known format. + /// + 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)); + } + } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/ECDsaTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/ECDsaTests.cs index 2174677..ad178b4 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/ECDsaTests.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/ECDsaTests.cs @@ -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); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj index e02f232..aa25b8f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj @@ -51,6 +51,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaImportExport.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaStub.cs @@ -218,9 +221,15 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj index 530ecb13..658286d 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/src/System.Security.Cryptography.Cng.csproj @@ -277,6 +277,9 @@ Common\System\Security\Cryptography\ECDsaCng.SignVerify.cs + + Common\System\Security\Cryptography\KeyBlobHelpers.cs + Common\System\Security\Cryptography\KeyFormatHelper.cs @@ -413,4 +416,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj index 7136058..b78e98c 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj @@ -105,6 +105,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs @@ -117,6 +120,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs @@ -138,6 +144,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaKeyFileTests.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaXml.cs @@ -203,4 +212,4 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Xml.cs - \ No newline at end of file + diff --git a/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DSACryptoServiceProvider.Unix.cs b/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DSACryptoServiceProvider.Unix.cs index 76777a0..52fdfcd 100644 --- a/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DSACryptoServiceProvider.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DSACryptoServiceProvider.Unix.cs @@ -5,6 +5,7 @@ using Internal.Cryptography; using Internal.NativeCrypto; using System.IO; +using System.Diagnostics; namespace System.Security.Cryptography { diff --git a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs index 8edcbd5..63dc763 100644 --- a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs @@ -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 baseMethods = shimType. diff --git a/src/libraries/System.Security.Cryptography.Csp/tests/System.Security.Cryptography.Csp.Tests.csproj b/src/libraries/System.Security.Cryptography.Csp/tests/System.Security.Cryptography.Csp.Tests.csproj index beaae7d..df8ab9e 100644 --- a/src/libraries/System.Security.Cryptography.Csp/tests/System.Security.Cryptography.Csp.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Csp/tests/System.Security.Cryptography.Csp.Tests.csproj @@ -66,6 +66,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs @@ -78,6 +81,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs @@ -115,4 +121,4 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\RSA\SignVerify.netcoreapp.cs - \ No newline at end of file + diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj b/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj index 2575a54..6f9f04c 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj +++ b/src/libraries/System.Security.Cryptography.OpenSsl/src/System.Security.Cryptography.OpenSsl.csproj @@ -24,6 +24,9 @@ Common\Internal\Cryptography\AsymmetricAlgorithmHelpers.Hash.cs + + Internal\Cryptography\Helpers.cs + Common\Interop\Unix\Interop.Libraries.cs diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj b/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj index 7a4b8af..147d63c 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj @@ -51,6 +51,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaImportExport.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaSignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaStub.cs @@ -96,6 +99,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAFactory.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DsaFamilySignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSAImportExport.cs @@ -105,6 +111,9 @@ CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatter.cs + + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignatureFormatTests.cs + CommonTest\System\Security\Cryptography\AlgorithmImplementations\DSA\DSASignVerify.cs