diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs index 83b614863f6889..f919daf562998b 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs @@ -78,6 +78,15 @@ internal static byte[] Consume(ReadOnlySpan blob, ref int offset, int coun return value; } + /// + /// Peel off the next "count" bytes in blob and copy them into the destination. + /// + internal static void Consume(ReadOnlySpan blob, ref int offset, int count, Span destination) + { + blob.Slice(offset, count).CopyTo(destination); + offset += count; + } + /// /// Magic numbers identifying blob types /// diff --git a/src/libraries/Common/src/System/Security/Cryptography/AsymmetricAlgorithmHelpers.Der.cs b/src/libraries/Common/src/System/Security/Cryptography/AsymmetricAlgorithmHelpers.Der.cs index 0f0a8b173be7c7..794e0b79dfc7cf 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/AsymmetricAlgorithmHelpers.Der.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/AsymmetricAlgorithmHelpers.Der.cs @@ -2,11 +2,109 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Formats.Asn1; namespace System.Security.Cryptography { internal static partial class AsymmetricAlgorithmHelpers { + /// + /// Convert Ieee1363 format of (r, s) to Der format + /// + public static byte[] ConvertIeee1363ToDer(ReadOnlySpan input) + { + AsnWriter writer = WriteIeee1363ToDer(input); + return writer.Encode(); + } + + internal static bool TryConvertIeee1363ToDer( + ReadOnlySpan input, + Span destination, + out int bytesWritten) + { + 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 SEQUENCE(INTEGER(r), INTEGER(s)). + int halfLength = input.Length / 2; + + 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(ReadOnlySpan input, int fieldSizeBits) + { + int fieldSizeBytes = BitsToBytes(fieldSizeBits); + int encodedSize = 2 * fieldSizeBytes; + byte[] response = new byte[encodedSize]; + + 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); + + try + { + AsnValueReader reader = new AsnValueReader(input, AsnEncodingRules.DER); + AsnValueReader sequenceReader = reader.ReadSequence(); + reader.ThrowIfNotEmpty(); + ReadOnlySpan rDer = sequenceReader.ReadIntegerBytes(); + ReadOnlySpan sDer = sequenceReader.ReadIntegerBytes(); + sequenceReader.ThrowIfNotEmpty(); + + CopySignatureField(rDer, destination.Slice(0, fieldSizeBytes)); + CopySignatureField(sDer, destination.Slice(fieldSizeBytes, fieldSizeBytes)); + return encodedSize; + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + private static void CopySignatureField(ReadOnlySpan signatureField, Span response) + { + if (signatureField.Length > response.Length) + { + 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); + } + + // If the field is too short then it needs to be prepended + // with zeroes in the response. + int writeOffset = response.Length - signatureField.Length; + response.Slice(0, writeOffset).Clear(); + signatureField.CopyTo(response.Slice(writeOffset)); + } + internal static int BitsToBytes(int bitLength) { int byteLength = (bitLength + 7) / 8; diff --git a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs index a74d2810708958..1c781f653ce425 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CngHelpers.cs @@ -17,6 +17,22 @@ internal static CryptographicException ToCryptographicException(this Interop.NCr return ((int)errorCode).ToCryptographicException(); } + internal static SafeNCryptProviderHandle OpenStorageProvider(this CngProvider provider) + { + ErrorCode errorCode = Interop.NCrypt.NCryptOpenStorageProvider( + out SafeNCryptProviderHandle providerHandle, + provider.Provider, + 0); + + if (errorCode != ErrorCode.ERROR_SUCCESS) + { + providerHandle.Dispose(); + throw errorCode.ToCryptographicException(); + } + + return providerHandle; + } + internal static void SetExportPolicy(this SafeNCryptKeyHandle keyHandle, CngExportPolicies exportPolicy) { unsafe @@ -125,6 +141,40 @@ internal static void SetExportPolicy(this SafeNCryptKeyHandle keyHandle, CngExpo } } + internal delegate void ExportKeyBlobCallback(ReadOnlySpan blob); + + internal static void ExportKeyBlob( + this SafeNCryptKeyHandle handle, + string blobType, + ExportKeyBlobCallback callback) + { + int numBytesNeeded; + ErrorCode errorCode = Interop.NCrypt.NCryptExportKey(handle, IntPtr.Zero, blobType, IntPtr.Zero, null, 0, out numBytesNeeded, 0); + if (errorCode != ErrorCode.ERROR_SUCCESS) + throw errorCode.ToCryptographicException(); + + byte[] buffer = CryptoPool.Rent(numBytesNeeded); + + try + { + using (PinAndClear.Track(buffer)) + { + errorCode = Interop.NCrypt.NCryptExportKey(handle, IntPtr.Zero, blobType, IntPtr.Zero, buffer, buffer.Length, out numBytesNeeded, 0); + if (errorCode != ErrorCode.ERROR_SUCCESS) + throw errorCode.ToCryptographicException(); + + // The second call to NCryptExportKey should always return the same number of bytes as the first call, + // but we will slice the buffer just in case. + callback(buffer.AsSpan(0, numBytesNeeded)); + } + } + finally + { + // Already cleared by PinAndClear.Track above + CryptoPool.Return(buffer, clearSize: 0); + } + } + internal static bool TryExportKeyBlob( this SafeNCryptKeyHandle handle, string blobType, diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.ECDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.ECDsa.cs index 82111e0cf43d7f..e4391c3ec87052 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.ECDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.ECDsa.cs @@ -7,6 +7,11 @@ using System.Runtime.InteropServices; using System.Security.Cryptography.Asn1; using Internal.Cryptography; +using Microsoft.Win32.SafeHandles; + +#if NETFRAMEWORK +using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber; +#endif namespace System.Security.Cryptography { @@ -21,9 +26,19 @@ private sealed class ECDsaComponent : ComponentAlgorithm { private readonly ECDsaAlgorithm _algorithm; +#if NET || NETSTANDARD private ECDsa _ecdsa; +#else + private ECDsaCng _ecdsa; +#endif - private ECDsaComponent(ECDsa ecdsa, ECDsaAlgorithm algorithm) + private ECDsaComponent( +#if NET || NETSTANDARD + ECDsa ecdsa, +#else + ECDsaCng ecdsa, +#endif + ECDsaAlgorithm algorithm) { Debug.Assert(ecdsa != null); @@ -35,18 +50,14 @@ private ECDsaComponent(ECDsa ecdsa, ECDsaAlgorithm algorithm) // Limit this implementation to the NIST curves until we have a better understanding // of where native implementations of composite are aligning. public static bool IsAlgorithmSupported(ECDsaAlgorithm algorithm) => -#if NET - algorithm.CurveOid is Oids.secp256r1 or Oids.secp384r1 or Oids.secp521r1; -#else - false; -#endif + algorithm.CurveOidValue is Oids.secp256r1 or Oids.secp384r1 or Oids.secp521r1; public static ECDsaComponent GenerateKey(ECDsaAlgorithm algorithm) { -#if NET +#if NET || NETSTANDARD return new ECDsaComponent(ECDsa.Create(algorithm.Curve), algorithm); #else - throw new PlatformNotSupportedException(); + return new ECDsaComponent(CreateKey(algorithm.CurveOid.FriendlyName), algorithm); #endif } @@ -68,7 +79,7 @@ public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, R // If domain parameters are present, validate that they match the composite ML-DSA algorithm. if (ecPrivateKey.Parameters is ECDomainParameters domainParameters) { - if (domainParameters.Named is not string curveOid || curveOid != algorithm.CurveOid) + if (domainParameters.Named is not string curveOid || curveOid != algorithm.CurveOidValue) { // The curve specified must be named and match the required curve for the composite ML-DSA algorithm. throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); @@ -90,7 +101,7 @@ public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, R { ecPrivateKey.PrivateKey.CopyTo(d); -#if NET +#if NET || NETSTANDARD ECParameters parameters = new ECParameters { Curve = algorithm.Curve, @@ -105,8 +116,33 @@ public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, R parameters.Validate(); return new ECDsaComponent(ECDsa.Create(parameters), algorithm); -#else - throw new PlatformNotSupportedException(); +#else // NETFRAMEWORK +#if NET472_OR_GREATER +#error ECDsa.Create(ECParameters) is avaliable in .NET Framework 4.7.2 and later, so this workaround is not needed anymore. +#endif + Debug.Assert(!string.IsNullOrEmpty(algorithm.CurveOid.FriendlyName)); + Debug.Assert(x is null == y is null); + + if (x is null) + { + byte[] zero = new byte[d.Length]; + x = zero; + y = zero; + } + + if (!TryValidateNamedCurve(x, y, d)) + { + throw new CryptographicException(SR.Cryptography_InvalidECPrivateKeyParameters); + } + + return new ECDsaComponent( + ECCng.EncodeEccKeyBlob( + algorithm.PrivateKeyBlobMagicNumber, + x, + y, + d, + blob => ImportKeyBlob(blob, algorithm.CurveOid.FriendlyName, includePrivateParameters: true)), + algorithm); #endif } } @@ -130,26 +166,41 @@ public static unsafe ECDsaComponent ImportPublicKey(ECDsaAlgorithm algorithm, Re throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); } -#if NET + byte[] x = source.Slice(1, fieldWidth).ToArray(); + byte[] y = source.Slice(1 + fieldWidth).ToArray(); + +#if NET || NETSTANDARD ECParameters parameters = new ECParameters() { Curve = algorithm.Curve, Q = new ECPoint() { - X = source.Slice(1, fieldWidth).ToArray(), - Y = source.Slice(1 + fieldWidth).ToArray(), + X = x, + Y = y, } }; return new ECDsaComponent(ECDsa.Create(parameters), algorithm); -#else - throw new PlatformNotSupportedException(); +#else // NETFRAMEWORK +#if NET472_OR_GREATER +#error ECDsa.Create(ECParameters) is available in .NET Framework 4.7.2 and later, so this workaround is not needed anymore. +#endif + Debug.Assert(!string.IsNullOrEmpty(algorithm.CurveOid.FriendlyName)); + + return new ECDsaComponent( + ECCng.EncodeEccKeyBlob( + algorithm.PublicKeyBlobMagicNumber, + x, + y, + d: null, + blob => ImportKeyBlob(blob, algorithm.CurveOid.FriendlyName, includePrivateParameters: false)), + algorithm); #endif } internal override bool TryExportPrivateKey(Span destination, out int bytesWritten) { -#if NET +#if NET || NETSTANDARD ECParameters ecParameters = _ecdsa.ExportParameters(includePrivateParameters: true); Debug.Assert(ecParameters.D != null); @@ -166,65 +217,96 @@ internal override bool TryExportPrivateKey(Span destination, out int bytes // The curve OID must match the composite ML-DSA algorithm. if (!ecParameters.Curve.IsNamed || - (ecParameters.Curve.Oid.Value != _algorithm.Curve.Oid.Value && ecParameters.Curve.Oid.FriendlyName != _algorithm.Curve.Oid.FriendlyName)) + (ecParameters.Curve.Oid.Value != _algorithm.CurveOidValue && ecParameters.Curve.Oid.FriendlyName != _algorithm.CurveOid.FriendlyName)) { Debug.Fail("Unexpected curve OID."); throw new CryptographicException(); } - return TryWriteKey(ecParameters.D, ecParameters.Q.X, ecParameters.Q.Y, _algorithm.CurveOid, destination, out bytesWritten); - } -#else - throw new PlatformNotSupportedException(); -#endif -#if NET - static bool TryWriteKey(byte[] d, byte[]? x, byte[]? y, string curveOid, Span destination, out int bytesWritten) - { AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); try { - // ECPrivateKey - using (writer.PushSequence()) + WriteKey(ecParameters.D, ecParameters.Q.X, ecParameters.Q.Y, _algorithm.CurveOidValue, writer); + return writer.TryEncode(destination, out bytesWritten); + } + finally + { + writer.Reset(); + } + } +#else + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + + try + { + _ecdsa.Key.ExportKeyBlob( + CngKeyBlobFormat.EccPrivateBlob.Format, + blob => { - // version 1 - writer.WriteInteger(1); + ECCng.DecodeEccKeyBlob( + blob, + (magic, x, y, d) => + { + if (magic != _algorithm.PrivateKeyBlobMagicNumber) + { + Debug.Fail("Unexpected magic number."); + throw new CryptographicException(); + } + + if (!TryValidateNamedCurve(x, y, d)) + { + Debug.Fail("Invalid EC parameters."); + throw new CryptographicException(); + } + + WriteKey(d, x, y, _algorithm.CurveOidValue, writer); + return true; + }); + }); + + return writer.TryEncode(destination, out bytesWritten); + } + finally + { + writer.Reset(); + } +#endif - // privateKey - writer.WriteOctetString(d); + static void WriteKey(byte[] d, byte[]? x, byte[]? y, string curveOid, AsnWriter writer) + { + // ECPrivateKey + using (writer.PushSequence()) + { + // version 1 + writer.WriteInteger(1); - // domainParameters - using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true))) - { - writer.WriteObjectIdentifier(curveOid); - } + // privateKey + writer.WriteOctetString(d); - // publicKey - if (x != null) - { - Debug.Assert(y != null); + // domainParameters + using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true))) + { + writer.WriteObjectIdentifier(curveOid); + } - using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true))) - { - EccKeyFormatHelper.WriteUncompressedPublicKey(x, y, writer); - } + // publicKey + if (x != null) + { + Debug.Assert(y != null); + + using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true))) + { + EccKeyFormatHelper.WriteUncompressedPublicKey(x, y, writer); } } - - return writer.TryEncode(destination, out bytesWritten); - } - finally - { - writer.Reset(); } } -#endif } internal override bool TryExportPublicKey(Span destination, out int bytesWritten) { -#if NET int fieldWidth = _algorithm.KeySizeInBytes; if (destination.Length < 1 + 2 * fieldWidth) @@ -235,11 +317,49 @@ internal override bool TryExportPublicKey(Span destination, out int bytesW return false; } + byte[]? x = null; + byte[]? y = null; + +#if NET || NETSTANDARD ECParameters ecParameters = _ecdsa.ExportParameters(includePrivateParameters: false); ecParameters.Validate(); - if (ecParameters.Q.X?.Length != fieldWidth) + x = ecParameters.Q.X; + y = ecParameters.Q.Y; +#else + // Public parameters, x and y, don't need pinning so they can be pulled out of the inner lambdas. + _ecdsa.Key.ExportKeyBlob( + CngKeyBlobFormat.EccPublicBlob.Format, + blob => + { + ECCng.DecodeEccKeyBlob( + blob, + (magic, localX, localY, _) => + { + if (magic != _algorithm.PublicKeyBlobMagicNumber) + { + Debug.Fail("Unexpected magic number."); + throw new CryptographicException(); + } + + if (!TryValidateNamedCurve(localX, localY, null)) + { + Debug.Fail("Invalid EC parameters."); + throw new CryptographicException(); + } + + x = localX; + y = localY; + return true; + }); + }); +#endif + + Debug.Assert(x is not null); + Debug.Assert(y is not null); + + if (x.Length != fieldWidth || y.Length != fieldWidth) { Debug.Fail("Unexpected key size."); throw new CryptographicException(); @@ -248,14 +368,11 @@ internal override bool TryExportPublicKey(Span destination, out int bytesW // Uncompressed ECPoint format destination[0] = 0x04; - ecParameters.Q.X.CopyTo(destination.Slice(1, fieldWidth)); - ecParameters.Q.Y.CopyTo(destination.Slice(1 + fieldWidth)); + x.CopyTo(destination.Slice(1, fieldWidth)); + y.CopyTo(destination.Slice(1 + fieldWidth)); bytesWritten = 1 + 2 * fieldWidth; return true; -#else - throw new PlatformNotSupportedException(); -#endif } internal override bool VerifyData( @@ -269,7 +386,18 @@ internal override bool VerifyData( #if NET return _ecdsa.VerifyData(data, signature, _algorithm.HashAlgorithmName, DSASignatureFormat.Rfc3279DerSequence); #else - throw new PlatformNotSupportedException(); + byte[] ieeeSignature = null; + + try + { + ieeeSignature = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(signature, _algorithm.KeySizeInBits); + } + catch (CryptographicException) + { + return false; + } + + return _ecdsa.VerifyData(data, ieeeSignature, _algorithm.HashAlgorithmName); #endif } @@ -290,7 +418,15 @@ internal override int SignData( return bytesWritten; #else - throw new PlatformNotSupportedException(); + byte[] ieeeSignature = _ecdsa.SignData(data, _algorithm.HashAlgorithmName); + + if (!AsymmetricAlgorithmHelpers.TryConvertIeee1363ToDer(ieeeSignature, destination, out int bytesWritten)) + { + Debug.Fail("Buffer size should have been validated by caller."); + throw new CryptographicException(); + } + + return bytesWritten; #endif } @@ -304,6 +440,58 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + +#if NETFRAMEWORK +#if NET472_OR_GREATER +#error ECDsa.Create(ECParameters) is available in .NET Framework 4.7.2 and later, so this workaround is not needed anymore. +#endif + private static ECDsaCng ImportKeyBlob(byte[] ecKeyBlob, string curveName, bool includePrivateParameters) + { + CngKeyBlobFormat blobFormat = includePrivateParameters ? CngKeyBlobFormat.EccPrivateBlob : CngKeyBlobFormat.EccPublicBlob; + CngProvider provider = CngProvider.MicrosoftSoftwareKeyStorageProvider; + + using (SafeNCryptProviderHandle providerHandle = provider.OpenStorageProvider()) + using (SafeNCryptKeyHandle keyHandle = ECCng.ImportKeyBlob(blobFormat.Format, ecKeyBlob, curveName, providerHandle)) + using (CngKey key = CngKey.Open(keyHandle, CngKeyHandleOpenOptions.EphemeralKey)) + { + key.SetExportPolicy(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport); + return new ECDsaCng(key); + } + } + + private static ECDsaCng CreateKey(string curveName) + { + CngKeyCreationParameters creationParameters = new CngKeyCreationParameters() + { + ExportPolicy = CngExportPolicies.AllowPlaintextExport, + }; + + byte[] curveNameBytes = new byte[(curveName.Length + 1) * sizeof(char)]; // +1 to add trailing null + System.Text.Encoding.Unicode.GetBytes(curveName, 0, curveName.Length, curveNameBytes, 0); + creationParameters.Parameters.Add(new CngProperty(KeyPropertyName.ECCCurveName, curveNameBytes, CngPropertyOptions.None)); + + using (CngKey key = CngKey.Create(new CngAlgorithm("ECDSA"), null, creationParameters)) + { + return new ECDsaCng(key); + } + } + + private static bool TryValidateNamedCurve(byte[]? x, byte[]? y, byte[]? d) + { + bool hasErrors = true; + + if (d is not null && y is null && x is null) + { + hasErrors = false; + } + else if (y is not null && x is not null && y.Length == x.Length) + { + hasErrors = (d is not null && (d.Length != x.Length)); + } + + return !hasErrors; + } +#endif } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs index e0a5829f4e3756..2491364550cd7b 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.cs @@ -9,6 +9,10 @@ using System.Runtime.Versioning; using Internal.Cryptography; +#if NETFRAMEWORK +using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber; +#endif + namespace System.Security.Cryptography { #if !SYSTEM_SECURITY_CRYPTOGRAPHY @@ -580,7 +584,7 @@ private static Dictionary CreateAlgo CompositeMLDsaAlgorithm.MLDsa44WithECDsaP256, new AlgorithmMetadata( MLDsaAlgorithm.MLDsa44, - new ECDsaAlgorithm(256, Oids.secp256r1, HashAlgorithmName.SHA256), + ECDsaAlgorithm.CreateP256(HashAlgorithmName.SHA256), [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x03], HashAlgorithmName.SHA256) }, @@ -620,7 +624,7 @@ private static Dictionary CreateAlgo CompositeMLDsaAlgorithm.MLDsa65WithECDsaP256, new AlgorithmMetadata( MLDsaAlgorithm.MLDsa65, - new ECDsaAlgorithm(256, Oids.secp256r1, HashAlgorithmName.SHA256), + ECDsaAlgorithm.CreateP256(HashAlgorithmName.SHA256), [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x08], HashAlgorithmName.SHA512) }, @@ -628,7 +632,7 @@ private static Dictionary CreateAlgo CompositeMLDsaAlgorithm.MLDsa65WithECDsaP384, new AlgorithmMetadata( MLDsaAlgorithm.MLDsa65, - new ECDsaAlgorithm(384, Oids.secp384r1, HashAlgorithmName.SHA384), + ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384), [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x09], HashAlgorithmName.SHA512) }, @@ -636,7 +640,7 @@ private static Dictionary CreateAlgo CompositeMLDsaAlgorithm.MLDsa65WithECDsaBrainpoolP256r1, new AlgorithmMetadata( MLDsaAlgorithm.MLDsa65, - new ECDsaAlgorithm(256, "1.3.36.3.3.2.8.1.1.7", HashAlgorithmName.SHA256), + ECDsaAlgorithm.CreateBrainpoolP256r1(HashAlgorithmName.SHA256), [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0A], HashAlgorithmName.SHA512) }, @@ -652,7 +656,7 @@ private static Dictionary CreateAlgo CompositeMLDsaAlgorithm.MLDsa87WithECDsaP384, new AlgorithmMetadata( MLDsaAlgorithm.MLDsa87, - new ECDsaAlgorithm(384, Oids.secp384r1, HashAlgorithmName.SHA384), + ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384), [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0C], HashAlgorithmName.SHA512) }, @@ -660,7 +664,7 @@ private static Dictionary CreateAlgo CompositeMLDsaAlgorithm.MLDsa87WithECDsaBrainpoolP384r1, new AlgorithmMetadata( MLDsaAlgorithm.MLDsa87, - new ECDsaAlgorithm(384, "1.3.36.3.3.2.8.1.1.11", HashAlgorithmName.SHA384), + ECDsaAlgorithm.CreateBrainpoolP384r1(HashAlgorithmName.SHA384), [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0D], HashAlgorithmName.SHA512) }, @@ -692,7 +696,7 @@ private static Dictionary CreateAlgo CompositeMLDsaAlgorithm.MLDsa87WithECDsaP521, new AlgorithmMetadata( MLDsaAlgorithm.MLDsa87, - new ECDsaAlgorithm(521, Oids.secp521r1, HashAlgorithmName.SHA512), + ECDsaAlgorithm.CreateP521(HashAlgorithmName.SHA512), [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x11], HashAlgorithmName.SHA512) } @@ -722,37 +726,108 @@ private sealed class RsaAlgorithm(int keySizeInBits, HashAlgorithmName hashAlgor internal RSASignaturePadding Padding { get; } = padding; } - private sealed class ECDsaAlgorithm(int keySizeInBits, string curveOid, HashAlgorithmName hashAlgorithmName) + private sealed class ECDsaAlgorithm { - internal int KeySizeInBits { get; } = keySizeInBits; - internal HashAlgorithmName HashAlgorithmName { get; } = hashAlgorithmName; - internal string CurveOid { get; } = curveOid; + internal int KeySizeInBits { get; } + internal HashAlgorithmName HashAlgorithmName { get; } + +#if NET || NETSTANDARD + internal ECCurve Curve { get; } + internal Oid CurveOid => Curve.Oid; +#else + internal Oid CurveOid { get; } + internal KeyBlobMagicNumber PrivateKeyBlobMagicNumber { get; } + internal KeyBlobMagicNumber PublicKeyBlobMagicNumber { get; } +#endif + + internal string CurveOidValue => CurveOid.Value!; internal int KeySizeInBytes => (KeySizeInBits + 7) / 8; -#if NET - internal ECCurve Curve + private ECDsaAlgorithm( + int keySizeInBits, +#if NET || NETSTANDARD + ECCurve curve, +#else + Oid curveOid, + KeyBlobMagicNumber privateKeyBlobMagicNumber, + KeyBlobMagicNumber publicKeyBlobMagicNumber, +#endif + HashAlgorithmName hashAlgorithmName) { - get - { - return CurveOid switch - { - Oids.secp256r1 => ECCurve.NamedCurves.nistP256, - Oids.secp384r1 => ECCurve.NamedCurves.nistP384, - Oids.secp521r1 => ECCurve.NamedCurves.nistP521, - "1.3.36.3.3.2.8.1.1.7" => ECCurve.NamedCurves.brainpoolP256r1, - "1.3.36.3.3.2.8.1.1.11" => ECCurve.NamedCurves.brainpoolP384r1, - string oid => FailAndThrow(oid) - }; - - static ECCurve FailAndThrow(string oid) - { - Debug.Fail($"'{oid}' is not a valid ECDSA curve for Composite ML-DSA."); - throw new CryptographicException(); - } - } + KeySizeInBits = keySizeInBits; + HashAlgorithmName = hashAlgorithmName; + +#if NET || NETSTANDARD + Curve = curve; +#else + CurveOid = curveOid; + PrivateKeyBlobMagicNumber = privateKeyBlobMagicNumber; + PublicKeyBlobMagicNumber = publicKeyBlobMagicNumber; +#endif + + Debug.Assert(CurveOid.Value is not null); } + + internal static ECDsaAlgorithm CreateP256(HashAlgorithmName hashAlgorithmName) => + new ECDsaAlgorithm( + 256, +#if NET || NETSTANDARD + ECCurve.NamedCurves.nistP256, +#else + new Oid(Oids.secp256r1, "nistP256"), + KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P256_MAGIC, + KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC, +#endif + hashAlgorithmName); + + internal static ECDsaAlgorithm CreateP384(HashAlgorithmName hashAlgorithmName) => + new ECDsaAlgorithm( + 384, +#if NET || NETSTANDARD + ECCurve.NamedCurves.nistP384, +#else + new Oid(Oids.secp384r1, "nistP384"), + KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P384_MAGIC, + KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC, +#endif + hashAlgorithmName); + + internal static ECDsaAlgorithm CreateP521(HashAlgorithmName hashAlgorithmName) => + new ECDsaAlgorithm( + 521, +#if NET || NETSTANDARD + ECCurve.NamedCurves.nistP521, +#else + new Oid(Oids.secp521r1, "nistP521"), + KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P521_MAGIC, + KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC, +#endif + hashAlgorithmName); + + internal static ECDsaAlgorithm CreateBrainpoolP256r1(HashAlgorithmName hashAlgorithmName) => + new ECDsaAlgorithm( + 256, +#if NET || NETSTANDARD + ECCurve.NamedCurves.brainpoolP256r1, +#else + new Oid(Oids.brainpoolP256r1, "brainpoolP256r1"), + KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC, + KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC, +#endif + hashAlgorithmName); + + internal static ECDsaAlgorithm CreateBrainpoolP384r1(HashAlgorithmName hashAlgorithmName) => + new ECDsaAlgorithm( + 384, +#if NET || NETSTANDARD + ECCurve.NamedCurves.brainpoolP384r1, +#else + new Oid(Oids.brainpoolP384r1, "brainpoolP384r1"), + KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC, + KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC, #endif + hashAlgorithmName); } private sealed class EdDsaAlgorithm diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.NamedCurve.cs b/src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.NamedCurve.cs new file mode 100644 index 00000000000000..5dd46b4213c4af --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.NamedCurve.cs @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using BCRYPT_ECCKEY_BLOB = Interop.BCrypt.BCRYPT_ECCKEY_BLOB; +using ErrorCode = Interop.NCrypt.ErrorCode; +using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber; + +namespace System.Security.Cryptography +{ + internal static partial class ECCng + { + internal delegate TResult EncodeBlobFunc(byte[] blob); + + // When possible, operate on the blob inside the callback since the array will be pinned during its execution. + internal static T EncodeEccKeyBlob(KeyBlobMagicNumber magic, byte[] x, byte[] y, byte[]? d, Func encodeCallback, bool clearBlob = true) + { + bool includePrivateParameters = d is not null; + byte[] blob; + unsafe + { + // We need to build a key blob structured as follows: + // BCRYPT_ECCKEY_BLOB header + // byte[cbKey] Q.X + // byte[cbKey] Q.Y + // -- Only if "includePrivateParameters" is true -- + // byte[cbKey] D + + Debug.Assert(x.Length == y.Length); + + int blobSize; + + checked + { + blobSize = sizeof(BCRYPT_ECCKEY_BLOB) + + x.Length + + y.Length; + if (includePrivateParameters) + { + blobSize += d!.Length; + } + } + + blob = new byte[blobSize]; + fixed (byte* pBlob = &blob[0]) + { + try + { + // Build the header + BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*)pBlob; + pBcryptBlob->Magic = magic; + pBcryptBlob->cbKey = x.Length; + + // Emit the blob + int offset = sizeof(BCRYPT_ECCKEY_BLOB); + Interop.BCrypt.Emit(blob, ref offset, x); + Interop.BCrypt.Emit(blob, ref offset, y); + + if (includePrivateParameters) + { + Debug.Assert(x.Length == d?.Length); + Interop.BCrypt.Emit(blob, ref offset, d!); + } + + // We better have computed the right allocation size above! + Debug.Assert(offset == blobSize); + return encodeCallback(blob); + } + finally + { + if (clearBlob) + { + CryptographicOperations.ZeroMemory(blob); + } + } + } + } + } + + internal delegate T DecodeBlobFunc(KeyBlobMagicNumber magic, byte[] x, byte[] y, byte[]? d); + + // When possible, operate on the private key inside the callback since the array will be pinned during its execution. + internal static T DecodeEccKeyBlob(ReadOnlySpan ecBlob, DecodeBlobFunc decodeCallback, bool clearPrivateKey = true) + { + // We now have a buffer laid out as follows: + // BCRYPT_ECCKEY_BLOB header + // byte[cbKey] Q.X + // byte[cbKey] Q.Y + // -- Private only -- + // byte[cbKey] D + + KeyBlobMagicNumber magic = (KeyBlobMagicNumber)MemoryMarshal.Cast(ecBlob)[0]; + + unsafe + { + // Fail-fast if a rogue provider gave us a blob that isn't even the size of the blob header. + if (ecBlob.Length < sizeof(BCRYPT_ECCKEY_BLOB)) + throw ErrorCode.E_FAIL.ToCryptographicException(); + + fixed (byte* pEcBlob = &ecBlob[0]) + { + BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*)pEcBlob; + + int offset = sizeof(BCRYPT_ECCKEY_BLOB); + + byte[] x = Interop.BCrypt.Consume(ecBlob, ref offset, pBcryptBlob->cbKey); + byte[] y = Interop.BCrypt.Consume(ecBlob, ref offset, pBcryptBlob->cbKey); + + if (offset < ecBlob.Length) + { + byte[] d = new byte[pBcryptBlob->cbKey]; + + fixed (byte* pinnedD = d) + { + try + { + Interop.BCrypt.Consume(ecBlob, ref offset, d.Length, d); + + Debug.Assert(offset == ecBlob.Length); + + return decodeCallback(magic, x, y, d); + } + finally + { + if (clearPrivateKey) + { + CryptographicOperations.ZeroMemory(d); + } + } + } + } + else + { + Debug.Assert(offset == ecBlob.Length); + + return decodeCallback(magic, x, y, null); + } + } + } + } + + internal static SafeNCryptKeyHandle ImportKeyBlob( + string blobType, + ReadOnlySpan keyBlob, + string curveName, + SafeNCryptProviderHandle provider) + { + ErrorCode errorCode; + SafeNCryptKeyHandle keyHandle; + + using (SafeUnicodeStringHandle safeCurveName = new SafeUnicodeStringHandle(curveName)) + { + Interop.BCrypt.BCryptBufferDesc desc = default; + Interop.BCrypt.BCryptBuffer buff = default; + + IntPtr descPtr = IntPtr.Zero; + IntPtr buffPtr = IntPtr.Zero; + try + { + descPtr = Marshal.AllocHGlobal(Marshal.SizeOf(desc)); + buffPtr = Marshal.AllocHGlobal(Marshal.SizeOf(buff)); + buff.cbBuffer = (curveName.Length + 1) * 2; // Add 1 for null terminator + buff.BufferType = Interop.BCrypt.CngBufferDescriptors.NCRYPTBUFFER_ECC_CURVE_NAME; + buff.pvBuffer = safeCurveName.DangerousGetHandle(); + Marshal.StructureToPtr(buff, buffPtr, false); + + desc.cBuffers = 1; + desc.pBuffers = buffPtr; + desc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION; + Marshal.StructureToPtr(desc, descPtr, false); + + errorCode = Interop.NCrypt.NCryptImportKey( + provider, + IntPtr.Zero, + blobType, + descPtr, + out keyHandle, + ref MemoryMarshal.GetReference(keyBlob), + keyBlob.Length, + 0); + } + finally + { + Marshal.FreeHGlobal(descPtr); + Marshal.FreeHGlobal(buffPtr); + } + } + + if (errorCode != ErrorCode.ERROR_SUCCESS) + { + Exception e = errorCode.ToCryptographicException(); + keyHandle.Dispose(); + if (errorCode == ErrorCode.NTE_INVALID_PARAMETER) + { + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, curveName), e); + } + throw e; + } + + return keyHandle; + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.cs index d7c9c8a1a56a1b..f31194296c8852 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.cs @@ -2,13 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.InteropServices; -using Internal.Cryptography; -using Microsoft.Win32.SafeHandles; using static Internal.NativeCrypto.BCryptNative; using BCRYPT_ECC_PARAMETER_HEADER = Interop.BCrypt.BCRYPT_ECC_PARAMETER_HEADER; using BCRYPT_ECCFULLKEY_BLOB = Interop.BCrypt.BCRYPT_ECCFULLKEY_BLOB; -using BCRYPT_ECCKEY_BLOB = Interop.BCrypt.BCRYPT_ECCKEY_BLOB; using ErrorCode = Interop.NCrypt.ErrorCode; using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber; @@ -20,49 +16,19 @@ internal static byte[] GetNamedCurveBlob(ref ECParameters parameters, bool ecdh) { Debug.Assert(parameters.Curve.IsNamed); - bool includePrivateParameters = (parameters.D != null); - byte[] blob; - unsafe - { - // We need to build a key blob structured as follows: - // BCRYPT_ECCKEY_BLOB header - // byte[cbKey] Q.X - // byte[cbKey] Q.Y - // -- Only if "includePrivateParameters" is true -- - // byte[cbKey] D + bool includePrivateParameters = parameters.D is not null; - int blobSize = sizeof(BCRYPT_ECCKEY_BLOB) + - parameters.Q.X!.Length + - parameters.Q.Y!.Length; - if (includePrivateParameters) - { - blobSize += parameters.D!.Length; - } + KeyBlobMagicNumber magic = ecdh ? + EcdhCurveNameToMagicNumber(parameters.Curve.Oid.FriendlyName, includePrivateParameters) : + EcdsaCurveNameToMagicNumber(parameters.Curve.Oid.FriendlyName, includePrivateParameters); - blob = new byte[blobSize]; - fixed (byte* pBlob = &blob[0]) - { - // Build the header - BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*)pBlob; - pBcryptBlob->Magic = ecdh ? - EcdhCurveNameToMagicNumber(parameters.Curve.Oid.FriendlyName, includePrivateParameters) : - EcdsaCurveNameToMagicNumber(parameters.Curve.Oid.FriendlyName, includePrivateParameters); - pBcryptBlob->cbKey = parameters.Q.X.Length; - - // Emit the blob - int offset = sizeof(BCRYPT_ECCKEY_BLOB); - Interop.BCrypt.Emit(blob, ref offset, parameters.Q.X); - Interop.BCrypt.Emit(blob, ref offset, parameters.Q.Y); - if (includePrivateParameters) - { - Interop.BCrypt.Emit(blob, ref offset, parameters.D!); - } - - // We better have computed the right allocation size above! - Debug.Assert(offset == blobSize); - } - } - return blob; + return EncodeEccKeyBlob( + magic, + parameters.Q.X!, + parameters.Q.Y!, + parameters.D, + static blob => blob, + clearBlob: false); // Returning blob to caller, so don't clear it. } internal static byte[] GetPrimeCurveBlob(ref ECParameters parameters, bool ecdh) @@ -152,42 +118,43 @@ internal static byte[] GetPrimeCurveBlob(ref ECParameters parameters, bool ecdh) internal static void ExportNamedCurveParameters(ref ECParameters ecParams, byte[] ecBlob, bool includePrivateParameters) { - // We now have a buffer laid out as follows: - // BCRYPT_ECCKEY_BLOB header - // byte[cbKey] Q.X - // byte[cbKey] Q.Y - // -- Private only -- - // byte[cbKey] D - - KeyBlobMagicNumber magic = (KeyBlobMagicNumber)BitConverter.ToInt32(ecBlob, 0); - - // Check the magic value in the key blob header. If the blob does not have the required magic, - // then throw a CryptographicException. - CheckMagicValueOfKey(magic, includePrivateParameters); - - unsafe + if (includePrivateParameters) { - // Fail-fast if a rogue provider gave us a blob that isn't even the size of the blob header. - if (ecBlob.Length < sizeof(BCRYPT_ECCKEY_BLOB)) - throw ErrorCode.E_FAIL.ToCryptographicException(); - - fixed (byte* pEcBlob = &ecBlob[0]) - { - BCRYPT_ECCKEY_BLOB* pBcryptBlob = (BCRYPT_ECCKEY_BLOB*)pEcBlob; - - int offset = sizeof(BCRYPT_ECCKEY_BLOB); - - ecParams.Q = new ECPoint + ecParams = DecodeEccKeyBlob( + ecBlob, + static (KeyBlobMagicNumber magic, byte[] x, byte[] y, byte[]? d) => { - X = Interop.BCrypt.Consume(ecBlob, ref offset, pBcryptBlob->cbKey), - Y = Interop.BCrypt.Consume(ecBlob, ref offset, pBcryptBlob->cbKey) - }; - - if (includePrivateParameters) + CheckMagicValueOfKey(magic, includePrivateParameters: true); + + return new ECParameters + { + Q = new ECPoint + { + X = x, + Y = y, + }, + D = d, + }; + }, + clearPrivateKey: false); // Returning key to caller, so don't clear it. + } + else + { + ecParams = DecodeEccKeyBlob( + ecBlob, + static (KeyBlobMagicNumber magic, byte[] x, byte[] y, byte[]? d) => { - ecParams.D = Interop.BCrypt.Consume(ecBlob, ref offset, pBcryptBlob->cbKey); - } - } + CheckMagicValueOfKey(magic, includePrivateParameters: false); + + return new ECParameters + { + Q = new ECPoint + { + X = x, + Y = y, + }, + }; + }); } } @@ -451,66 +418,5 @@ private static ECCurve.ECCurveType ConvertToCurveTypeEnum(Interop.BCrypt.ECC_CUR curveType == ECCurve.ECCurveType.PrimeTwistedEdwards); return curveType; } - - internal static SafeNCryptKeyHandle ImportKeyBlob( - string blobType, - ReadOnlySpan keyBlob, - string curveName, - SafeNCryptProviderHandle provider) - { - ErrorCode errorCode; - SafeNCryptKeyHandle keyHandle; - - using (SafeUnicodeStringHandle safeCurveName = new SafeUnicodeStringHandle(curveName)) - { - Interop.BCrypt.BCryptBufferDesc desc = default; - Interop.BCrypt.BCryptBuffer buff = default; - - IntPtr descPtr = IntPtr.Zero; - IntPtr buffPtr = IntPtr.Zero; - try - { - descPtr = Marshal.AllocHGlobal(Marshal.SizeOf(desc)); - buffPtr = Marshal.AllocHGlobal(Marshal.SizeOf(buff)); - buff.cbBuffer = (curveName.Length + 1) * 2; // Add 1 for null terminator - buff.BufferType = Interop.BCrypt.CngBufferDescriptors.NCRYPTBUFFER_ECC_CURVE_NAME; - buff.pvBuffer = safeCurveName.DangerousGetHandle(); - Marshal.StructureToPtr(buff, buffPtr, false); - - desc.cBuffers = 1; - desc.pBuffers = buffPtr; - desc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION; - Marshal.StructureToPtr(desc, descPtr, false); - - errorCode = Interop.NCrypt.NCryptImportKey( - provider, - IntPtr.Zero, - blobType, - descPtr, - out keyHandle, - ref MemoryMarshal.GetReference(keyBlob), - keyBlob.Length, - 0); - } - finally - { - Marshal.FreeHGlobal(descPtr); - Marshal.FreeHGlobal(buffPtr); - } - } - - if (errorCode != ErrorCode.ERROR_SUCCESS) - { - Exception e = errorCode.ToCryptographicException(); - keyHandle.Dispose(); - if (errorCode == ErrorCode.NTE_INVALID_PARAMETER) - { - throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, curveName), e); - } - throw e; - } - - return keyHandle; - } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs index d7dd37968b1849..15ced1c97f7c85 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs @@ -263,6 +263,9 @@ internal static partial class Oids internal const string secp384r1 = "1.3.132.0.34"; internal const string secp521r1 = "1.3.132.0.35"; + internal const string brainpoolP256r1 = "1.3.36.3.3.2.8.1.1.7"; + internal const string brainpoolP384r1 = "1.3.36.3.3.2.8.1.1.11"; + // LDAP internal const string DomainComponent = "0.9.2342.19200300.100.1.25"; diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs index 6c6e9c8565a54c..84e6cca5aee07a 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/CompositeMLDsa/CompositeMLDsaFactoryTests.cs @@ -185,7 +185,7 @@ public static void ImportBadPrivateKey_ECDsa_WrongCurve() AssertImportBadPrivateKey( algorithm, - CreateKeyWithCurveOid(ECCurve.NamedCurves.brainpoolP256r1.Oid.Value)); + CreateKeyWithCurveOid("1.3.36.3.3.2.8.1.1.7")); // brainpoolP256r1 // Domain parameters are optional, don't throw (unless platform does not support Composite ML-DSA) CompositeMLDsaTestHelpers.AssertImportPrivateKey( @@ -469,11 +469,7 @@ public static void IsAlgorithmSupported_AgreesWithPlatform(CompositeMLDsaAlgorit bool supported = CompositeMLDsaTestHelpers.ExecuteComponentFunc( algorithm, rsa => MLDsa.IsSupported, -#if NET ecdsa => ecdsa.IsSec && MLDsa.IsSupported, -#else - ecdsa => false, -#endif eddsa => false); Assert.Equal( diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj index 9199288186c28d..21d422abe5fa48 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj @@ -370,6 +370,8 @@ Link="Common\System\Security\Cryptography\AesAEAD.cs" /> + @@ -577,6 +579,8 @@ Link="Common\Interop\Windows\NCrypt\Interop.NCryptBuffer.cs" /> + Composite signature generation failed due to an error in one or both of the components. + + The specified curve '{0}' or its parameters are not valid for this platform. + ASN1 corrupted data. @@ -171,6 +174,9 @@ The size of the specified tag does not match the expected size of {0}. + + The specified key parameters are not valid. X and Y, or D, must be specified. X, Y must be the same length. If D is specified it must be the same length as X and Y if also specified. + Specified key is not a valid size for this algorithm. diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs index 464ab69639f00b..8a2f2c1ded858b 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/CngExtensions.cs @@ -49,6 +49,17 @@ internal static bool TryExportKeyBlob( } } + internal static void ExportKeyBlob( + this CngKey key, + string blobType, + CngHelpers.ExportKeyBlobCallback callback) + { + using (SafeNCryptKeyHandle keyHandle = key.Handle) + { + keyHandle.ExportKeyBlob(blobType, callback); + } + } + internal static byte[] ExportPkcs8KeyBlob( this CngKey key, ReadOnlySpan password, diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index bb7248e78862bb..d732f3a02a5e64 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1881,6 +1881,8 @@ Link="Common\System\Security\Cryptography\DSACng.ImportExport.cs" /> + - /// Convert Ieee1363 format of (r, s) to Der format - /// - public static byte[] ConvertIeee1363ToDer(ReadOnlySpan input) - { - AsnWriter writer = WriteIeee1363ToDer(input); - return writer.Encode(); - } - - internal static bool TryConvertIeee1363ToDer( - ReadOnlySpan input, - Span destination, - out int bytesWritten) - { - 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 SEQUENCE(INTEGER(r), INTEGER(s)). - int halfLength = input.Length / 2; - - 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(ReadOnlySpan input, int fieldSizeBits) - { - int fieldSizeBytes = BitsToBytes(fieldSizeBits); - int encodedSize = 2 * fieldSizeBytes; - byte[] response = new byte[encodedSize]; - - 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); - - try - { - AsnValueReader reader = new AsnValueReader(input, AsnEncodingRules.DER); - AsnValueReader sequenceReader = reader.ReadSequence(); - reader.ThrowIfNotEmpty(); - ReadOnlySpan rDer = sequenceReader.ReadIntegerBytes(); - ReadOnlySpan sDer = sequenceReader.ReadIntegerBytes(); - sequenceReader.ThrowIfNotEmpty(); - - CopySignatureField(rDer, destination.Slice(0, fieldSizeBytes)); - CopySignatureField(sDer, destination.Slice(fieldSizeBytes, fieldSizeBytes)); - return encodedSize; - } - catch (AsnContentException e) - { - throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); - } - } - /// /// Converts IeeeP1363 format to the specified signature format /// @@ -124,30 +50,6 @@ internal static byte[] ConvertSignatureToIeeeP1363( } } - private static void CopySignatureField(ReadOnlySpan signatureField, Span response) - { - if (signatureField.Length > response.Length) - { - 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); - } - - // If the field is too short then it needs to be prepended - // 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)); - } - internal static byte[]? ConvertSignatureToIeeeP1363( this DSA dsa, DSASignatureFormat currentFormat, diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs index a28da92a91a1ed..a3bf82a0c53d72 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs @@ -17,21 +17,6 @@ internal static partial class CngHelpers { private static readonly CngKeyBlobFormat s_cipherKeyBlobFormat = new CngKeyBlobFormat(Interop.NCrypt.NCRYPT_CIPHER_KEY_BLOB); - internal static SafeNCryptProviderHandle OpenStorageProvider(this CngProvider provider) - { - string providerName = provider.Provider; - SafeNCryptProviderHandle providerHandle; - ErrorCode errorCode = Interop.NCrypt.NCryptOpenStorageProvider(out providerHandle, providerName, 0); - - if (errorCode != ErrorCode.ERROR_SUCCESS) - { - providerHandle.Dispose(); - throw errorCode.ToCryptographicException(); - } - - return providerHandle; - } - /// /// Retrieve a well-known CNG dword property. (Note: .NET Framework compat: this helper likes to return special values /// rather than throw exceptions for missing or ill-formatted property values. Only use it for well-known properties that