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

Conversation

PranavSenthilnathan
Copy link
Member

Adds the Windows implementation of ML-DSA using BCrypt as the underlying crypto provider. The MLDsa factory methods will create this implementation when called on Windows.

Certificates are not supported yet so tests requiring them are disabled on Windows. A PR following this one will add NCrypt and re-enable the tests again.

Contributes to #113502

@PranavSenthilnathan PranavSenthilnathan self-assigned this Jun 4, 2025
@Copilot Copilot AI review requested due to automatic review settings June 4, 2025 05:52
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds support for ML-DSA on Windows by implementing the algorithm over BCrypt, updating tests to disable certificate scenarios until NCrypt is available, and introducing helpers for PQC key blob handling.

  • Updated tests to conditionally skip certificate- and empty-context tests on Windows.
  • Introduced PqcBlobHelpers plus new BCrypt interop (SetProperty, SignHashPure, VerifySignaturePure).
  • Added a full Windows-side MLDsaImplementation using SafeBCryptAlgorithmHandle.

Reviewed Changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/libraries/System.Security.Cryptography/tests/X509Certificates/CertTests.cs Use MLDsaTestHelpers.MLDsaCertificatesAreSupported for certificate tests on Windows
src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs Added canary IsWindows10Version27858OrGreater for PQC support
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/MLDsa/MLDsaTestHelpers.cs New helper flags to disable cert/context tests on Windows
src/libraries/Common/src/System/Security/Cryptography/PqcBlobHelpers.cs New helper class to encode/decode ML-DSA blobs
src/libraries/Common/src/System/Security/Cryptography/MLDsaImplementation.Windows.cs Full Windows BCrypt-based implementation for ML-DSA
src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs Registered the ML-DSA algorithm name
Comments suppressed due to low confidence (1)

src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs:34

  • The algorithm name passed to BCryptOpenAlgorithmProvider should match the native BCRYPT_MLDSA_ALGORITHM constant (likely "MLDSA" without a hyphen). Using "ML-DSA" may cause the provider open call to fail.
public const string MLDsa = "ML-DSA";               // BCRYPT_MLDSA_ALGORITHM

int cbInput,
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.

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)?

ReadOnlySpan<byte> data,
EncodeBlobFunc<TResult> callback)
{
int blobHeaderSize = Unsafe.SizeOf<BCRYPT_PQDSA_KEY_BLOB>();
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be Marshal.SizeOf since we care about the marshal size, not the managed size?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, that seems to only be relevant if we let the marshalling be done automatically via Marshal.PtrToStructure or Marshal.StructureToPtr. For example:

Console.WriteLine(Unsafe.SizeOf<S>());   // 1
Console.WriteLine(Marshal.SizeOf<S>());  // 4

[StructLayout(LayoutKind.Sequential)]
struct S
{
    bool b;
}

That's because Marshal will marshal a C# bool as a native 32 bit unsigned int. But in our code we're doing the marshalling ourselves by using the C# struct layout as the exact native layout (and projecting the BCRYPT_PQDSA_KEY_BLOB on top of raw bytes from the rented buffer).

That being said, I don't think Unsafe.SizeOf<BCRYPT_PQDSA_KEY_BLOB> is also 100% correct. Right now there's no padding, but if the struct was something like:

[StructLayout(LayoutKind.Sequential)]
struct S
{
    long Foo;
    int Bar;
    // Let's say Windows has data starting at offset 12
}

Then sizeof(S) would give 16 (with padding) and we would be off by 4. The right approach is probably calculating the total size without the struct and only using the struct as a way to project data in the backing array (blob->Bar = 42).

Copy link
Member Author

Choose a reason for hiding this comment

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

The padding conversation was resolved in #116291 (comment)

int keyLength = blob.cbKey;
index += Unsafe.SizeOf<BCRYPT_PQDSA_KEY_BLOB>();

parameterSet = MemoryMarshal.Cast<byte, char>(blobBytes.Slice(index, parameterSetLength));
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should use MemoryMarshal here, one because it's not needed, and two I don't think this will work when you try to light it up for Microsoft.Bcl.Cryptography. All of this marshal logic is going to need to work on .NET Framework.

Copy link
Member Author

Choose a reason for hiding this comment

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

Microsoft.Bcl.Cryptography already references System.Memory where MemoryMarshal.Cast<,> is available for netstandard2.0 (and AsRef can be built as foo.Cast<byte,T>()[0]). This feels more natural to me rather than unsafe and pinning (maybe because I don't do much interop).

@PranavSenthilnathan PranavSenthilnathan enabled auto-merge (squash) June 5, 2025 22:58
@PranavSenthilnathan PranavSenthilnathan merged commit aaa3a24 into dotnet:main Jun 5, 2025
86 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants