diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs index b77702a6d4d9a0..88a4f8f7db5f37 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; @@ -102,6 +103,29 @@ private static partial int CryptoNative_X509StoreSetVerifyTime( [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_CheckX509Hostname", StringMarshalling = StringMarshalling.Utf8)] internal static partial int CheckX509Hostname(SafeX509Handle x509, string hostname, int cchHostname); + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + private static partial int CryptoNative_IsSignatureAlgorithmAvailable(string algorithm); + + internal static string? IsSignatureAlgorithmAvailable(string algorithm) + { + const int Available = 1; + const int NotAvailable = 0; + + int ret = CryptoNative_IsSignatureAlgorithmAvailable(algorithm); + return ret switch + { + Available => algorithm, + NotAvailable => null, + int other => throw Fail(other), + }; + + static CryptographicException Fail(int result) + { + Debug.Fail($"Unexpected result {result} from {nameof(CryptoNative_IsSignatureAlgorithmAvailable)}"); + return new CryptographicException(); + } + } + internal static byte[] GetAsn1StringBytes(IntPtr asn1) { return GetDynamicBuffer(GetAsn1StringBytes, asn1); diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Kem.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Kem.cs index be1756075bde53..941c82bd3c4e2f 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Kem.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Kem.cs @@ -107,13 +107,13 @@ internal static void EvpKemDecapsulate(SafeEvpPKeyHandle key, ReadOnlySpan } internal static void EvpKemExportPrivateSeed(SafeEvpPKeyHandle key, Span destination) => - ExportKeyContents(key, destination, CryptoNative_EvpKemExportPrivateSeed); + Interop.Crypto.ExportKeyContents(key, destination, CryptoNative_EvpKemExportPrivateSeed); internal static void EvpKemExportDecapsulationKey(SafeEvpPKeyHandle key, Span destination) => - ExportKeyContents(key, destination, CryptoNative_EvpKemExportDecapsulationKey); + Interop.Crypto.ExportKeyContents(key, destination, CryptoNative_EvpKemExportDecapsulationKey); internal static void EvpKemExportEncapsulationKey(SafeEvpPKeyHandle key, Span destination) => - ExportKeyContents(key, destination, CryptoNative_EvpKemExportEncapsulationKey); + Interop.Crypto.ExportKeyContents(key, destination, CryptoNative_EvpKemExportEncapsulationKey); internal static void EvpKemEncapsulate(SafeEvpPKeyHandle key, Span ciphertext, Span sharedSecret) { @@ -137,33 +137,5 @@ internal static void EvpKemEncapsulate(SafeEvpPKeyHandle key, Span ciphert throw new CryptographicException(); } } - - private static void ExportKeyContents( - SafeEvpPKeyHandle key, - Span destination, - Func, int, int> action) - { - const int Success = 1; - const int Fail = 0; - const int NotRetrievable = -1; - - int ret = action(key, destination, destination.Length); - - switch (ret) - { - case Success: - return; - case NotRetrievable: - destination.Clear(); - throw new CryptographicException(SR.Cryptography_NotRetrievable); - case Fail: - destination.Clear(); - throw CreateOpenSslCryptographicException(); - default: - destination.Clear(); - Debug.Fail($"Unexpected return value {ret}."); - throw new CryptographicException(); - } - } } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPKey.MLDsa.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPKey.MLDsa.cs new file mode 100644 index 00000000000000..29f5ea08b6b9bc --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPKey.MLDsa.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Crypto + { + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + private static partial SafeEvpPKeyHandle CryptoNative_MLDsaGenerateKey(string keyType, ReadOnlySpan seed, int seedLength); + + internal static SafeEvpPKeyHandle MLDsaGenerateKey(string algorithmName, ReadOnlySpan seed) + { + SafeEvpPKeyHandle handle = CryptoNative_MLDsaGenerateKey(algorithmName, seed, seed.Length); + Debug.Assert(handle != null, "handle != null"); + + if (handle.IsInvalid) + { + Exception ex = Interop.Crypto.CreateOpenSslCryptographicException(); + handle.Dispose(); + throw ex; + } + + return handle; + } + + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + private static partial SafeEvpPKeyHandle CryptoNative_MLDsaImportSecretKey(string keyType, ReadOnlySpan sk, int skLength); + + internal static SafeEvpPKeyHandle MLDsaImportSecretKey(string algorithmName, ReadOnlySpan sk) + { + SafeEvpPKeyHandle? handle = CryptoNative_MLDsaImportSecretKey(algorithmName, sk, sk.Length); + Debug.Assert(handle != null, "handle != null"); + + if (handle.IsInvalid) + { + Exception ex = Interop.Crypto.CreateOpenSslCryptographicException(); + handle.Dispose(); + throw ex; + } + + return handle; + } + + [LibraryImport(Libraries.CryptoNative, StringMarshalling = StringMarshalling.Utf8)] + private static partial SafeEvpPKeyHandle CryptoNative_MLDsaImportPublicKey(string keyType, ReadOnlySpan pk, int pkLength); + + internal static SafeEvpPKeyHandle MLDsaImportPublicKey(string algorithmName, ReadOnlySpan pk) + { + SafeEvpPKeyHandle handle = CryptoNative_MLDsaImportPublicKey(algorithmName, pk, pk.Length); + Debug.Assert(handle != null, "handle != null"); + + if (handle.IsInvalid) + { + Exception ex = Interop.Crypto.CreateOpenSslCryptographicException(); + handle.Dispose(); + throw ex; + } + + return handle; + } + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_MLDsaSignPure( + SafeEvpPKeyHandle pkey, IntPtr extraHandle, + ReadOnlySpan msg, int msgLength, + ReadOnlySpan context, int contextLength, + Span destination, int destinationLength); + + internal static void MLDsaSignPure( + SafeEvpPKeyHandle pkey, + ReadOnlySpan msg, + ReadOnlySpan context, + Span destination) + { + int ret = CryptoNative_MLDsaSignPure( + pkey, pkey.ExtraHandle, + msg, msg.Length, + context, context.Length, + destination, destination.Length); + + if (ret != 1) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + } + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_MLDsaVerifyPure( + SafeEvpPKeyHandle pkey, IntPtr extraHandle, + ReadOnlySpan msg, int msgLength, + ReadOnlySpan context, int contextLength, + ReadOnlySpan signature, int signatureLength); + + internal static bool MLDsaVerifyPure( + SafeEvpPKeyHandle pkey, + ReadOnlySpan msg, + ReadOnlySpan context, + ReadOnlySpan signature) + { + int ret = CryptoNative_MLDsaVerifyPure( + pkey, pkey.ExtraHandle, + msg, msg.Length, + context, context.Length, + signature, signature.Length); + + if (ret == 1) + { + return true; + } + else if (ret == 0) + { + return false; + } + else + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + } + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_MLDsaExportSecretKey(SafeEvpPKeyHandle pkey, Span destination, int destinationLength); + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_MLDsaExportSeed(SafeEvpPKeyHandle pkey, Span destination, int destinationLength); + + [LibraryImport(Libraries.CryptoNative)] + private static partial int CryptoNative_MLDsaExportPublicKey(SafeEvpPKeyHandle pkey, Span destination, int destinationLength); + + internal static void MLDsaExportSecretKey(SafeEvpPKeyHandle key, Span destination) => + Interop.Crypto.ExportKeyContents(key, destination, CryptoNative_MLDsaExportSecretKey); + + internal static void MLDsaExportSeed(SafeEvpPKeyHandle key, Span destination) => + Interop.Crypto.ExportKeyContents(key, destination, CryptoNative_MLDsaExportSeed); + + internal static void MLDsaExportPublicKey(SafeEvpPKeyHandle key, Span destination) => + Interop.Crypto.ExportKeyContents(key, destination, CryptoNative_MLDsaExportPublicKey); + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPKey.MLDsaAlgs.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPKey.MLDsaAlgs.cs new file mode 100644 index 00000000000000..3c33a0b903b254 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPKey.MLDsaAlgs.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Crypto + { + internal static partial class EvpPKeyMLDsaAlgs + { + internal static string? MLDsa44 { get; } + internal static string? MLDsa65 { get; } + internal static string? MLDsa87 { get; } + + static EvpPKeyMLDsaAlgs() + { + CryptoInitializer.Initialize(); + + // Do not use property initializers for these because we need to ensure CryptoInitializer.Initialize + // is called first. Property initializers happen before cctors, so instead set the property after the + // initializer is run. + MLDsa44 = IsSignatureAlgorithmAvailable(MLDsaAlgorithm.MLDsa44.Name); + MLDsa65 = IsSignatureAlgorithmAvailable(MLDsaAlgorithm.MLDsa65.Name); + MLDsa87 = IsSignatureAlgorithmAvailable(MLDsaAlgorithm.MLDsa87.Name); + } + } + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs index 559ede03ae1072..1146fde0988f77 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs @@ -341,6 +341,34 @@ internal static SafeEvpPKeyHandle LoadKeyFromProvider( } } + internal static void ExportKeyContents( + SafeEvpPKeyHandle key, + Span destination, + Func, int, int> action) + { + const int Success = 1; + const int Fail = 0; + const int NotRetrievable = -1; + + int ret = action(key, destination, destination.Length); + + switch (ret) + { + case Success: + return; + case NotRetrievable: + destination.Clear(); + throw new CryptographicException(SR.Cryptography_NotRetrievable); + case Fail: + destination.Clear(); + throw CreateOpenSslCryptographicException(); + default: + destination.Clear(); + Debug.Fail($"Unexpected return value {ret}."); + throw new CryptographicException(); + } + } + internal enum EvpAlgorithmId { Unknown = 0, diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs index ec1ae759c610a5..a800b8cc53650d 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs @@ -8,6 +8,8 @@ using System.Security.Cryptography.Asn1; using Internal.Cryptography; +#pragma warning disable CA1510, CA1513 + // The type being internal is making unused parameter warnings fire for // not-implemented methods. Suppress those warnings. #pragma warning disable IDE0060 @@ -24,7 +26,7 @@ namespace System.Security.Cryptography /// cryptographic libraries. /// [Experimental(Experimentals.PostQuantumCryptographyDiagId)] - internal abstract partial class MLDsa : IDisposable + public abstract partial class MLDsa : IDisposable #if DESIGNTIMEINTERFACES #pragma warning disable SA1001 , IImportExportShape @@ -32,18 +34,13 @@ internal abstract partial class MLDsa : IDisposable #endif { private const int MaxContextLength = 255; - private const int PrivateSeedSizeInBytes = 32; - private readonly ParameterSetInfo _parameterSetInfo; + /// + /// Gets the specific ML-DSA algorithm for this key. + /// + public MLDsaAlgorithm Algorithm { get; } private bool _disposed; - private MLDsa(ParameterSetInfo parameterSetInfo) - { - Debug.Assert(parameterSetInfo is not null); - - _parameterSetInfo = parameterSetInfo; - } - /// /// Initializes a new instance of the class. /// @@ -51,13 +48,21 @@ private MLDsa(ParameterSetInfo parameterSetInfo) /// The specific ML-DSA algorithm for this key. /// protected MLDsa(MLDsaAlgorithm algorithm) - : this(ParameterSetInfo.GetParameterSetInfo(algorithm)) { + if (algorithm is null) + { + throw new ArgumentNullException(nameof(algorithm)); + } + + Algorithm = algorithm; } protected void ThrowIfDisposed() { - ObjectDisposedException.ThrowIf(_disposed, this); + if (_disposed) + { + throw new ObjectDisposedException(typeof(MLDsa).FullName); + } } /// @@ -68,83 +73,17 @@ protected void ThrowIfDisposed() /// public static bool IsSupported { get; } = MLDsaImplementation.SupportsAny(); - /// - /// Gets the size of the signature for the specified algorithm. - /// - /// - /// The specific ML-DSA algorithm to query. - /// - /// - /// The size, in bytes, of the signature for the specified algorithm. - /// - /// - /// is not a valid ML-DSA algorithm identifier. - /// - public static int GetSignatureSizeInBytes(MLDsaAlgorithm algorithm) => - ParameterSetInfo.GetParameterSetInfo(algorithm).SignatureSizeInBytes; - - /// - /// Gets the size of the ML-DSA secret key for the specified algorithm. - /// - /// - /// The specific ML-DSA algorithm to query. - /// - /// - /// The size, in bytes, of the ML-DSA secret key for the specified algorithm. - /// - /// - /// is not a valid ML-DSA algorithm identifier. - /// - public static int GetSecretKeySizeInBytes(MLDsaAlgorithm algorithm) => - ParameterSetInfo.GetParameterSetInfo(algorithm).SecretKeySizeInBytes; - - /// - /// Gets the size of the ML-DSA public key for the specified algorithm. - /// - /// - /// The specific ML-DSA algorithm to query. - /// - /// - /// The size, in bytes, of the ML-DSA public key for the specified algorithm. - /// - /// - /// is not a valid ML-DSA algorithm identifier. - /// - public static int GetPublicKeySizeInBytes(MLDsaAlgorithm algorithm) => - ParameterSetInfo.GetParameterSetInfo(algorithm).PublicKeySizeInBytes; - - /// - /// Gets the size, in bytes, of the signature for the current instance. - /// - /// - /// The size, in bytes, of the signature for the current instance. - /// - public int SignatureSizeInBytes => _parameterSetInfo.SignatureSizeInBytes; - - /// - /// Gets the size, in bytes, of the ML-DSA secret key for the current instance. - /// - /// - /// The size, in bytes, of the ML-DSA secret key for the current instance. - /// - public int SecretKeySizeInBytes => _parameterSetInfo.SecretKeySizeInBytes; - - /// - /// Gets the size, in bytes, of the ML-DSA public key for the current instance. - /// - /// - /// The size, in bytes, of the ML-DSA public key for the current instance. - /// - public int PublicKeySizeInBytes => _parameterSetInfo.PublicKeySizeInBytes; - /// /// Releases all resources used by the class. /// public void Dispose() { - Dispose(true); - _disposed = true; - GC.SuppressFinalize(this); + if (!_disposed) + { + Dispose(true); + _disposed = true; + GC.SuppressFinalize(this); + } } /// @@ -180,8 +119,6 @@ public void Dispose() /// public int SignData(ReadOnlySpan data, Span destination, ReadOnlySpan context = default) { - ThrowIfDisposed(); - if (context.Length > MaxContextLength) { throw new ArgumentOutOfRangeException( @@ -190,13 +127,14 @@ public int SignData(ReadOnlySpan data, Span destination, ReadOnlySpa SR.Argument_SignatureContextTooLong255); } - if (destination.Length < SignatureSizeInBytes) + if (destination.Length < Algorithm.SignatureSizeInBytes) { - throw new ArgumentException(nameof(destination), SR.Argument_DestinationTooShort); + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); } - SignDataCore(data, context, destination.Slice(0, SignatureSizeInBytes)); - return SignatureSizeInBytes; + ThrowIfDisposed(); + SignDataCore(data, context, destination.Slice(0, Algorithm.SignatureSizeInBytes)); + return Algorithm.SignatureSizeInBytes; } // TODO: SignPreHash @@ -231,8 +169,6 @@ public int SignData(ReadOnlySpan data, Span destination, ReadOnlySpa /// public bool VerifyData(ReadOnlySpan data, ReadOnlySpan signature, ReadOnlySpan context = default) { - ThrowIfDisposed(); - if (context.Length > MaxContextLength) { throw new ArgumentOutOfRangeException( @@ -241,7 +177,9 @@ public bool VerifyData(ReadOnlySpan data, ReadOnlySpan signature, Re SR.Argument_SignatureContextTooLong255); } - if (signature.Length != SignatureSizeInBytes) + ThrowIfDisposed(); + + if (signature.Length != Algorithm.SignatureSizeInBytes) { return false; } @@ -686,15 +624,14 @@ public string ExportEncryptedPkcs8PrivateKeyPem( /// public int ExportMLDsaPublicKey(Span destination) { - ThrowIfDisposed(); - - if (destination.Length < PublicKeySizeInBytes) + if (destination.Length < Algorithm.PublicKeySizeInBytes) { - throw new ArgumentException(nameof(destination), SR.Argument_DestinationTooShort); + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); } - ExportMLDsaPublicKeyCore(destination.Slice(0, PublicKeySizeInBytes)); - return PublicKeySizeInBytes; + ThrowIfDisposed(); + ExportMLDsaPublicKeyCore(destination.Slice(0, Algorithm.PublicKeySizeInBytes)); + return Algorithm.PublicKeySizeInBytes; } /// @@ -714,15 +651,14 @@ public int ExportMLDsaPublicKey(Span destination) /// public int ExportMLDsaSecretKey(Span destination) { - ThrowIfDisposed(); - - if (destination.Length < SecretKeySizeInBytes) + if (destination.Length < Algorithm.SecretKeySizeInBytes) { - throw new ArgumentException(nameof(destination), SR.Argument_DestinationTooShort); + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); } - ExportMLDsaSecretKeyCore(destination.Slice(0, SecretKeySizeInBytes)); - return SecretKeySizeInBytes; + ThrowIfDisposed(); + ExportMLDsaSecretKeyCore(destination.Slice(0, Algorithm.SecretKeySizeInBytes)); + return Algorithm.SecretKeySizeInBytes; } /// @@ -742,54 +678,44 @@ public int ExportMLDsaSecretKey(Span destination) /// public int ExportMLDsaPrivateSeed(Span destination) { - ThrowIfDisposed(); - - if (destination.Length < PrivateSeedSizeInBytes) + if (destination.Length < Algorithm.PrivateSeedSizeInBytes) { - throw new ArgumentException(nameof(destination), SR.Argument_DestinationTooShort); + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); } - ExportMLDsaPrivateSeedCore(destination.Slice(0, PrivateSeedSizeInBytes)); - return PrivateSeedSizeInBytes; - } - - /// - /// Generates a new ML-DSA-44 key. - /// - /// - /// The generated key. - /// - public static MLDsa GenerateMLDsa44Key() - { - ThrowIfNotSupported(); - - return MLDsaImplementation.GenerateKey(MLDsaAlgorithm.MLDsa44); + ThrowIfDisposed(); + ExportMLDsaPrivateSeedCore(destination.Slice(0, Algorithm.PrivateSeedSizeInBytes)); + return Algorithm.PrivateSeedSizeInBytes; } /// - /// Generates a new ML-DSA-65 key. + /// Generates a new ML-DSA key. /// + /// + /// An algorithm identifying what kind of ML-DSA key to generate. + /// /// /// The generated key. /// - public static MLDsa GenerateMLDsa65Key() + /// + /// is + /// + /// + /// An error occured generating the ML-DSA key. + /// + /// + /// The platform does not support ML-DSA. Callers can use the property + /// to determine if the platform supports ML-DSA. + /// + public static MLDsa GenerateKey(MLDsaAlgorithm algorithm) { - ThrowIfNotSupported(); - - return MLDsaImplementation.GenerateKey(MLDsaAlgorithm.MLDsa65); - } + if (algorithm is null) + { + throw new ArgumentNullException(nameof(algorithm)); + } - /// - /// Generates a new ML-DSA-87 key. - /// - /// - /// The generated key. - /// - public static MLDsa GenerateMLDsa87Key() - { ThrowIfNotSupported(); - - return MLDsaImplementation.GenerateKey(MLDsaAlgorithm.MLDsa87); + return MLDsaImplementation.GenerateKeyImpl(algorithm); } /// @@ -827,7 +753,7 @@ public static MLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan source) AsnValueReader reader = new AsnValueReader(source, AsnEncodingRules.DER); SubjectPublicKeyInfoAsn.Decode(ref reader, manager.Memory, out SubjectPublicKeyInfoAsn spki); - ParameterSetInfo info = ParameterSetInfo.GetParameterSetInfoFromOid(spki.Algorithm.Algorithm); + MLDsaAlgorithm algorithm = MLDsaAlgorithm.GetMLDsaAlgorithmFromOid(spki.Algorithm.Algorithm); if (spki.Algorithm.Parameters.HasValue) { @@ -836,7 +762,7 @@ public static MLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan source) throw Helpers.CreateAlgorithmUnknownException(writer); } - return MLDsaImplementation.ImportPublicKey(info, spki.SubjectPublicKey.Span); + return MLDsaImplementation.ImportPublicKey(algorithm, spki.SubjectPublicKey.Span); } } } @@ -877,7 +803,7 @@ public static MLDsa ImportPkcs8PrivateKey(ReadOnlySpan source) AsnValueReader reader = new AsnValueReader(source, AsnEncodingRules.DER); PrivateKeyInfoAsn.Decode(ref reader, manager.Memory, out PrivateKeyInfoAsn pki); - ParameterSetInfo info = ParameterSetInfo.GetParameterSetInfoFromOid(pki.PrivateKeyAlgorithm.Algorithm); + MLDsaAlgorithm algorithm = MLDsaAlgorithm.GetMLDsaAlgorithmFromOid(pki.PrivateKeyAlgorithm.Algorithm); if (pki.PrivateKeyAlgorithm.Parameters.HasValue) { @@ -886,7 +812,7 @@ public static MLDsa ImportPkcs8PrivateKey(ReadOnlySpan source) throw Helpers.CreateAlgorithmUnknownException(writer); } - return MLDsaImplementation.ImportPkcs8PrivateKeyValue(info, pki.PrivateKey.Span); + return MLDsaImplementation.ImportPkcs8PrivateKeyValue(algorithm, pki.PrivateKey.Span); } } } @@ -1096,17 +1022,18 @@ public static MLDsa ImportFromEncryptedPem(ReadOnlySpan source, ReadOnlySp /// public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) { - ThrowIfNotSupported(); - ArgumentNullException.ThrowIfNull(algorithm); - - ParameterSetInfo info = ParameterSetInfo.GetParameterSetInfo(algorithm); + if (algorithm is null) + { + throw new ArgumentNullException(nameof(algorithm)); + } - if (source.Length != info.PublicKeySizeInBytes) + if (source.Length != algorithm.PublicKeySizeInBytes) { - throw new CryptographicException(SR.Cryptography_KeyWrongSizeForAlgorithm); + throw new ArgumentException(SR.Cryptography_KeyWrongSizeForAlgorithm, nameof(source)); } - return MLDsaImplementation.ImportPublicKey(info, source); + ThrowIfNotSupported(); + return MLDsaImplementation.ImportPublicKey(algorithm, source); } /// @@ -1136,17 +1063,18 @@ public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan< /// public static MLDsa ImportMLDsaSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) { - ThrowIfNotSupported(); - ArgumentNullException.ThrowIfNull(algorithm); - - ParameterSetInfo info = ParameterSetInfo.GetParameterSetInfo(algorithm); + if (algorithm is null) + { + throw new ArgumentNullException(nameof(algorithm)); + } - if (source.Length != info.SecretKeySizeInBytes) + if (source.Length != algorithm.SecretKeySizeInBytes) { - throw new CryptographicException(SR.Cryptography_KeyWrongSizeForAlgorithm); + throw new ArgumentException(SR.Cryptography_KeyWrongSizeForAlgorithm, nameof(source)); } - return MLDsaImplementation.ImportSecretKey(info, source); + ThrowIfNotSupported(); + return MLDsaImplementation.ImportSecretKey(algorithm, source); } /// @@ -1176,16 +1104,18 @@ public static MLDsa ImportMLDsaSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan< /// public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpan source) { - ThrowIfNotSupported(); - - ParameterSetInfo info = ParameterSetInfo.GetParameterSetInfo(algorithm); + if (algorithm is null) + { + throw new ArgumentNullException(nameof(algorithm)); + } - if (source.Length != PrivateSeedSizeInBytes) + if (source.Length != algorithm.PrivateSeedSizeInBytes) { - throw new CryptographicException(SR.Cryptography_KeyWrongSizeForAlgorithm); + throw new ArgumentException(SR.Cryptography_KeyWrongSizeForAlgorithm, nameof(source)); } - return MLDsaImplementation.ImportSeed(info, source); + ThrowIfNotSupported(); + return MLDsaImplementation.ImportSeed(algorithm, source); } /// @@ -1266,11 +1196,11 @@ private AsnWriter ExportSubjectPublicKeyInfoCore() { ThrowIfDisposed(); - byte[] rented = CryptoPool.Rent(_parameterSetInfo.PublicKeySizeInBytes); + byte[] rented = CryptoPool.Rent(Algorithm.PublicKeySizeInBytes); try { - Span keySpan = rented.AsSpan(0, _parameterSetInfo.PublicKeySizeInBytes); + Span keySpan = rented.AsSpan(0, Algorithm.PublicKeySizeInBytes); ExportMLDsaPublicKey(keySpan); AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); @@ -1279,7 +1209,7 @@ private AsnWriter ExportSubjectPublicKeyInfoCore() { using (writer.PushSequence()) { - writer.WriteObjectIdentifier(_parameterSetInfo.Oid); + writer.WriteObjectIdentifier(Algorithm.Oid); } writer.WriteBitString(keySpan); @@ -1299,7 +1229,7 @@ private AsnWriter ExportEncryptedPkcs8PrivateKeyCore(ReadOnlySpan password ThrowIfDisposed(); // TODO: Determine a more appropriate maximum size once the format is actually known. - int size = _parameterSetInfo.SecretKeySizeInBytes * 2; + int size = Algorithm.SecretKeySizeInBytes * 2; // The buffer is only being passed out as a span, so the derived type can't meaningfully // hold on to it without being malicious. byte[] rented = CryptoPool.Rent(size); @@ -1331,7 +1261,7 @@ private AsnWriter ExportEncryptedPkcs8PrivateKeyCore(ReadOnlySpan password ThrowIfDisposed(); // TODO: Determine a more appropriate maximum size once the format is actually known. - int initialSize = _parameterSetInfo.SecretKeySizeInBytes * 2; + int initialSize = Algorithm.SecretKeySizeInBytes * 2; // The buffer is only being passed out as a span, so the derived type can't meaningfully // hold on to it without being malicious. byte[] rented = CryptoPool.Rent(initialSize); @@ -1364,75 +1294,5 @@ internal static void ThrowIfNotSupported() throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(MLDsa))); } } - - [DoesNotReturn] - private static ParameterSetInfo ThrowAlgorithmUnknown(string algorithmId) - { - throw new CryptographicException( - SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, algorithmId)); - } - - internal sealed class ParameterSetInfo - { - // TODO: If MLDsaAlgorithm is a class, this class can be merged into it. - // TODO: Some of the information maybe then becomes public on MLDsaAlgorithm, rather than MLDsa? - - internal int SecretKeySizeInBytes { get; } - internal int PublicKeySizeInBytes { get; } - internal int SignatureSizeInBytes { get; } - internal MLDsaAlgorithm Algorithm { get; } - internal string Oid { get; } - - private ParameterSetInfo( - int secretKeySizeInBytes, - int publicKeySizeInBytes, - int signatureSizeInBytes, - MLDsaAlgorithm algorithm, - string oid) - { - SecretKeySizeInBytes = secretKeySizeInBytes; - PublicKeySizeInBytes = publicKeySizeInBytes; - SignatureSizeInBytes = signatureSizeInBytes; - Algorithm = algorithm; - Oid = oid; - } - - // ML-DSA parameter sets, and the sizes associated with them, - // are defined in FIPS 204, section 4 "Parameter Sets". - // particularly Table 2 "Sizes (in bytes) of keys and signatures of ML-DSA" - - internal static readonly ParameterSetInfo MLDsa44 = - new ParameterSetInfo(2560, 1312, 2420, MLDsaAlgorithm.MLDsa44, Oids.MLDsa44); - - internal static readonly ParameterSetInfo MLDsa65 = - new ParameterSetInfo(4032, 1952, 3309, MLDsaAlgorithm.MLDsa65, Oids.MLDsa65); - - internal static readonly ParameterSetInfo MLDsa87 = - new ParameterSetInfo(4896, 2592, 4627, MLDsaAlgorithm.MLDsa87, Oids.MLDsa87); - - internal static ParameterSetInfo GetParameterSetInfo(MLDsaAlgorithm algorithm) - { - ArgumentNullException.ThrowIfNull(algorithm); - - return algorithm.Name switch - { - "ML-DSA-44" => MLDsa44, - "ML-DSA-65" => MLDsa65, - "ML-DSA-87" => MLDsa87, - _ => ThrowAlgorithmUnknown(algorithm.Name), - }; - } - - internal static ParameterSetInfo GetParameterSetInfoFromOid(string oid) - { - return oid switch - { - Oids.MLDsa44 => MLDsa44, - Oids.MLDsa65 => MLDsa65, - Oids.MLDsa87 => MLDsa87, - _ => ThrowAlgorithmUnknown(oid), - }; - } - } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaAlgorithm.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaAlgorithm.cs index 7f6a4a59618998..de5cd727ba12b2 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaAlgorithm.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaAlgorithm.cs @@ -9,7 +9,7 @@ namespace System.Security.Cryptography /// Represents a specific algorithm within the ML-DSA family. /// [Experimental(Experimentals.PostQuantumCryptographyDiagId)] - internal sealed class MLDsaAlgorithm + public sealed class MLDsaAlgorithm { /// /// Gets the underlying string representation of the algorithm name. @@ -19,18 +19,69 @@ internal sealed class MLDsaAlgorithm /// public string Name { get; } + /// + /// Gets the size, in bytes, of the ML-DSA secret key for the current ML-DSA algorithm. + /// + /// + /// The size, in bytes, of the ML-DSA secret key for the current ML-DSA algorithm. + /// + public int SecretKeySizeInBytes { get; } + + /// + /// Gets the size, in bytes, of the ML-DSA private seed for the current ML-DSA algorithm. + /// + /// + /// The size, in bytes, of the ML-DSA private seed for the current ML-DSA algorithm. + /// + public int PrivateSeedSizeInBytes => 32; + + /// + /// Gets the size of the ML-DSA public key for the current ML-DSA algorithm. + /// + /// + /// The size, in bytes, of the ML-DSA public key for the current ML-DSA algorithm. + /// + public int PublicKeySizeInBytes { get; } + + /// + /// Gets the size, in bytes, of the signature for the current ML-DSA algorithm. + /// + /// + /// The size, in bytes, of the signature for the current ML-DSA algorithm. + /// + public int SignatureSizeInBytes { get; } + + internal string Oid { get; } + /// /// Initializes a new instance of the structure with a custom name. /// /// /// The name of the algorithm. /// - private MLDsaAlgorithm(string name) + /// + /// The size of the secret key in bytes. + /// + /// + /// The size of the public key in bytes. + /// + /// + /// The size of the signature in bytes. + /// + /// + /// The OID of the algorithm. + /// + private MLDsaAlgorithm(string name, int secretKeySizeInBytes, int publicKeySizeInBytes, int signatureSizeInBytes, string oid) { Name = name; + SecretKeySizeInBytes = secretKeySizeInBytes; + PublicKeySizeInBytes = publicKeySizeInBytes; + SignatureSizeInBytes = signatureSizeInBytes; + Oid = oid; } // TODO: Our algorithm names generally match CNG. If they don't in this case, consider changing the values. + // TODO: These values match OpenSSL names, if changing this for CNG, we should make sure to do the right thing for OpenSSL. /// /// Gets an ML-DSA algorithm identifier for the ML-DSA-44 algorithm. @@ -38,7 +89,7 @@ private MLDsaAlgorithm(string name) /// /// An ML-DSA algorithm identifier for the ML-DSA-44 algorithm. /// - public static MLDsaAlgorithm MLDsa44 { get; } = new MLDsaAlgorithm("ML-DSA-44"); + public static MLDsaAlgorithm MLDsa44 { get; } = new MLDsaAlgorithm("ML-DSA-44", 2560, 1312, 2420, Oids.MLDsa44); /// /// Gets an ML-DSA algorithm identifier for the ML-DSA-65 algorithm. @@ -46,7 +97,7 @@ private MLDsaAlgorithm(string name) /// /// An ML-DSA algorithm identifier for the ML-DSA-65 algorithm. /// - public static MLDsaAlgorithm MLDsa65 { get; } = new MLDsaAlgorithm("ML-DSA-65"); + public static MLDsaAlgorithm MLDsa65 { get; } = new MLDsaAlgorithm("ML-DSA-65", 4032, 1952, 3309, Oids.MLDsa65); /// /// Gets an ML-DSA algorithm identifier for the ML-DSA-87 algorithm. @@ -54,6 +105,24 @@ private MLDsaAlgorithm(string name) /// /// An ML-DSA algorithm identifier for the ML-DSA-87 algorithm. /// - public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87"); + public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87", 4896, 2592, 4627, Oids.MLDsa87); + + internal static MLDsaAlgorithm GetMLDsaAlgorithmFromOid(string oid) + { + return oid switch + { + Oids.MLDsa44 => MLDsa44, + Oids.MLDsa65 => MLDsa65, + Oids.MLDsa87 => MLDsa87, + _ => ThrowAlgorithmUnknown(oid), + }; + } + + [DoesNotReturn] + private static MLDsaAlgorithm ThrowAlgorithmUnknown(string algorithmId) + { + throw new CryptographicException( + SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, algorithmId)); + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.NotSupported.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.NotSupported.cs index 4f2aa1767c9515..f1b7db027941bd 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.NotSupported.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.NotSupported.cs @@ -26,19 +26,19 @@ protected override void ExportMLDsaSecretKeyCore(Span destination) => protected override void ExportMLDsaPrivateSeedCore(Span destination) => throw new PlatformNotSupportedException(); - internal static partial MLDsa GenerateKey(MLDsaAlgorithm algorithm) => + internal static partial MLDsa GenerateKeyImpl(MLDsaAlgorithm algorithm) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportPublicKey(MLDsa.ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsa.ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportSecretKey(ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportSeed(ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.Windows.cs index dcc0275116257f..09775651c73c4d 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.Windows.cs @@ -27,19 +27,19 @@ protected override void ExportMLDsaSecretKeyCore(Span destination) => protected override void ExportMLDsaPrivateSeedCore(Span destination) => throw new PlatformNotSupportedException(); - internal static partial MLDsa GenerateKey(MLDsaAlgorithm algorithm) => + internal static partial MLDsa GenerateKeyImpl(MLDsaAlgorithm algorithm) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportPublicKey(MLDsa.ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsa.ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportSecretKey(ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); - internal static partial MLDsa ImportSeed(ParameterSetInfo info, ReadOnlySpan source) => + internal static partial MLDsa ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan source) => throw new PlatformNotSupportedException(); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs index 591fdb91c2a625..ce74bfd9c2fc06 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.cs @@ -17,10 +17,10 @@ private MLDsaImplementation(MLDsaAlgorithm algorithm) internal static partial bool SupportsAny(); - internal static partial MLDsa GenerateKey(MLDsaAlgorithm algorithm); - internal static partial MLDsa ImportPublicKey(ParameterSetInfo info, ReadOnlySpan source); - internal static partial MLDsa ImportPkcs8PrivateKeyValue(ParameterSetInfo info, ReadOnlySpan source); - internal static partial MLDsa ImportSecretKey(ParameterSetInfo info, ReadOnlySpan source); - internal static partial MLDsa ImportSeed(ParameterSetInfo info, ReadOnlySpan source); + internal static partial MLDsa GenerateKeyImpl(MLDsaAlgorithm algorithm); + internal static partial MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan source); + internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan source); + internal static partial MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan source); + internal static partial MLDsa ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan source); } } diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs new file mode 100644 index 00000000000000..10bf4a01ed81c9 --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTests.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.DotNet.XUnitExtensions; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Tests +{ + public static partial class MLDsaTests + { + [ConditionalTheory(typeof(MLDsa), nameof(MLDsa.IsSupported))] + [MemberData(nameof(AllMLDsaAlgorithms))] + public static void GenerateSignVerifyNoContext(MLDsaAlgorithm algorithm) + { + using MLDsa mldsa = MLDsa.GenerateKey(algorithm); + byte[] data = [ 1, 2, 3, 4, 5 ]; + byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes]; + Assert.Equal(signature.Length, mldsa.SignData(data, signature)); + Assert.True(mldsa.VerifyData(data, signature)); + + data[0] ^= 1; + Assert.False(mldsa.VerifyData(data, signature)); + + data[0] ^= 1; + Assert.True(mldsa.VerifyData(data, signature)); + + signature[0] ^= 1; + Assert.False(mldsa.VerifyData(data, signature)); + } + + [ConditionalTheory(typeof(MLDsa), nameof(MLDsa.IsSupported))] + [MemberData(nameof(AllMLDsaAlgorithms))] + public static void GenerateSignVerifyWithContext(MLDsaAlgorithm algorithm) + { + using MLDsa mldsa = MLDsa.GenerateKey(algorithm); + byte[] context = [ 1, 1, 3, 5, 6 ]; + byte[] data = [ 1, 2, 3, 4, 5 ]; + byte[] signature = new byte[mldsa.Algorithm.SignatureSizeInBytes]; + Assert.Equal(signature.Length, mldsa.SignData(data, signature, context)); + Assert.True(mldsa.VerifyData(data, signature, context)); + Assert.False(mldsa.VerifyData(data, signature)); + + data[0] ^= 1; + Assert.False(mldsa.VerifyData(data, signature, context)); + + data[0] ^= 1; + Assert.True(mldsa.VerifyData(data, signature, context)); + + signature[0] ^= 1; + Assert.False(mldsa.VerifyData(data, signature, context)); + + signature[0] ^= 1; + Assert.True(mldsa.VerifyData(data, signature, context)); + + context[0] ^= 1; + Assert.False(mldsa.VerifyData(data, signature, context)); + } + + [ConditionalTheory(typeof(MLDsa), nameof(MLDsa.IsSupported))] + [MemberData(nameof(AllMLDsaAlgorithms))] + public static void GenerateSignExportPublicVerifyWithPublicOnly(MLDsaAlgorithm algorithm) + { + byte[] publicKey; + byte[] data = [ 1, 2, 3, 4, 5 ]; + byte[] signature; + + using (MLDsa mldsa = MLDsa.GenerateKey(algorithm)) + { + signature = new byte[algorithm.SignatureSizeInBytes]; + Assert.Equal(signature.Length, mldsa.SignData(data, signature)); + Assert.True(mldsa.VerifyData(data, signature)); + + publicKey = new byte[algorithm.PublicKeySizeInBytes]; + Assert.Equal(publicKey.Length, mldsa.ExportMLDsaPublicKey(publicKey)); + } + + using MLDsa mldsaPub = MLDsa.ImportMLDsaPublicKey(algorithm, publicKey); + + Assert.True(mldsaPub.VerifyData(data, signature)); + data[0] ^= 1; + Assert.False(mldsaPub.VerifyData(data, signature)); + } + + [ConditionalTheory(typeof(MLDsa), nameof(MLDsa.IsSupported))] + [MemberData(nameof(AllMLDsaAlgorithms))] + public static void GenerateExportSecretKeySignAndVerify(MLDsaAlgorithm algorithm) + { + byte[] secretKey; + byte[] data = [ 1, 2, 3, 4, 5 ]; + byte[] signature; + + using (MLDsa mldsaTmp = MLDsa.GenerateKey(algorithm)) + { + signature = new byte[algorithm.SignatureSizeInBytes]; + Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature)); + + secretKey = new byte[algorithm.SecretKeySizeInBytes]; + Assert.Equal(secretKey.Length, mldsaTmp.ExportMLDsaSecretKey(secretKey)); + } + + using MLDsa mldsa = MLDsa.ImportMLDsaSecretKey(algorithm, secretKey); + + Assert.True(mldsa.VerifyData(data, signature)); + + ((Span)signature).Fill(0); + Assert.Equal(signature.Length, mldsa.SignData(data, signature)); + + Assert.True(mldsa.VerifyData(data, signature)); + data[0] ^= 1; + Assert.False(mldsa.VerifyData(data, signature)); + } + + [ConditionalTheory(typeof(MLDsa), nameof(MLDsa.IsSupported))] + [MemberData(nameof(AllMLDsaAlgorithms))] + public static void GenerateExportPrivateSeedSignAndVerify(MLDsaAlgorithm algorithm) + { + byte[] privateSeed; + byte[] data = [ 1, 2, 3, 4, 5 ]; + byte[] signature; + + using (MLDsa mldsaTmp = MLDsa.GenerateKey(algorithm)) + { + signature = new byte[algorithm.SignatureSizeInBytes]; + Assert.Equal(signature.Length, mldsaTmp.SignData(data, signature)); + + privateSeed = new byte[algorithm.PrivateSeedSizeInBytes]; + Assert.Equal(privateSeed.Length, mldsaTmp.ExportMLDsaPrivateSeed(privateSeed)); + } + + using MLDsa mldsa = MLDsa.ImportMLDsaPrivateSeed(algorithm, privateSeed); + + Assert.True(mldsa.VerifyData(data, signature)); + + ((Span)signature).Fill(0); + Assert.Equal(signature.Length, mldsa.SignData(data, signature)); + + Assert.True(mldsa.VerifyData(data, signature)); + data[0] ^= 1; + Assert.False(mldsa.VerifyData(data, signature)); + } + + [ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))] + public static void NistImportPublicKeyVerify() + { + MLDsaAlgorithm algorithm = MLDsaAlgorithm.MLDsa44; + string pubKeyHex = "8DDF48136E57F2EF242382612DDAADFE417FFC335337C07DB85FBE2BDB977E6019A10B0F643638C947B14E6B42D6A30EB9C6B33C2E349C1875B0E5C4F3016FEB3569A81F0385920038B7F6E740830B62220FF81976EF3FEC9808D6A90A727E9616E51F6A92F70FC7FB569EA27C4CA198EA10B00000DADEC74A0B4B6277C2691A041BF073003D0EFCAD5CCD7AED8AA77CB079C7C3DC1649001DB19D1CEE1EF1013565E8238CF74164FADD44B67ECA1FE939CA793841E64C62BB47EE2F67282A6D44D36AB8E010ADB64EAC09F8107CB6BB709A92303640E4370C9FB1D3DABF2B19AE07A22EE0799E8B26A4779F2973F3B9470112817C5B972E7CDF15C6F1A9F9A1DBB442372A1BE632D5684A59D9E648C993469A9CBC5C6AAE238EE0595854CB04C1527DD364AC01A5841F152212B247594C458852EF9F9377EA415E979C0D2C8BA6EF67CDF7A9B39D7D4387455E71B6C89BD54114A01947379E6B1008E142F60805CE1F425BF18FA767069F7ECD4FA0064472304014C259AD3E5B1A288F24E58A419B4C746452FB478E802DB6F9E46751A3D5B74AAFC140FD21A82B58E151107517B1F74182339172FE4FCFB6E8E927EF7AF754E8DC2279AA16D8E89409417E09375DC22FEC3D3A34277EC2AE20B0B106218D7AB084B4EB875F12C0FA58AF2443E72A97DF725CC7B469248D0B2B7532D73E224336314E9D2311C4F271C0116F6A8DCD7CA94B11A4C66C3E9AF0DA0BA297B01309A20F3FBD87E125ACAD221AC189149C97FD898E933BCFF959C037299CF5B484C7228C17C96CD6C5E18C59DC4686D41D4E59E805F164F751922A2620A355E40FE6660D979831EE799E8D736D9877330BB32976D194F2D8942628578B41936514C2AD5CB225C42D889E781593A5E4B437A39FA255E1462E29655E8B6B46CC2B1C4B29E1686A9A8156686D88F18E512428E6C7E115E6EA7BAF47A4F42655DCCAD5DF2E3C9769849F34BFC2776313908CCC1B9622C4D73D6B285AB5DE4839FB161CAEE1699E7D53BDF2E6C7DCC514FAC00D809FC31F1F022229E0CFB44FD9A73C8BACFBC5946DEC85BDB264BA1A49C2FFFA033BA32E67451A8D94828C135170F4078F6A0F05483004F85DAE152F5F758C07AE392FF6409E0E8208BB4B9272568F191B7870B44EFB3D38EB6A8B483CBADD02B74D17A69FE22FFEBFF7CD5E97F154562A88E5DF45BFB64AE2B662AF2C67BB6E8EF7490460C03A9D93FC15D734CE68572A142806A7563A91B96875A3CE12A7240503274ECD3870733303103A453237556B5CBFD71AAE2A200B3315D2DC25BD3DC5C27FE583CF986275CF9C1DC55CE891B0D1EEB4C165CCD1FFBE13B04479319B054DC055344411935CE4B72479B9DEC8B17CA58DFB0501963336DDB572C7A5E0830999310255FA5ABE52F347E395D864846E033079F795EC17964C82CDAA80B75C75DD45ED458E2BF097EC3C06CC28CD4DC5FC6D00B740EAF6E2FF5A8A8EB9D2BB167FE7793C3864370E5B5DDE98E475C83E1C81636BC5ADEAD4F9D5CF104574120C4DE99A67A4F1CCF9B5E244CE230F2D83E6505C6A5AF99B3D35889838FCA0A7F76A5556A525DFA58587614BCB5CCF4D96F69D6379D695BC8CFAC9B8E4DCCA32E8749AB3D6965BFB43C41C7B1C33CA8B09DEE23516A5B9621A8A3D064FB2ADB6501AA8F6D7BC1A0076D2AFC6AC22A7BD85A18377ED5DAE99A86AB1EB3720AA35AD4CB857A6C5C074F1CE6B4BCCF289CB9C19DBB4068B7FABA0D61233FB9ABFC8F0ECF570704BB9F4CFA4ADB0E86F13D9603B1F79F073C0E56C78FBBF00F50AE33B13CBC7739AE46BF71BB3CC0DA0AA1224319B64E6C9F1E4E4C4F4B0EB"; + string msgHex = "11DBE386C9D30750CDEF285535A5C2808FFF8F73F402F78FCF73759D061A2E5BBB9530683DB28A077F7BEC30485F03EE803C508352DE444144213954DA87CBB91F71B5389442BCE082E15138443BB008C75025D1D34ECC70CEC4ABFA920CE8D1B4306CF968CA3FA50E24D11F4E1205EF1BCB60C3587FE98B6A1D03D3E8CB045300A04B492BCCD328137D51BDDA378DD19793C59660E972FF7997C53BF352235FE1AB56DCE49C83E162C1ACCA7F63223058F6250B2AE5FF577C30E6AEAD36E9BF48D021FD68E6C8BC9FF07C5F40EE2F5F467372DC116B2F03A8BEC2AC5923D1DF21ECB9AEC369A4CBD336775AE8A8163FDD3FD17F150BAB027FE4660CB19BF3164A6508562D0C9F17F287E7BD6DAEE70ECB73484C391B452942E7BB62819652BAF3E509D0B6E1B2258B0B9A5A34EEE93A46E7A30356E6FB5336BE37E0322A238DE21CAB57E92EF56E779E33356F7BE87AC7ED8DA42435D79E3CF2FEAC5029C3BE410AD51D68B128D38BC8392D853846346B880AF5E96597B839140912018136B0CBB86EB5AF5402D2E37609BCED10B34504002E87DA8F77AFBF85716B830B4A9584263D387D4B359A0FE4856114573DAA7F8C1E4C17BBD609F1EB00E4EE12B668D71E132ABA32D2234C4642B098699BC06A607E3A7422B49751F86B506E81E052951DCAB017A80AFEC7926ECB5227015D3B6F66CF654B86A2CE2BD3C53199B6BA1195D89AC6D72FD12FE385FBC482C224BE9CE9243CF2E76DB7908747793AE24D8E324E154CBFFA2593A3C24DB83E3256724DFF13E644ED60AA2459C0FCC374F1303E3AF7D93571BDB0D0692A5392911151840BEE2B128BC829274ABD7DAAF5FDCD15DF4A5084DA7D3B2B26720D1788F5E2D66BF2C0420610898A7BB3B3ADE9D6427641DB8874E8BC76CC3132231553A4DBFE6A668137740EF168BE3840504A6C5E498761BB63F34C3DEC2A3451F87948C2BB03FD194720CAD8FC3F7CC025A8A001822A9C5E1660DFFB84AEA1024C0B30E57F5EE8F14BEF88E6E5771B42EFD1EB8D2C815D295EC820A4F54809D006B04F18826C921136CD82E053FA263704D4448D7EA8B29B24B351116F1FBECDCFDCE10CAE2017335E6A36DE1C82DCAFDA90C4B416D8ACE7BCFD39999FF07B6DAC9F6230AA01C7C8885A652B906F995D4DF002EB8C6A6C310C8B0D75E9A08EC495C64DB6E029A0DD7B501F830AB973CA73223A2EB51033FA5542594ADE4219A8DE70E403390D80871FB8FCC065C27365D5ECB802486D347ACA87C360B0BF95771184457D1F48B62C24829BEA8BFD8118A2090A74BA2A2C24ED044BF039A509BBA287BEE6DB166FDF60732C7591227E1678FD05BA7A38FECF6B21948F2816A051F72D0C0913F916A8C385ACB9F9984582937407D9F601E8AF91D2E0F1BE33640C2C260DD6A4118A7FD7FCBB63E78FA14C1252C28D23CC780404B11B9E79EDA47FE893499BE458D556012AA66F05EC60AC705ADE63B24D9F75C2F12E7A678BAA7E6BC14F483C12A6F1B675DAC2423AF6332B832A236B104037DB85AD8A498BCBDBEDE9A9FC1F8C309A7D4A73F2857BFB6F7C78053367976715BD6ED56DAD243B29675F3D3EAA72AB31B691BAD225FDA6B0F35AAF84B4333B2B28536FCABEE72C381D98F02B2BEFCA2BFDB966A2C28F692C399354D95C58C1588B692D759EA8EB1D347D628E2AE04447340BDD758BC69456C90D73A90460B57F261B0C7036FF37DA2FF26957E5323AAC3D583A12D8E87497D96474D5831B0F9DD448C0658D82A06E1EB65EFF5D6EBA3DE3F6C513FE8AC8E78099F2D76D55908DF1C7C6A06F1DA3026C399DA1CFF431CC3CE22A13A900659BE9308230840CA8BB572DF48F3BF6200E0A6E146425B11802CA77E31610E7BBE65601978F2BAD71809DC353E7F7FB4320F83BFB64E7BB9C80C0AEDEF68D457EAA15EB8B7E16184906D3FB20C232D8B1A96D715B8E92D0D1E180AEA875B4447BBD8585C0A8A5B19154CA8E35B89D1C2F6923933A09030430D594302FA86C139409B4BDDF6241D43CA87A5E93C2A66512AF561589484D2F7B65E8027DE2590BB35D50DC1EB0CC857EACDB88397E6918D7111289B31F71E5E5D26DACE7C0811830B3DDFCDA7E7625390935FA2D3302E911CFC2FEFA8ABE905A2B5B95D5058FB3A709BB3B677AE89A100D1372B0CB11406743B177732821ECEE4577C31887324C7CE5C63F73692CC34735271C76ADD6D2EE731C07520B996D482FA4943E2FCF38298920869D00B4834A04830D956A1B93D9B6F2E8BF5BD69C6C633F27EC76C7CA42BD869240AF88E15CDC6328A806D08757910B06F912F5B01C603625E1105FAD2C2B1366F27224E4BCECFBB50BF2FDA38920384C24F4F556247905659444CD57916F1BA5A95AA88FDE2FFAEF69192921C6CB6AAF68F01C48D0BDC19A020214B6DB64CE3513C36EF3CFA03D413CC613E5140768A8FBA8BC5FA42F68184E78301B7037B57CFF3E292E7465572450C17739D60F13321E85EE91E0CD743CF1E35927C58E02B400E9F4273A862EE93A9D2D186C59FCA79FF5A973ECB565F46563B4A24F92C17EDAE393D2B6B328C8F8DA4746C2DAA0A685A8B9860CDC9893BBABDA624E26C481E3EFF20C00814F597BB6508449219F40C603C81313A56ECDF09353060CA47E485F9068611A66A94EBBCD2B7AAA88260FB1E706CD6F23114C510D73A9D00B27A730FA0D6F305128CCD0383725F1BFDA643D2411334BAE436C0099B342337A86F3F21D3A85ED6333C31B527CB29011E50450A72457FE19C46B80FAD36414431F4CEBC737D6C42B0B617B104D0F1AC367D3FDC78652C672BB29F3FFB0F8C4AED368629676F7E5E2CCD7FAEBC9BA42B86A30F29813E7AC81E261BF1CB99102054C44CA90C884F45A6B10AF32D4A9D47A46AD90B3FD10F39EF82A1A6B5151F37B09CE949D358965FBE4F0A80685A5ABFDCC106EAC0D0CD3E1E2FF192D80D84A141735D87FA7A18BB45A9EC0D1F1D69D12589A449C660AB1E24E3C3EC03DBF99A4C29ADC23B7049811ADD4B7BEEE211D21C056ED197D0DAFF4588F8340B23888115CAEF30FF75D6845B4C1A915FE0A71D27CF0F8C5D0F653B6AAD48120494DD256E9B6C860F49912B81CBE10D1C359C8DAAFCD8D848F72C7250EE7A909168503C4926D6FC6E4209F09758738981B629D53DD97A54D1165C4597CAC8193A3874054562B3361ADA572551A9FEA53CB44D2907B4D9157DA5AF0C6CF1C6C5676BAA95FDB04DCEEF218A06FEF7FA7022B9375E903F74E4DE71C583319267A4D1461D4C2A115E7412DD587C99A7973328D0AEC496981B74A7C4A75C3450AC379E23AE862B2350C17DCDF6BBB41CA975DDF9E3DDD9F5C87FDC386F7AE2DC4219990C99431D652D1CBE88CA5F17E7BD174F9A38767A35442766CBFB58300F5A482D1E936133F90016606E132E2D2F4BC780AF7A3D146F382C7DCD3C2D8B9D7AC1B6E78E53BF4DC6C9C75CE5B261C1F492E976E5BAD1012FBB7252AF0AB2D763461074F011C2354E766A5B3E441AAA8F2CD7EEF4EEE39B46A70940990DB43114F591006CF0EECC81EE65C95617A828FC0B50633BD7CD04467D75EEEE4413A43FC072E66D16B5EF8003C6C1E32A18BC85E794BCA1F3F187DC0E1712D07EC048B058F36ED43E1090B802572D5FA9026D90466DBE9CD3FD332F47147DB77DE1E731167AF597A902E3963180AF2720DEF08BDE90C266016F3B4E8B17D721A6C248B18E4E2EA98E3C2108C3BFFC0F4BCE472E362479B693658F451473D3DA0BB7562D9FEAE567FD26A0584755CB80A44FC26EE1E2D8FCFFD89C63B62BC03E84786E8F40A05F0E2D29010F21799F3FD93560E4B268426A3696DDFEF91280FE35D1C1D69594C6153474C7245D1F93CDC79576471A0600564F042406B59372E45D5E2EB54756C55F7C8AFAB2BE917E6A9D93826BF6EE5C5487093D6AF432903F2775B5CB8A316159F17D25666A976F8C6F284955C29A6A1319A8F6A8B0C6CCA1F1554A1E1DED82E6F9550660F165FD7DB1FE21B0E481846193D3A91D185AAEF7DE16DC6805117A90E92DE7570C0DE6C2B60EF35FBEAAC8CDCD5F3F987E6C04D0AC026EE838B38CA54DE06BAD5F41B0C37C27891D2B5B0A4D0F051B91C6C88144CB46A8CCC162EA3F2C2EAAC8F7C7D2D8E0F6610FB0D02754CF304B12E2A8D3F8A96365322AA1C9DD2739ED42AFC97D4A27FE3AAAB13AA83078C36E9B30116919714A927501FAC71B91224A690090183DCB7B245BB255434BD8772CF1BD6AFF61A2EDE2C80748C54C49ECF0047680A7A5C52BF62F831BF7641299BDC74711089126B0E9A835DD59B1AAA767B9B955D5863E7412C5FC16229C9628A5510A6FF3B1A6A95A5C06D619D7F1E245B5A88D8CB7EAB8374AD2BF3A810CF08E5FA54B93F05D0047460DC04E75BEE57526A73F8D14885A9CAE6050C1EDEF681C555BD8BD6708FCE535A20502875697AB949D29E92955DA3862B2ACED4235E94B1047A50D5A7A12509068D2EEB0BD5F4B4DA307C73A7863FA69332789698D4E0880C5BFE6A1E54E9C6464618D50336B9A3C166A904E6A10D03E56F2A91676B501E623122C479B61B84346315B5892415BDC3E001E8F343BA2E2077203AD91E2A5BFBA1F918F47C83C24F9D48A1D02D7A05AA03AF91C64BC05C4A1641FD786A217D04E5E2032ABFC17372C834B3A00445617499A5FA9C2E40DEA424D8B6F0F68A8350E9A5158EEE9F4FFE4BC0072A658123CC31F303EE7267AAFA70F1CA0FBFBE32CF5E1822281292679C936C03D93B4E96EEE775AF2959E1690CCD2821553CE26A4E994278FB700B42E01C5DCFDB737823924706F22A0DCC11D7149333557384F9634876515EE1DEF4DAA0BCF66BB87D9F007D95DA3546B3C5EBA15ADA65E5E29D12C888591279A027213B38DDD3B8B331EFF818371096C2EBA189CAFE080030127D8FAE1EEA6C9719B4A570F831544EB5A062DA96F7602CC0E772C6AA9255CD92346A32216D5394C98E10973D57A95E68335D54D592657CAFB11C717E3626294CE3C7A190BD8212F4386DFB7410DEDC59F1FDD0BBA1B05AD6FAEFA48047CC378984A71C33024F650319F686EC3B779F559DB94A6EB0ADF4FFFBA4E022862BEABE5A36EBAF3379133C6B806996B0557BDB122AAC328A91C06F574FAD168CD184828C3642B652B17F49ECD500701D4C4FA9BB6CF40AAE77F49CEC3D61B3C3FF116CF187E2FFFF364706345BA97F2EADADB80371159E3D75096F3541EC99B8B4A596552F1DCA04531AD00A3180B8E72ADED9D719686995679B0F51E75219065238B51390AA0D8685D26BDAAB05D8855B56885BF3ECF06AB927FF3CA450D6BFF57B100A05133DB33ACDC40AD222BC66D7B4A2BE110F826242BB27CE5D475449DC3EFC686CB039FD159A6B764F3325E0F65C84F899B3FDF2E47CBA3E11534DA543133DF802B3C35CAD3B720F0167DFFDFA3922AAED975C1A36FF1B2F0CE63987A96CDD2E301F1918077190F6CE34F1FFACC7254BDAF71121416E171CCBCBCD3D324043F3668BC51F84FCA2FEB99E894E1BE6FBDA236BC7ED5F44BAF63E9D15A970499557820DE6A7A249B7A2E57165FC6456BAE0753D87C797C6004D3367683B9F87701E9AEDA4D5C77018AB335FB36091ED5DA7D9B431E0682F4427E3AE18BAF4EA0198D1F6E9C1FD9394A61EF0F7DCEDE3FB4B215C25A01F411584AFA2695C16369C2A3BA0438C397E03FF5451227DB4F4C94C0A2FF190153C39158323400192E9F7406EABD0B62F057B12ED3ADD595698A7F0A3FB450E770AD60B577392B491F13931AA78165CE46C47503D689BEB48908815D8D2F3CC1BA1B98407B5237755B87A882625E9DFAE80C296C760B0308FDDC17AEAE76D0C7A60579457C4DCA6030FB1ED982AC8090A73FD46DE6D2C72361149E35B268606A613B3EE2FF452ABC6A004F4D3E641D8C11CA171EE141F2D4E8689AA70A247080384D4D9038A26ACE5EC5AFD9B7BF9BFBBBA86A32F8A8B7BECB67956A03797209D9F6FCCA5FD9247C0D0FC7A8F0BE3C8DC29E207C3E5A773507AD696D2E67CE166EA097E43D5E99DC14988D6FDDCF5C34C4C3C1D19FD0BA71EB3A28F9A549148A7FA5685CF30141947FB05CE8CC626F30971EA632F23DBBC70D9AF58EF3098C717149D24CFFE3537B06C41F4BA7B80CD0B91BF331CA95D441E26840567C48588EFC04A0C933285C8A5070D8C68FAE2F4DE161D75B888876221305655C7DB3EBDF9A9EE9D6F5EE4280778BCA30B7F75757B4CE0A6827E31DE5DBA6B785B8C4161AB180CDD6F5E40BFBD9FF4EA67E51C8DA8B1E1093CE9D605875DFBB01EA1C8FB622389270C1E265C551D905884B93C586A89192388A484344018FC95BB95DC4DF778A3A33E8617C2D56AADC11E171D83F3B645FAEEC07406C6C7AE24E0FCC702DFAE6FA13C300FB08408E3FCAA4BFBEF3CDD918297BCFC671AE0D34D379CCAB867570D1A065A618538DDB7EB278EE69A14E5C0ABB42E5AECC875FC9EB6488E3BF30A8B91E510D5FC8946B0F2A8C5DD167E8995C5D8BEC3F2E9F32ADB57EACCE9CF5FB004A46523AAAB986A782043DFF351538882315D9BA30F30F709BA8095F3BFD13688B82E69070CC7237362D322E0FCE55BECDE8CBB05E868330E6C3071CE0CCC4C4324DCD8AD40670EEC63840C433F64392C4EA1C00B155A6591DCAA6E1AB487F86F1761443612AA8E9F97D0604DF910B9DB4B2555043C63A56913EB57CF06A6BDC4D10653F83F8552A3A6FC6FBAC3787C337974C1714BC9756A5984BC644E359B068A24C6754432500C96A1CEB1CEFEEDCF4FA504A2AC47E8977C972A9091E94474AE3DB9B148CCD97D166EB83461F10283144D4E93A94A4443D50E411DAF677A4FD52FA3D182011D9D38AEECF18BBDA5BEB5DAF70C391AA3510E3BB26569122B035F44F206B0B85E0C5987AD4D9C527F8873AB30DB6AD994C7365389E00D7625E6FC88430A45196AE6B662E5CDE1DAA5981CC0DB8FF9288BA34F381F1A9F146E3F558F2841EB9C484F3B2904F67CE3FF566D28F57CB67A7524CA2FA78E68A510BA5862B1E13013C1113C910A6E4C55D2F6C688BFAFCEC7A106705855ACAD85E14598504C4FDAC6DE2DD8C3435781E67C1FDC541449EBA80AA5B18BA70E4199D580E98B54C974AE4BDBD1AA58D86C5392186796468305CAD1ED19C1FD25A1B0D8309CAFAB14FEE3B523D81A14BC962466AFB148BE1ACA712F95A052AC2AD123F5E667EEFDD203446025B011F435607140C6EBCF499696401F9982AE585F096634CC50742DD6EA517FFBDF555AE90BB243F9B1B69005F0455A95C4E723823F407B0BFAFFE7A36BF17C7BD539DBF1D5E480A630989F84712ECC371898C866CD5434BB462FCD8B587491513A0134BC7D732E8D4EF1D674FBC13AC88B3F17FC43B77D0AF659640371CF97070C9B46ACF7577730950B1E4A5E2B14AA333F5BEF7B9EEC81AD2B14F5A3C4986C3B359F106B6E07E62F929B2F382A350B4E07C70A3BD23CABABB21AFE3EA6C3313B5675943663B1FA4996C2B74A9733C72A539C66F5D4C3B1C9D5731D69507DF14AE5C76BF25F5EEA6DBCDEFE8305811FD0834FBB980D1602C268F4944E1FB7279545BCCBB7034B790E34730CD68519BD69C13D2078123607B17E6F3DD86D1FE1C8B5849CC2EAE10DA3C7CECF14C88132A5CD016D0180DD19E27FB69811EF40DC0388D458B7BE8A6A2E3FA1552B19866717802C194A226A9D0D3C5B4D550F4C451DF9E89B2F98B2D2E64B8F6B1EEA553ECD0BA85D345DD6EC2304F06A9171B6BEAABF3F81585373BFDE6177210E8DA2FEA6EDBE0CCEF7045C7B41EFA35790335E6C9EF9C0E8FC2C8B18765539BA522D12A848FBC5CF225CA37F287E84DF8B35349A32733C9F4A46A2B38D2F2A9B8485FAC6CE2F8DC4CE90E0DA990A2F9EDF1947A1F1BD404DF3E1D7D5C0935F03363C8A571D64F9D2D6255872C12F81211C6247ED3261D80EDC182AC70163EA2602A10A1A2B4B0126067902C9CC1C651EA8D939AABA61A54057B5A21F8AFF4CD9EF258E274D2AFA550D014ED7A22FC47F37B5C74E3DD94C91C9584426A00E285551A5F9919C9DD37027768407F26E999BDB432DB3559D7EE8B62AC4219A21CC910841453BCBA0C265681CEBBBC202BC8D79DE476C9F319796E1C4315E023C8C12324609AEFC127A23B0CDE4EC335FADE789C75D2734FED812F0414E45C472324D091F1B70EA4555D7057C6B784387D82F0AF5D1358995314DB7F3F451E9809F1B4528740C8FB8DD5BFCE7BD654C8F05E49338C5648E78767D2BAA28C7A051BEC3E1B807496C6B67D05A5FADFF135DF8DFBB95A3A84381D34C72E178F68837BAA9E6D0C3456EC17A36E009E562C5E85EA4A1B0FF5BFBA593A4FD760AE6A247D940C172E772029990BAF09E4DD726D0C37DC81A3E61777C1CCE42C5DEFF7B6AD52DE00C32886526BFDF1277D81773D669DB7586517774B94CF4FA4D98C179A232C8B6C0AD90AC39031992B31BC471171BDB8657A1AD338BED8E9CDFC1694051922D3B492F8FB2F033173484036248E8D4BCB6D8130BD06D12E5506080E050389D951663AC45D3D2B49963721B45713EEE1DBF12A1DDE7A891E88E0262668A97894FC974A1360593CF58B6FAB46155A0E8B2C60B77DA88670AB79AB2D9A22A6A9417D370E65AF0B42837ED2A45F15F2B9A67A58CD11FAF5DC0AFCF91EB2ECABC82FBEC148CF0088E3E972A26384917D94885E0A35FAC66812C5F60EEE82F4C525D25735CCBA8B07B0B053937A0DBBC219EB7C1378028E2F558B7BC3C6B149378CFCA01F56AB5900ABC61CC41A1BF38E99CCC037BA71B1C5F1ADD125CB26DDCD846DBF6D8C459EDA13368529D88FDD36A136FA948ECD953C5A3C64E5857C4720FFE543E752A4B584514EF55FBE0BF2569366920257CDB2D855020241D9F01918963335DAE6609D433952523195FA3E6C7E3ED5AE84DD6425C03A47EFE7FEE6B3709B59EC787095690526EFCBE37302ACE1FA40B0A399A3E02FBE4B7BC9927A192E45B498972066BEEF893838CD93D41BA61172621805092533E10CEF3F74E23D6FAD6FC78EB8BE646C68C5BCCED7D13C9D8AF0BBDFE9C69E1D43515584D317B535F7109ED07196A6CD425BDE931668854691B2572A1834703C4039255F8BA6B83768C3376D17DE4FD22AD40469F2DE4668A0E12B8B5F1E093E7DA6AEC178C19156B5D1716CD12627CD4DB6482162FDE2EEFA9E112D0D7E08DA3008834F9B9D8862F57757D5BD4106C20E38F967E8393700F63A5F013496DE1DB72EBDD7E835F6471E306AA9FED92959AF45C3DB8BC008EDE07B7581D4A693502156BEE69AA607C2048527F127177C6BCC33A7C7E6FF646605587DF0080ACC462191C17B34E58D571DAADAE0261E465E7B7D8CA0FCC4E1E2C1F3CC27FD257CDF755E0F7D75F7E948D4A48CD8E23659D1707B2584DA3194210F37632E3E51FBAA83BD268FEFB6C990F266D280E5E12A35120A89E8D0009C9BDDEF244F98EBBFB72572CA1C7B7BEBFA53F58566DD72102386FC09FE19111D4DC991FBCBB12D9687DDBFF2F614B1312BF89309ED4E563DD9BAD56D2F638D672868DECDAC97D48D5DE2D3CD1B4BCB08F42B6D7B7049862C874E93A03E7B895B9ACB4EC11DA55C99641343737AED8C6D6B13E8A8BF18326739FC630921489091664EAA97B9361CECD42C93B2A0B018FA47C4F8DF1CEF9A4562F7F73A8230B607C5E43393E7D43253E30588595D955F2126F539E740668599D2826BEB8A0FE26C85E1580FE14FFF81BC4935A2897CC7CED3713A749D4BE91DF424D3D755EE9D28A811D6256432A26169C733397479CEB669B1DA27289A7E5C16BC4293A15958EF72BA2C48233180BDA8630F0B980FCA36B88530AEDE1FE2C45A0956E4E04AC468D2CB494EB0773B9A60A8371B5EC8557DA132EE75C22BBACC08DCBF6ECBF155DF7E6DD3FD8D6B6996FAF2CA88DBEBA1196822D6462A42C6A6C8C6BA2A0E368D3B2113070FD1CD90777908B898C27382C2F6CD95592C86E2756DB5BB2DE2E29FB38B9D0C6EBEA7D70D7C02DD1CC8345C83F4AD0CADEA18C83D71B5A7B8CB1C7A462580CD2F078760037B8F61F4B4643B057E863A6D38C53458BC03E62EAB528CEDF79170F349CB0A2F1A35632E61844157D4431B47FA71D7967406FA11A68A38EEBDD37905CC1F5889DE24DF105BB85FB6259CA8D4313AB170973D029244E853E945CEE6C13A66F48D274C82ABE27ED386A93EFA0A638EBD9494B36CB09C8FC650D0B18F3F4EF8F51F971C62EEDF4623F3FD9F221B4934AAC50E25BA2F768B999A3B51F21662D125CB5A333F1E53BBCA5EE2D33C261A01B2D2D7E20064C44F52C62AB69A99A516D739B54C1CFC1772B70E9A7E7EC951B440D5FB972D3A0AEB07773F46910ABA54CD61AC9DDA6AD7BE7A052C0B4FDBB6821CBBC154FE627873C79FA6F4BA8177D319EA5A354B7E60022AFA09A5ADC27ABE6BE9D808EDC43F2796F80C4A7CF2A883AAD9CDCA8A226BF4C4AF193D9A1F6BC3EA23E7531522ACE7A4D457B76334B1442793B5198654D2533CED3B9AABE40BEEBF6F7F3E46C0276FE3542F6C75A8D1F039C52195FE1B2359523"; + string contextHex = "7ED22840C607F95D48271A8A8A0BBB4451736A4035B4E247BBB7196EC1542D069D9CF7941C390A42136F133E6064D81942C6E236EE09D575664479D1490DCD5C657BEE6324FD0A33A9584A0A2A498B3589637B8EB193818BFF1C91266DB39B5D7A3E87EC99617CBE5593C97095D592F86BE784E46A479BED6788C4A1BB5FC773C8E0DFBC41F71C0D48"; + string sigHex = "C738F5C87F6824F734070E2B94595DE1E2D03073083C94BDD6B91904D8149045CEF1035C6EF453F7AA03C416998C8C0AF810E983F57A90E1E73930E8EDF99C765A3FE04A407B6039424B986AB7625C246C0C610D5D561AD69505791453948B5C7EA28F06CF60929683C91F53BDE37575A0D3BF3DE514112B878FAD8AF1DB1E240D13BBA7C9E3F1D39C55F5EA7101EEB5AC71CF2741AAE9C72A8C572210311F51D866E8B316A4E6237D0D44DB82E2D102B845827880813EB2FDCBDF46655A5108FE6A208487E9F27A36187459E09C40A2A410778E6E0B0431ED52187E9AB2108F49506BD7C30F6BB88FBF3B713F4F95E1764F757B623A537BB51EE367B86DF5F9A4D1F0F008C237AA8B54B53E425AF16E86258B9405AA2160DEFC256415D5C989F35EB2BD9B011454152EBE675E5FD2B2AA5BF4C452CB3FBFDEA2E7826CDD77C0B103FEBEAAB3E1DA96CD906699A35816E72C21D7344CBD258C0B5D8ACA3CAE1B9827F18E14F9FC65675EB37162AF2E0BCCDE364050026724D82701197153980C4AFB3BEEE2DD4F6A66C77CF23436DD676A52B38BECF952291115028F290F21881DD5D1D7631F361B4EA9C0BD901EF4EA0F20033D2CDE82C53D6A9DF25826AC9B58D1B8664A211E37D99C17E1160E307BF778C2D16F69669C194AA6C5B50D2D78C4E6A9BE2BC38718D0634F20C5653A402651C077F85624BF8D551B7A9FAAF3B7C72C1EA2C647D784177730C4EFA7228F4C5C2505601E7A8DB208A85A587071F58A568BBF489C17CE515D00A9D4F2F86A2C783EEE5301F2A2C46B2943A48566B0E2657EE45CADC875B21E775B252C7EF713DB58BCBDEFF6EAF09DDEC8D19F4C78E71FA3C93F30E05A3D61479B68D535C648A4DA7F71B2EF9C8CD350030E4886D8F10FD63D827F20BFEBA934A9A4278A2C4104ADFF369F173FDD42207BC4207FE00802286962678A0C52410EA33327F68C5A505A0F929B162F6554246F4320429003E507E9BEC91428BDA8CBBF86A0F6F88C7D63DC57537FC883252B6F075B7FD061F9A8CBF47188C10E373672AD73DF2CEAB07D2E8E3243180A67918AAEC7FA2270AB86D646DB752AC05629AC8114E18478DAFE7C69C3E7F62B79DAC9847030C55C34BFA3447EEE752D74C626FD3CE589966C3C4BF236B5C1FA020A212B6CE880B3B7097D014510B051E77BB0C6905081CD7AA66A25BC81CA276501365F3DA9B82F8A3A7734C372A6A7E1D53A3524A020D15E5E3C7B9A456D21DF2EBD8BA67292DBDDF67C36CEA455B9D218E9FFF03667953C9CCBEEA357932395BFA9B8AFF03F043A86A26252599331FBA579E667E64CE5F8576D4BDCB23F7083F1D20F3404098B47FC3FF4EAC06ED09E241DA2AD3631E70E3EC45640A69B8B497456CD3ED4AF6ABDAF8843B56E7AEA432B2DD9BC3681028B5CB795EA2BE9A634957A449A78083BBA8598B93AAF098DA3045247015AED0602DECE3EA357B09041DC55A6CBB911C523E7065C369EB8AFEE37EF6998D044B44D1CB599335F1DF86727F78CD3F4D2F29BD8A08BCEF7755EE56B3B1A33831BF46D472EA88B437613A00B2A1D6476CAED7645AFE1E245479AA7A890B1B054F6CDD29865BDC68997DF697EB32F027EDE56B2FEBCCF78040CA861F08C6C346FACFAA7AAD538CF2CB0AF04EA2C475B329DCE4513CEB522750A56ADBD90B92A4BFFABA1FD10E0F1593ECA41C6116AD206F4DDE3B4CE468210F67BEE637B06E16355E0CDF31B1B0C17D6AA999BBC5BD7A855507C6E722D0291AA02EFD5D9E86E864E1531984278E0E602DBE6ACC42FDABAAE253F498D0CBDF814ADF1F309B933B2C19E0620683A11089F103AE881B353C0FD6EBE2DA3BBC4CB3CDFF4A0BEB48BB4DE4B386AC17C46381D1CF4E34F9E1BADF47C8F63D66093E1D8B810A2BB8BBC247753584BBAB5D90FE5BF3AB23F92154314C3182CB058B84194836DE8A3622719CC2C692D5D7782F13944E78634874115174048E9A9A0CEFCC6DB4E43BEC628392C4A3126DD6DA5829A053969FA53C4226DBF54EE3AE171A034C738D135A9BC93ABDE14C88265B29261643E8AA6AAF5E4B7FE0C94D0D609BC4C83690EFCB1A2CAF13DC865E116FE895AFE1901A4E97EAF2C1CC8B24A1772385111F6B1ED8299629505552EF4FB52FB4672C6A3CF277AA576457230DCB75A94305E79714436097C0637EEECC43308FD663D0D5CBD7E210A1153025EB4C6D4863B35102D6D8719DEDA0E3509F937955909F28E2085047289D72D8728A14E52E0B991BDD3A3817E850933A666E048D4EA98CDAA56112E52B150DED5D69AEB91071B4C18FCDBA2D646D29C700FC792559DC3D5068B8983CC08D8B98F200AE4186DAA473A253153903B1B1D3167DF22137EF9C0C6A7EFC3A4D5F3C70295BA9F91F0BD0A26F2C9DAAC20F5E121556F02187A212BC6FC2AAD9EA94DE5AE00D683C0939446FE8F514671E6957D0BC1D0F298C288CCDDA139957DD45B646B63DD3DF26881A605CDDF6E344E7D208AB3F7AF80E0343B1E90B8EE1FAEAF5955A294A63E20AF13DFF2DD87534994F206BB53CA93FC96A1838EC39C0263928617F0BEB342FD43CBBF8B701E0ADCE92B7D45D8F8F0185DF01C39CFDD4D876049EB0F0E0FB623D5E181D07174B75FA7FECDFDFC67B269AB34E3FF486853239824807937CB03AADA8AF15A9659C8F7968A5A08159AA53466E20CA9B9C9AB7DF21F307EDA3E70C24917D07E4DFC217E8F9D6A3822839BA651153E049B132CD82D19983DBE5D38BC98D33691EBFCDBA6634406254CFDC2FFA734B3879EEE394F727CE91541D402B61DC96AF0EABAB70450377AB445FA2DB9CDF214088487ADDDA7719C38716735A8A3219D8118D28A56F1DE0A8D39B5BCF9048E070DCE1C226FD22109424E209A1D7DEF5FE99C5548CA3387F0AD99C5B1758F658F6EC7DC07B888E67282C02F61A2EEE09DF890B041561DB8439855311002CECBE78E22187BA3281F3CB807313AE0FB0044BB9FB3AB5F67257E0650DD577B9D0091890CD7C6A8EC0500A2A8932399C907D7818877C918689D7022CD7AFC315A4A7BDA269417B441F882328D5272CE00C7144F970A3C53F1CC46644A1A14D5483F33EACBFF0D3FD5567F242819AEB157D496C86404EC25ACD5BCB76BB8D9578F3DF2BA9D8817320E616E3F1F733CFE20B54633D29E63FBE417EC2FA744178C16B495176C81FF7B2BD4CC2497474870B05A196C47382D34159A97FA2E8EBF0801A757EDF36E74279AFD95CF7524BC8CA6206B4508CE8A2F8BE68E987B2A1BA6227ABD1FF3E3A870B80621223F45788E909DB7BDDEE7EAFA1A202E373A50699FA7BDBFCDE7EBF3081A2127294142474C4E5B7D8390B8C1D4D5DDDE1D292D4748777C888CA1ADB4D0E3EF0000000000000000000000000000000F1E3241"; + + byte[] pubKey = pubKeyHex.HexToByteArray(); + byte[] msg = msgHex.HexToByteArray(); + byte[] sig = sigHex.HexToByteArray(); + byte[] context = contextHex.HexToByteArray(); + using MLDsa mldsa = MLDsa.ImportMLDsaPublicKey(algorithm, pubKey); + Assert.True(mldsa.VerifyData(msg, sig, context)); + } + + public static IEnumerable AllMLDsaAlgorithms() + { + yield return new object[] { MLDsaAlgorithm.MLDsa44 }; + yield return new object[] { MLDsaAlgorithm.MLDsa65 }; + yield return new object[] { MLDsaAlgorithm.MLDsa87 }; + } + } +} diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 3a6c1b60b841fa..31e8697c1b46f4 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -1853,6 +1853,62 @@ internal MLKemAlgorithm() { } public static bool operator ==(System.Security.Cryptography.MLKemAlgorithm? left, System.Security.Cryptography.MLKemAlgorithm? right) { throw null; } public static bool operator !=(System.Security.Cryptography.MLKemAlgorithm? left, System.Security.Cryptography.MLKemAlgorithm? right) { throw null; } } + [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006")] + public abstract partial class MLDsa : System.IDisposable + { + protected MLDsa(System.Security.Cryptography.MLDsaAlgorithm algorithm) { } + public static bool IsSupported { get { throw null; } } + public System.Security.Cryptography.MLDsaAlgorithm Algorithm { get { throw null; } } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + public byte[] ExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } + public byte[] ExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } + public string ExportEncryptedPkcs8PrivateKeyPem(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } + public string ExportEncryptedPkcs8PrivateKeyPem(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters) { throw null; } + public int ExportMLDsaPrivateSeed(System.Span destination) { throw null; } + protected abstract void ExportMLDsaPrivateSeedCore(System.Span destination); + public int ExportMLDsaPublicKey(System.Span destination) { throw null; } + protected abstract void ExportMLDsaPublicKeyCore(System.Span destination); + public int ExportMLDsaSecretKey(System.Span destination) { throw null; } + protected abstract void ExportMLDsaSecretKeyCore(System.Span destination); + public byte[] ExportPkcs8PrivateKey() { throw null; } + public string ExportPkcs8PrivateKeyPem() { throw null; } + public byte[] ExportSubjectPublicKeyInfo() { throw null; } + public string ExportSubjectPublicKeyInfoPem() { throw null; } + public static System.Security.Cryptography.MLDsa GenerateKey(System.Security.Cryptography.MLDsaAlgorithm algorithm) { throw null; } + public static System.Security.Cryptography.MLDsa ImportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportFromEncryptedPem(System.ReadOnlySpan source, System.ReadOnlySpan passwordBytes) { throw null; } + public static System.Security.Cryptography.MLDsa ImportFromEncryptedPem(System.ReadOnlySpan source, System.ReadOnlySpan password) { throw null; } + public static System.Security.Cryptography.MLDsa ImportFromPem(System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportMLDsaPrivateSeed(System.Security.Cryptography.MLDsaAlgorithm algorithm, System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportMLDsaPublicKey(System.Security.Cryptography.MLDsaAlgorithm algorithm, System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportMLDsaSecretKey(System.Security.Cryptography.MLDsaAlgorithm algorithm, System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportPkcs8PrivateKey(System.ReadOnlySpan source) { throw null; } + public static System.Security.Cryptography.MLDsa ImportSubjectPublicKeyInfo(System.ReadOnlySpan source) { throw null; } + public int SignData(System.ReadOnlySpan data, System.Span destination, System.ReadOnlySpan context = default(System.ReadOnlySpan)) { throw null; } + protected abstract void SignDataCore(System.ReadOnlySpan data, System.ReadOnlySpan context, System.Span destination); + protected void ThrowIfDisposed() { } + public bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan passwordBytes, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } + public bool TryExportEncryptedPkcs8PrivateKey(System.ReadOnlySpan password, System.Security.Cryptography.PbeParameters pbeParameters, System.Span destination, out int bytesWritten) { throw null; } + public bool TryExportPkcs8PrivateKey(System.Span destination, out int bytesWritten) { throw null; } + public bool TryExportSubjectPublicKeyInfo(System.Span destination, out int bytesWritten) { throw null; } + public bool VerifyData(System.ReadOnlySpan data, System.ReadOnlySpan signature, System.ReadOnlySpan context = default(System.ReadOnlySpan)) { throw null; } + protected abstract bool VerifyDataCore(System.ReadOnlySpan data, System.ReadOnlySpan context, System.ReadOnlySpan signature); + } + [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006")] + public sealed partial class MLDsaAlgorithm + { + internal MLDsaAlgorithm() { } + public static System.Security.Cryptography.MLDsaAlgorithm MLDsa44 { get { throw null; } } + public static System.Security.Cryptography.MLDsaAlgorithm MLDsa65 { get { throw null; } } + public static System.Security.Cryptography.MLDsaAlgorithm MLDsa87 { get { throw null; } } + public string Name { get { throw null; } } + public int PrivateSeedSizeInBytes { get { throw null; } } + public int PublicKeySizeInBytes { get { throw null; } } + public int SecretKeySizeInBytes { get { throw null; } } + public int SignatureSizeInBytes { get { throw null; } } + } public sealed partial class Oid { public Oid() { } 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 8600fbb4bfa780..2dd332bc49178e 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -890,6 +890,10 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EvpPkey.EcDsa.cs" /> + + - + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/MLDsaImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/MLDsaImplementation.OpenSsl.cs new file mode 100644 index 00000000000000..a34603d58fb9b1 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/MLDsaImplementation.OpenSsl.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Security.Cryptography +{ + internal sealed partial class MLDsaImplementation : MLDsa + { + private SafeEvpPKeyHandle _key = null!; + + private MLDsaImplementation(MLDsaAlgorithm algorithm, SafeEvpPKeyHandle key) + : base(algorithm) + { + Debug.Assert(key is not null); + Debug.Assert(!key.IsInvalid); + Debug.Assert(SupportsAny()); + + _key = key; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _key?.Dispose(); + _key = null!; + } + + base.Dispose(disposing); + } + + internal static partial bool SupportsAny() => + Interop.Crypto.EvpPKeyMLDsaAlgs.MLDsa44 != null || + Interop.Crypto.EvpPKeyMLDsaAlgs.MLDsa65 != null || + Interop.Crypto.EvpPKeyMLDsaAlgs.MLDsa87 != null; + + protected override void SignDataCore(ReadOnlySpan data, ReadOnlySpan context, Span destination) => + Interop.Crypto.MLDsaSignPure(_key, data, context, destination); + + protected override bool VerifyDataCore(ReadOnlySpan data, ReadOnlySpan context, ReadOnlySpan signature) => + Interop.Crypto.MLDsaVerifyPure(_key, data, context, signature); + + protected override void ExportMLDsaPublicKeyCore(Span destination) => + Interop.Crypto.MLDsaExportPublicKey(_key, destination); + + protected override void ExportMLDsaSecretKeyCore(Span destination) => + Interop.Crypto.MLDsaExportSecretKey(_key, destination); + + protected override void ExportMLDsaPrivateSeedCore(Span destination) => + Interop.Crypto.MLDsaExportSeed(_key, destination); + + internal static partial MLDsa GenerateKeyImpl(MLDsaAlgorithm algorithm) + { + SafeEvpPKeyHandle key = Interop.Crypto.MLDsaGenerateKey(algorithm.Name, ReadOnlySpan.Empty); + return new MLDsaImplementation(algorithm, key); + } + + internal static partial MLDsa ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) + { + Debug.Assert(source.Length == algorithm.PublicKeySizeInBytes, $"Public key was expected to be {algorithm.PublicKeySizeInBytes} bytes, but was {source.Length} bytes."); + SafeEvpPKeyHandle key = Interop.Crypto.EvpPKeyFromData(algorithm.Name, source, privateKey: false); + return new MLDsaImplementation(algorithm, key); + } + + internal static partial MLDsa ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan source) => + throw new PlatformNotSupportedException(); + + internal static partial MLDsa ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan source) + { + Debug.Assert(source.Length == algorithm.SecretKeySizeInBytes, $"Secret key was expected to be {algorithm.SecretKeySizeInBytes} bytes, but was {source.Length} bytes."); + SafeEvpPKeyHandle key = Interop.Crypto.EvpPKeyFromData(algorithm.Name, source, privateKey: true); + return new MLDsaImplementation(algorithm, key); + } + + internal static partial MLDsa ImportSeed(MLDsaAlgorithm algorithm, ReadOnlySpan source) + { + Debug.Assert(source.Length == algorithm.PrivateSeedSizeInBytes, $"Seed was expected to be {algorithm.PrivateSeedSizeInBytes} bytes, but was {source.Length} bytes."); + SafeEvpPKeyHandle key = Interop.Crypto.MLDsaGenerateKey(algorithm.Name, source); + return new MLDsaImplementation(algorithm, key); + } + } +} diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index 9ec984c1da9b33..fb18ddd05dd666 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -279,6 +279,8 @@ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaTestsBase.cs" /> + int main(void) { ENGINE_init(NULL); return 1; }" diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 3045c21c4cd959..8ad8b66d8187c5 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -24,6 +24,7 @@ #include "pal_evp_pkey_ecdsa.h" #include "pal_evp_pkey_eckey.h" #include "pal_evp_pkey_rsa.h" +#include "pal_evp_pkey_ml_dsa.h" #include "pal_hmac.h" #include "pal_ocsp.h" #include "pal_pkcs7.h" @@ -242,10 +243,17 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_HmacOneShot) DllImportEntry(CryptoNative_HmacReset) DllImportEntry(CryptoNative_HmacUpdate) + DllImportEntry(CryptoNative_IsSignatureAlgorithmAvailable) DllImportEntry(CryptoNative_LoadKeyFromProvider) DllImportEntry(CryptoNative_LoadPrivateKeyFromEngine) DllImportEntry(CryptoNative_LoadPublicKeyFromEngine) DllImportEntry(CryptoNative_LookupFriendlyNameByOid) + DllImportEntry(CryptoNative_MLDsaExportSecretKey) + DllImportEntry(CryptoNative_MLDsaExportSeed) + DllImportEntry(CryptoNative_MLDsaExportPublicKey) + DllImportEntry(CryptoNative_MLDsaGenerateKey) + DllImportEntry(CryptoNative_MLDsaSignPure) + DllImportEntry(CryptoNative_MLDsaVerifyPure) DllImportEntry(CryptoNative_NewX509Stack) DllImportEntry(CryptoNative_ObjNid2Obj) DllImportEntry(CryptoNative_ObjObj2Txt) diff --git a/src/native/libs/System.Security.Cryptography.Native/openssl.c b/src/native/libs/System.Security.Cryptography.Native/openssl.c index 26a850c520224a..6beb1a8a11ab93 100644 --- a/src/native/libs/System.Security.Cryptography.Native/openssl.c +++ b/src/native/libs/System.Security.Cryptography.Native/openssl.c @@ -1288,6 +1288,31 @@ void CryptoNative_RegisterLegacyAlgorithms(void) #endif } +int32_t CryptoNative_IsSignatureAlgorithmAvailable(const char* algorithm) +{ + int32_t ret = 0; + +#if defined(NEED_OPENSSL_3_0) && defined(HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT) + if (!API_EXISTS(EVP_PKEY_sign_message_init) || + !API_EXISTS(EVP_PKEY_verify_message_init)) + { + return 0; + } + + EVP_SIGNATURE* sigAlg = NULL; + + sigAlg = EVP_SIGNATURE_fetch(NULL, algorithm, NULL); + if (sigAlg) + { + ret = 1; + EVP_SIGNATURE_free(sigAlg); + } +#endif + + (void)algorithm; + return ret; +} + #ifdef NEED_OPENSSL_1_0 // Lock used to make sure EnsureopenSslInitialized itself is thread safe static pthread_mutex_t g_initLock = PTHREAD_MUTEX_INITIALIZER; diff --git a/src/native/libs/System.Security.Cryptography.Native/openssl.h b/src/native/libs/System.Security.Cryptography.Native/openssl.h index 8caa99caf194dd..244bc6c4d83b47 100644 --- a/src/native/libs/System.Security.Cryptography.Native/openssl.h +++ b/src/native/libs/System.Security.Cryptography.Native/openssl.h @@ -63,6 +63,8 @@ PALEXPORT int32_t CryptoNative_PushX509StackField(STACK_OF(X509) * stack, X509* PALEXPORT int32_t CryptoNative_GetRandomBytes(uint8_t* buf, int32_t num); +PALEXPORT int32_t CryptoNative_IsSignatureAlgorithmAvailable(const char* algorithm); + PALEXPORT int32_t CryptoNative_LookupFriendlyNameByOid(const char* oidValue, const char** friendlyName); PALEXPORT int32_t CryptoNative_EnsureOpenSslInitialized(void); diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index e1d80337948dc1..38ac8c5c9632fa 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -156,6 +156,14 @@ c_static_assert(EVP_PKEY_KEYPAIR == 135); c_static_assert(EVP_PKEY_PUBLIC_KEY == 134); #endif +#ifndef OSSL_PKEY_PARAM_ML_DSA_SEED +#define OSSL_PKEY_PARAM_ML_DSA_SEED "seed" +#endif + +#ifndef OSSL_SIGNATURE_PARAM_CONTEXT_STRING +#define OSSL_SIGNATURE_PARAM_CONTEXT_STRING "context-string" +#endif + #if defined FEATURE_DISTRO_AGNOSTIC_SSL || OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_3_0_RTM #include "apibridge_30_rev.h" #endif @@ -246,6 +254,16 @@ int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, unsigned char *md, size_t len); int EVP_DigestSqueeze(EVP_MD_CTX *ctx, unsigned char *out, size_t outlen); #endif +#if !HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT +#undef HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT +#define HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT 1 +EVP_SIGNATURE *EVP_SIGNATURE_fetch(OSSL_LIB_CTX *ctx, const char *algorithm, const char *properties); +void EVP_SIGNATURE_free(EVP_SIGNATURE *signature); +int EVP_PKEY_sign_message_init(EVP_PKEY_CTX *ctx, EVP_SIGNATURE *algo, const OSSL_PARAM params[]); +int EVP_PKEY_verify_message_init(EVP_PKEY_CTX *ctx, EVP_SIGNATURE *algo, const OSSL_PARAM params[]); +const char *EVP_PKEY_get0_type_name(const EVP_PKEY *key); +#endif + #if !HAVE_OPENSSL_ENGINE #undef HAVE_OPENSSL_ENGINE #define HAVE_OPENSSL_ENGINE 1 @@ -526,6 +544,7 @@ extern bool g_libSslUses32BitTime; RENAMED_FUNCTION(EVP_PKEY_get_base_id, EVP_PKEY_base_id) \ RENAMED_FUNCTION(EVP_PKEY_get_bits, EVP_PKEY_bits) \ FALLBACK_FUNCTION(EVP_PKEY_get0_RSA) \ + LIGHTUP_FUNCTION(EVP_PKEY_get0_type_name) \ REQUIRED_FUNCTION(EVP_PKEY_get1_DSA) \ REQUIRED_FUNCTION(EVP_PKEY_get1_EC_KEY) \ REQUIRED_FUNCTION(EVP_PKEY_get1_RSA) \ @@ -538,9 +557,11 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_PKEY_set1_RSA) \ REQUIRED_FUNCTION(EVP_PKEY_sign) \ REQUIRED_FUNCTION(EVP_PKEY_sign_init) \ + LIGHTUP_FUNCTION(EVP_PKEY_sign_message_init) \ FALLBACK_FUNCTION(EVP_PKEY_up_ref) \ REQUIRED_FUNCTION(EVP_PKEY_verify) \ REQUIRED_FUNCTION(EVP_PKEY_verify_init) \ + LIGHTUP_FUNCTION(EVP_PKEY_verify_message_init) \ LIGHTUP_FUNCTION(EVP_PKEY_get_bn_param) \ LIGHTUP_FUNCTION(EVP_PKEY_get_utf8_string_param) \ LIGHTUP_FUNCTION(EVP_PKEY_get_octet_string_param) \ @@ -555,6 +576,8 @@ extern bool g_libSslUses32BitTime; LIGHTUP_FUNCTION(EVP_sha3_512) \ LIGHTUP_FUNCTION(EVP_shake128) \ LIGHTUP_FUNCTION(EVP_shake256) \ + LIGHTUP_FUNCTION(EVP_SIGNATURE_fetch) \ + LIGHTUP_FUNCTION(EVP_SIGNATURE_free) \ REQUIRED_FUNCTION(GENERAL_NAMES_free) \ REQUIRED_FUNCTION(HMAC) \ LEGACY_FUNCTION(HMAC_CTX_cleanup) \ @@ -1089,6 +1112,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_get_base_id EVP_PKEY_get_base_id_ptr #define EVP_PKEY_get_bits EVP_PKEY_get_bits_ptr #define EVP_PKEY_get0_RSA EVP_PKEY_get0_RSA_ptr +#define EVP_PKEY_get0_type_name EVP_PKEY_get0_type_name_ptr #define EVP_PKEY_get1_DSA EVP_PKEY_get1_DSA_ptr #define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEY_ptr #define EVP_PKEY_get1_RSA EVP_PKEY_get1_RSA_ptr @@ -1097,14 +1121,18 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_PKEY_new EVP_PKEY_new_ptr #define EVP_PKEY_CTX_new_from_name EVP_PKEY_CTX_new_from_name_ptr #define EVP_PKEY_CTX_new_from_pkey EVP_PKEY_CTX_new_from_pkey_ptr +#define EVP_PKEY_CTX_new_from_name EVP_PKEY_CTX_new_from_name_ptr +#define EVP_PKEY_CTX_set_params EVP_PKEY_CTX_set_params_ptr #define EVP_PKEY_public_check EVP_PKEY_public_check_ptr #define EVP_PKEY_set1_DSA EVP_PKEY_set1_DSA_ptr #define EVP_PKEY_set1_EC_KEY EVP_PKEY_set1_EC_KEY_ptr #define EVP_PKEY_set1_RSA EVP_PKEY_set1_RSA_ptr #define EVP_PKEY_sign_init EVP_PKEY_sign_init_ptr +#define EVP_PKEY_sign_message_init EVP_PKEY_sign_message_init_ptr #define EVP_PKEY_sign EVP_PKEY_sign_ptr #define EVP_PKEY_up_ref EVP_PKEY_up_ref_ptr #define EVP_PKEY_verify_init EVP_PKEY_verify_init_ptr +#define EVP_PKEY_verify_message_init EVP_PKEY_verify_message_init_ptr #define EVP_PKEY_verify EVP_PKEY_verify_ptr #define EVP_PKEY_get_bn_param EVP_PKEY_get_bn_param_ptr #define EVP_PKEY_get_utf8_string_param EVP_PKEY_get_utf8_string_param_ptr @@ -1120,6 +1148,8 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_sha3_512 EVP_sha3_512_ptr #define EVP_shake128 EVP_shake128_ptr #define EVP_shake256 EVP_shake256_ptr +#define EVP_SIGNATURE_fetch EVP_SIGNATURE_fetch_ptr +#define EVP_SIGNATURE_free EVP_SIGNATURE_free_ptr #define GENERAL_NAMES_free GENERAL_NAMES_free_ptr #define HMAC HMAC_ptr #define HMAC_CTX_cleanup HMAC_CTX_cleanup_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in b/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in index 7ef6127a6faa7e..3664c96114b653 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in +++ b/src/native/libs/System.Security.Cryptography.Native/pal_crypto_config.h.in @@ -5,4 +5,5 @@ #cmakedefine01 HAVE_OPENSSL_CHACHA20POLY1305 #cmakedefine01 HAVE_OPENSSL_SHA3 #cmakedefine01 HAVE_OPENSSL_SHA3_SQUEEZE +#cmakedefine01 HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT #cmakedefine01 HAVE_OPENSSL_ENGINE diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_kem.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_kem.c index 909e7a76e80e29..a0f390dc02cbde 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_kem.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_kem.c @@ -3,58 +3,11 @@ #include "openssl.h" #include "pal_evp_kem.h" +#include "pal_evp_pkey.h" #include "pal_utilities.h" #include -static int32_t GetKeyOctetStringParam(const EVP_PKEY* pKey, - const char* name, - uint8_t* destination, - int32_t destinationLength) -{ - assert(pKey); - assert(destination); - assert(name); - -#ifdef NEED_OPENSSL_3_0 - if (API_EXISTS(EVP_PKEY_get_octet_string_param)) - { - ERR_clear_error(); - - size_t destinationLengthT = Int32ToSizeT(destinationLength); - size_t outLength = 0; - - int ret = EVP_PKEY_get_octet_string_param(pKey, name, NULL, 0, &outLength); - - if (ret != 1) - { - return -1; - } - - ret = EVP_PKEY_get_octet_string_param(pKey, name, (unsigned char*)destination, destinationLengthT, &outLength); - - if (ret != 1) - { - return 0; - } - - if (outLength != destinationLengthT) - { - return -2; - } - - return 1; - } -#else - (void)pKey; - (void)name; - (void)destination; - (void)destinationLength; -#endif - - return 0; -} - int32_t CryptoNative_EvpKemAvailable(const char* algorithm) { #ifdef NEED_OPENSSL_3_0 @@ -278,15 +231,15 @@ int32_t CryptoNative_EvpKemDecapsulate(EVP_PKEY* pKey, int32_t CryptoNative_EvpKemExportPrivateSeed(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength) { - return GetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_ML_KEM_SEED, destination, destinationLength); + return EvpPKeyGetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_ML_KEM_SEED, destination, destinationLength); } int32_t CryptoNative_EvpKemExportDecapsulationKey(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength) { - return GetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_PRIV_KEY, destination, destinationLength); + return EvpPKeyGetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_PRIV_KEY, destination, destinationLength); } int32_t CryptoNative_EvpKemExportEncapsulationKey(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength) { - return GetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_PUB_KEY, destination, destinationLength); + return EvpPKeyGetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_PUB_KEY, destination, destinationLength); } diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c index 3b498a433a5d87..25dc5cd42270e7 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c @@ -4,21 +4,11 @@ #include #include "pal_evp_pkey.h" #include "pal_utilities.h" -#include "pal_atomic.h" #ifdef NEED_OPENSSL_3_0 c_static_assert(OSSL_STORE_INFO_PKEY == 4); c_static_assert(OSSL_STORE_INFO_PUBKEY == 3); -struct EvpPKeyExtraHandle_st -{ - atomic_int refCount; - OSSL_LIB_CTX* libCtx; - OSSL_PROVIDER* prov; -}; - -typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; - #pragma clang diagnostic push // There's no way to specify explicit memory ordering for increment/decrement with C atomics. #pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" @@ -837,3 +827,51 @@ EVP_PKEY* CryptoNative_EvpPKeyFromData(const char* algorithmName, uint8_t* key, (void)privateKey; return NULL; } + +int32_t EvpPKeyGetKeyOctetStringParam(const EVP_PKEY* pKey, + const char* name, + uint8_t* destination, + int32_t destinationLength) +{ + assert(pKey); + assert(destination); + assert(name); + +#ifdef NEED_OPENSSL_3_0 + if (API_EXISTS(EVP_PKEY_get_octet_string_param)) + { + ERR_clear_error(); + + size_t destinationLengthT = Int32ToSizeT(destinationLength); + size_t outLength = 0; + + int ret = EVP_PKEY_get_octet_string_param(pKey, name, NULL, 0, &outLength); + + if (ret != 1) + { + return -1; + } + + ret = EVP_PKEY_get_octet_string_param(pKey, name, (unsigned char*)destination, destinationLengthT, &outLength); + + if (ret != 1) + { + return 0; + } + + if (outLength != destinationLengthT) + { + return -2; + } + + return 1; + } +#else + (void)pKey; + (void)name; + (void)destination; + (void)destinationLength; +#endif + + return 0; +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h index 81bb2121b1b8e8..a07f7fb55cd9df 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h @@ -4,6 +4,16 @@ #include "pal_types.h" #include "pal_compiler.h" #include "opensslshim.h" +#include "pal_atomic.h" + +struct EvpPKeyExtraHandle_st +{ + atomic_int refCount; + OSSL_LIB_CTX* libCtx; + OSSL_PROVIDER* prov; +}; + +typedef struct EvpPKeyExtraHandle_st EvpPKeyExtraHandle; /* Shims the EVP_PKEY_new method. @@ -129,3 +139,11 @@ It's a wrapper for EVP_PKEY_CTX_new_from_pkey and EVP_PKEY_CTX_new which handles extraHandle. */ EVP_PKEY_CTX* EvpPKeyCtxCreateFromPKey(EVP_PKEY* pkey, void* extraHandle); + +/* +Internal function to get the octet string parameter from the given EVP_PKEY. +*/ +int32_t EvpPKeyGetKeyOctetStringParam(const EVP_PKEY* pKey, + const char* name, + uint8_t* destination, + int32_t destinationLength); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ml_dsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ml_dsa.c new file mode 100644 index 00000000000000..b2d0eb9cb53213 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ml_dsa.c @@ -0,0 +1,233 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_evp_pkey.h" +#include "pal_evp_pkey_ml_dsa.h" +#include "pal_utilities.h" +#include "openssl.h" +#include + +EVP_PKEY* CryptoNative_MLDsaGenerateKey(const char* keyType, uint8_t* seed, int32_t seedLen) +{ +#if defined(NEED_OPENSSL_3_0) && defined(HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT) + if (!API_EXISTS(EVP_PKEY_sign_message_init) || + !API_EXISTS(EVP_PKEY_verify_message_init)) + { + return NULL; + } + + ERR_clear_error(); + + if (seed && seedLen != 32) + { + return NULL; + } + + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(NULL, keyType, NULL); + EVP_PKEY* pkey = NULL; + + if (!pctx) + { + return NULL; + } + + if (EVP_PKEY_keygen_init(pctx) <= 0) + { + goto done; + } + + if (seed) + { + OSSL_PARAM params[] = + { + OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, (void*)seed, Int32ToSizeT(seedLen)), + OSSL_PARAM_construct_end(), + }; + + if (EVP_PKEY_CTX_set_params(pctx, params) <= 0) + { + goto done; + } + } + + if (EVP_PKEY_keygen(pctx, &pkey) != 1 && pkey != NULL) + { + EVP_PKEY_free(pkey); + pkey = NULL; + } + +done: + if (pctx != NULL) + { + EVP_PKEY_CTX_free(pctx); + } + + return pkey; +#else + (void)keyType; + (void)seed; + (void)seedLen; + return NULL; +#endif +} + +int32_t CryptoNative_MLDsaSignPure(EVP_PKEY *pkey, + void* extraHandle, + uint8_t* msg, int32_t msgLen, + uint8_t* context, int32_t contextLen, + uint8_t* destination, int32_t destinationLen) +{ + assert(pkey); + assert(msg); + assert(msgLen >= 0); + assert(contextLen >= 0); + assert(destination); + assert(destinationLen >= 2420 /* ML-DSA-44 signature size */); + +#if defined(NEED_OPENSSL_3_0) && defined(HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT) + if (!API_EXISTS(EVP_PKEY_sign_message_init) || + !API_EXISTS(EVP_PKEY_verify_message_init)) + { + return -1; + } + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = NULL; + + int ret = -1; + + ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + if (!ctx) + { + goto done; + } + + OSSL_PARAM contextParams[] = + { + OSSL_PARAM_construct_end(), + OSSL_PARAM_construct_end(), + }; + + if (context) + { + contextParams[0] = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, (void*)context, Int32ToSizeT(contextLen)); + } + + if (EVP_PKEY_sign_message_init(ctx, NULL, contextParams) <= 0) + { + goto done; + } + + size_t dstLen = Int32ToSizeT(destinationLen); + if (EVP_PKEY_sign(ctx, destination, &dstLen, msg, Int32ToSizeT(msgLen)) == 1) + { + if (dstLen != Int32ToSizeT(destinationLen)) + { + assert(false); // length mismatch + goto done; + } + + ret = 1; + } + else + { + ret = 0; + } + +done: + if (ctx != NULL) EVP_PKEY_CTX_free(ctx); + return ret; +#else + (void)pkey; + (void)extraHandle; + (void)msg; + (void)msgLen; + (void)context; + (void)contextLen; + (void)destination; + (void)destinationLen; + return -1; +#endif +} + +int32_t CryptoNative_MLDsaVerifyPure(EVP_PKEY *pkey, + void* extraHandle, + uint8_t* msg, int32_t msgLen, + uint8_t* context, int32_t contextLen, + uint8_t* sig, int32_t sigLen) +{ + assert(pkey); + assert(msg); + assert(msgLen >= 0); + assert(sig); + assert(sigLen >= 2420 /* ML-DSA-44 signature size */); + assert(contextLen >= 0); + +#if defined(NEED_OPENSSL_3_0) && defined(HAVE_OPENSSL_EVP_PKEY_SIGN_MESSAGE_INIT) + if (!API_EXISTS(EVP_PKEY_sign_message_init) || + !API_EXISTS(EVP_PKEY_verify_message_init)) + { + return -1; + } + + ERR_clear_error(); + + EVP_PKEY_CTX* ctx = NULL; + + int ret = -1; + + ctx = EvpPKeyCtxCreateFromPKey(pkey, extraHandle); + if (!ctx) + { + goto done; + } + + OSSL_PARAM contextParams[] = + { + OSSL_PARAM_construct_end(), + OSSL_PARAM_construct_end(), + }; + + if (context) + { + contextParams[0] = OSSL_PARAM_construct_octet_string(OSSL_SIGNATURE_PARAM_CONTEXT_STRING, (void*)context, Int32ToSizeT(contextLen)); + } + + if (EVP_PKEY_verify_message_init(ctx, NULL, contextParams) <= 0) + { + goto done; + } + + ret = EVP_PKEY_verify(ctx, sig, Int32ToSizeT(sigLen), msg, Int32ToSizeT(msgLen)) == 1; + +done: + if (ctx != NULL) EVP_PKEY_CTX_free(ctx); + return ret; +#else + (void)pkey; + (void)extraHandle; + (void)msg; + (void)msgLen; + (void)context; + (void)contextLen; + (void)sig; + (void)sigLen; + return -1; +#endif +} + +int32_t CryptoNative_MLDsaExportSecretKey(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength) +{ + return EvpPKeyGetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_PRIV_KEY, destination, destinationLength); +} + +int32_t CryptoNative_MLDsaExportSeed(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength) +{ + return EvpPKeyGetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_ML_DSA_SEED, destination, destinationLength); +} + +int32_t CryptoNative_MLDsaExportPublicKey(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength) +{ + return EvpPKeyGetKeyOctetStringParam(pKey, OSSL_PKEY_PARAM_PUB_KEY, destination, destinationLength); +} diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ml_dsa.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ml_dsa.h new file mode 100644 index 00000000000000..4dd3d4623dac24 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_ml_dsa.h @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "opensslshim.h" +#include "pal_compiler.h" +#include "pal_types.h" + +/* +Generates a new EVP_PKEY with random parameters or if seed is not NULL, uses the seed to generate the key. +The keyType is the type of the key (e.g., "ML-DSA-65"). +*/ +PALEXPORT EVP_PKEY* CryptoNative_MLDsaGenerateKey(const char* keyType, uint8_t* seed, int32_t seedLen); + +/* +Sign a message using the provided ML-DSA key. + +Returns 1 on success, 0 on a mismatched signature, -1 on error. +*/ +PALEXPORT int32_t CryptoNative_MLDsaSignPure(EVP_PKEY *pkey, + void* extraHandle, + uint8_t* msg, int32_t msgLen, + uint8_t* context, int32_t contextLen, + uint8_t* destination, int32_t destinationLen); + +/* +Verify a message using the provided ML-DSA key. + +Returns 1 on a verified signature, 0 on a mismatched signature, -1 on error. +*/ +PALEXPORT int32_t CryptoNative_MLDsaVerifyPure(EVP_PKEY *pkey, + void* extraHandle, + uint8_t* msg, int32_t msgLen, + uint8_t* context, int32_t contextLen, + uint8_t* sig, int32_t sigLen); + +/* +Export the secret key from the given ML-DSA key. +*/ +PALEXPORT int32_t CryptoNative_MLDsaExportSecretKey(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength); + +/* +Export the seed from the given ML-DSA key which can be used to generate secret key. +*/ +PALEXPORT int32_t CryptoNative_MLDsaExportSeed(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength); + +/* +Export the public key from the given ML-DSA key. +*/ +PALEXPORT int32_t CryptoNative_MLDsaExportPublicKey(const EVP_PKEY* pKey, uint8_t* destination, int32_t destinationLength);