Skip to content

Enable ML-DSA on Windows #116291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal static class AlgorithmName
public const string ECDsaP521 = "ECDSA_P521"; // BCRYPT_ECDSA_P521_ALGORITHM
public const string RSA = "RSA"; // BCRYPT_RSA_ALGORITHM
public const string MD5 = "MD5"; // BCRYPT_MD5_ALGORITHM
public const string MLDsa = "ML-DSA"; // BCRYPT_MLDSA_ALGORITHM
public const string Sha1 = "SHA1"; // BCRYPT_SHA1_ALGORITHM
public const string Sha256 = "SHA256"; // BCRYPT_SHA256_ALGORITHM
public const string Sha384 = "SHA384"; // BCRYPT_SHA384_ALGORITHM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,24 @@ internal struct BCRYPT_PSS_PADDING_INFO
/// </summary>
internal int cbSalt;
}

[StructLayout(LayoutKind.Sequential)]
internal struct BCRYPT_PQDSA_PADDING_INFO
{
/// <summary>
/// Address of the context buffer.
/// </summary>
internal IntPtr pbCtx;

/// <summary>
/// The size, in bytes, of the context buffer.
/// </summary>
internal int cbCtx;

/// <summary>
/// Null-terminated Unicode string that identifies the pre-hash algorithm used to create the hash.
/// </summary>
internal IntPtr pszPreHashAlgId;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private static unsafe partial NTSTATUS BCryptGenerateKeyPair(

internal static SafeBCryptKeyHandle BCryptGenerateKeyPair(
SafeBCryptAlgorithmHandle hAlgorithm,
int keyLength)
int keyLength = 0)
{
NTSTATUS status = BCryptGenerateKeyPair(
hAlgorithm,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal static class BCryptPropertyStrings
internal const string BCRYPT_HASH_LENGTH = "HashDigestLength";
internal const string BCRYPT_KEY_STRENGTH = "KeyStrength";
internal const string BCRYPT_MESSAGE_BLOCK_LENGTH = "MessageBlockLength";
internal const string BCRYPT_PARAMETER_SET_NAME = "ParameterSetName";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class BCrypt
{
[LibraryImport(Libraries.BCrypt, StringMarshalling = StringMarshalling.Utf16)]
internal static unsafe partial NTSTATUS BCryptSetProperty(
SafeBCryptHandle hObject,
string pszProperty,
void* pbInput,
int cbInput,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cbInput should be a uint.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most other places use int for cb and dwFlags. Is the guidance that if we add new interop signatures we should use uint (when the underlying native API is ULONG)?

int dwFlags);

internal static unsafe void BCryptSetSZProperty(SafeBCryptHandle hObject, string pszProperty, string pszValue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with the helper, I think, but once we're adding it we should use it. So as a followup to this PR we should delete the public static partial NTSTATUS BCryptSetProperty(SafeAlgorithmHandle hObject, string pszProperty, string pbInput, int cbInput, int dwFlags); import in Interop\Windows\BCrypt\Cng.cs, and change all the callers of that to use this.

{
fixed (void* pbInput = pszValue)
{
NTSTATUS status = BCryptSetProperty(
hObject,
pszProperty,
pbInput,
(pszValue.Length + 1) * 2,
0);

if (status != NTSTATUS.STATUS_SUCCESS)
{
throw CreateCryptographicException(status);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Internal.Cryptography;

Expand Down Expand Up @@ -74,5 +75,58 @@ internal static unsafe NTSTATUS BCryptSignHashPss(
BCryptSignVerifyFlags.BCRYPT_PAD_PSS);
}
}

internal static unsafe void BCryptSignHashPure(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> data,
ReadOnlySpan<byte> context,
Span<byte> destination)
{
NTSTATUS status;
int bytesWritten;

fixed (byte* pData = &MemoryMarshal.GetReference(data))
fixed (byte* pDest = &Helpers.GetNonNullPinnableReference(destination))
{
if (context.Length == 0)
{
status = BCryptSignHash(
key,
pPaddingInfo: null,
pData,
data.Length,
pDest,
destination.Length,
out bytesWritten,
default(BCryptSignVerifyFlags));
}
else
{
fixed (byte* pContext = &MemoryMarshal.GetReference(context))
{
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
paddingInfo.pbCtx = (IntPtr)pContext;
paddingInfo.cbCtx = context.Length;

status = BCryptSignHash(
key,
&paddingInfo,
pData,
data.Length,
pDest,
destination.Length,
out bytesWritten,
BCryptSignVerifyFlags.BCRYPT_PAD_PQDSA);
}
}
}

Debug.Assert(bytesWritten == destination.Length);

if (status != Interop.BCrypt.NTSTATUS.STATUS_SUCCESS)
{
throw Interop.BCrypt.CreateCryptographicException(status);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;

using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
Expand All @@ -15,6 +16,7 @@ private enum BCryptSignVerifyFlags : uint
{
BCRYPT_PAD_PKCS1 = 2,
BCRYPT_PAD_PSS = 8,
BCRYPT_PAD_PQDSA = 32,
}

[LibraryImport(Libraries.BCrypt)]
Expand Down Expand Up @@ -84,5 +86,50 @@ internal static unsafe bool BCryptVerifySignaturePss(

return status == NTSTATUS.STATUS_SUCCESS;
}

internal static unsafe bool BCryptVerifySignaturePure(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> data,
ReadOnlySpan<byte> context,
ReadOnlySpan<byte> signature)
{
NTSTATUS status;

fixed (byte* pData = &Helpers.GetNonNullPinnableReference(data))
fixed (byte* pSignature = &MemoryMarshal.GetReference(signature))
{
if (context.Length == 0)
{
status = BCryptVerifySignature(
key,
pPaddingInfo: null,
pData,
data.Length,
pSignature,
signature.Length,
default(BCryptSignVerifyFlags));
}
else
{
fixed (byte* pContext = &MemoryMarshal.GetReference(context))
{
BCRYPT_PQDSA_PADDING_INFO paddingInfo = default;
paddingInfo.pbCtx = (IntPtr)pContext;
paddingInfo.cbCtx = context.Length;

status = BCryptVerifySignature(
key,
&paddingInfo,
pData,
data.Length,
pSignature,
signature.Length,
BCryptSignVerifyFlags.BCRYPT_PAD_PQDSA);
}
}
}

return status == NTSTATUS.STATUS_SUCCESS;
}
}
}
20 changes: 20 additions & 0 deletions src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ internal enum KeyBlobMagicNumber : int
BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC = 0x50444345,
BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC = 0x56444345,

BCRYPT_MLDSA_PUBLIC_MAGIC = 0x4B505344,
BCRYPT_MLDSA_PRIVATE_MAGIC = 0x4B535344,
BCRYPT_MLDSA_PRIVATE_SEED_MAGIC = 0x53535344,

BCRYPT_RSAPUBLIC_MAGIC = 0x31415352,
BCRYPT_RSAPRIVATE_MAGIC = 0x32415352,
BCRYPT_RSAFULLPRIVATE_MAGIC = 0x33415352,
Expand Down Expand Up @@ -133,6 +137,10 @@ internal static class KeyBlobType
internal const string BCRYPT_ECCPRIVATE_BLOB = "ECCPRIVATEBLOB";
internal const string BCRYPT_ECCFULLPUBLIC_BLOB = "ECCFULLPUBLICBLOB";
internal const string BCRYPT_ECCFULLPRIVATE_BLOB = "ECCFULLPRIVATEBLOB";

internal const string BCRYPT_PQDSA_PUBLIC_BLOB = "PQDSAPUBLICBLOB";
internal const string BCRYPT_PQDSA_PRIVATE_BLOB = "PQDSAPRIVATEBLOB";
internal const string BCRYPT_PQDSA_PRIVATE_SEED_BLOB = "PQDSAPRIVATESEEDBLOB";
}

/// <summary>
Expand Down Expand Up @@ -235,6 +243,18 @@ internal struct BCRYPT_ECCFULLKEY_BLOB
// The rest of the buffer contains the domain parameters
}

/// <summary>
/// Used as a header to PQC parameters including the parameters set and key/seed.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct BCRYPT_PQDSA_KEY_BLOB
{
internal KeyBlobMagicNumber Magic;
internal int cbParameterSet; // Byte size of parameterSet[]
internal int cbKey; // Byte size of key[]
// The rest of the buffer contains the data
}

/// <summary>
/// NCrypt or BCrypt buffer descriptors
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ namespace System.Security.Cryptography
{
internal sealed partial class MLDsaImplementation : MLDsa
{
public MLDsaImplementation(MLDsaAlgorithm algorithm)
: base(algorithm)
{
ThrowIfNotSupported();
}

internal static partial bool SupportsAny() => false;

// The instance override methods are unreachable, as the constructor will always throw.
Expand Down Expand Up @@ -35,9 +41,6 @@ internal static partial MLDsaImplementation GenerateKeyImpl(MLDsaAlgorithm algor
internal static partial MLDsaImplementation ImportPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
throw new PlatformNotSupportedException();

internal static partial MLDsaImplementation ImportPkcs8PrivateKeyValue(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
throw new PlatformNotSupportedException();

internal static partial MLDsaImplementation ImportSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
throw new PlatformNotSupportedException();

Expand Down
Loading
Loading