Skip to content

Add stubs for CompositeMLDsa APIs #118520

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 3 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Security.Cryptography
{
public sealed partial class CompositeMLDsaCng : CompositeMLDsa
{
public partial CngKey GetKey() =>
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa)));

/// <inheritdoc/>
protected override int SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination) =>
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa)));

/// <inheritdoc/>
protected override bool TryExportCompositeMLDsaPrivateKeyCore(Span<byte> destination, out int bytesWritten) =>
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa)));

/// <inheritdoc/>
protected override bool TryExportCompositeMLDsaPublicKeyCore(Span<byte> destination, out int bytesWritten) =>
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa)));

/// <inheritdoc/>
protected override bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten) =>
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa)));

/// <inheritdoc/>
protected override bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature) =>
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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.CodeAnalysis;
using System.Runtime.Versioning;

namespace System.Security.Cryptography
{
/// <summary>
/// Provides a Cryptography Next Generation (CNG) implementation of Composite ML-DSA.
/// </summary>
/// <remarks>
/// <para>
/// Developers are encouraged to program against the <see cref="CompositeMLDsa" /> base class,
/// rather than any specific derived class.
/// The derived classes are intended for interop with the underlying system
/// cryptographic libraries.
/// </para>
/// </remarks>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public sealed partial class CompositeMLDsaCng : CompositeMLDsa
{
/// <summary>
/// Initializes a new instance of the <see cref="CompositeMLDsaCng"/> class by using the specified <see cref="CngKey"/>.
/// </summary>
/// <param name="key">
/// The key that will be used as input to the cryptographic operations performed by the current object.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="key"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="key"/> does not specify a Composite ML-DSA group.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// Cryptography Next Generation (CNG) classes are not supported on this system.
/// </exception>
[SupportedOSPlatform("windows")]
public CompositeMLDsaCng(CngKey key)
: base(AlgorithmFromHandle(key))
{
throw new PlatformNotSupportedException();
}

private static CompositeMLDsaAlgorithm AlgorithmFromHandle(CngKey key) =>
throw new PlatformNotSupportedException();

/// <summary>
/// Gets a new <see cref="CngKey" /> representing the key used by the current instance.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// This instance has been disposed.
/// </exception>
/// <remarks>
/// This <see cref="CngKey"/> object is not the same as the one passed to <see cref="CompositeMLDsaCng(CngKey)"/>,
/// if that constructor was used. However, it will point to the same CNG key.
/// </remarks>
public partial CngKey GetKey();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#if NET10_0_OR_GREATER
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.CompositeMLDsa))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.CompositeMLDsaAlgorithm))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.CompositeMLDsaCng))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.MLDsa))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.MLDsaAlgorithm))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.MLDsaCng))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@
Link="Common\System\Security\Cryptography\CngHelpers.SignVerify.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\CngPkcs8.Shared.cs"
Link="Common\System\Security\Cryptography\CngPkcs8.Shared.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\CompositeMLDsaCng.cs"
Link="Common\System\Security\Cryptography\CompositeMLDsaCng.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\CompositeMLDsaCng.Windows.cs"
Link="Common\System\Security\Cryptography\CompositeMLDsaCng.Windows.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KeyPropertyName.cs"
Link="Common\System\Security\Cryptography\KeyPropertyName.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\MLDsaCng.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,154 @@ public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certific
#endif
}

/// <summary>
/// Gets the <see cref="CompositeMLDsa"/> public key from this certificate.
/// </summary>
/// <param name="certificate">
/// The X.509 certificate that contains the public key.
/// </param>
/// <returns>
/// The public key, or <see langword="null"/> if this certificate does not have a Composite ML-DSA public key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// The certificate has a Composite ML-DSA public key, but the platform does not support Composite ML-DSA.
/// </exception>
/// <exception cref="CryptographicException">
/// The public key was invalid, or otherwise could not be imported.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static CompositeMLDsa? GetCompositeMLDsaPublicKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);

#if NET10_0_OR_GREATER
return certificate.GetCompositeMLDsaPublicKey();
#else
if (CompositeMLDsaAlgorithm.GetAlgorithmFromOid(certificate.GetKeyAlgorithm()) is null)
{
return null;
}

ArraySegment<byte> encoded = GetCertificateSubjectPublicKeyInfo(certificate);

try
{
return CompositeMLDsa.ImportSubjectPublicKeyInfo(encoded);
}
finally
{
// SubjectPublicKeyInfo does not need to clear since it's public
CryptoPool.Return(encoded, clearSize: 0);
}
#endif
}

/// <summary>
/// Gets the <see cref="CompositeMLDsa"/> private key from this certificate.
/// </summary>
/// <param name="certificate">
/// The X.509 certificate that contains the private key.
/// </param>
/// <returns>
/// The private key, or <see langword="null"/> if this certificate does not have a Composite ML-DSA private key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// Retrieving a Composite ML-DSA private key from a certificate is not supported on this platform.
/// </exception>
/// <exception cref="CryptographicException">
/// An error occurred accessing the private key.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static CompositeMLDsa? GetCompositeMLDsaPrivateKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);

#if NET10_0_OR_GREATER
return certificate.GetCompositeMLDsaPrivateKey();
#else
if (CompositeMLDsaAlgorithm.GetAlgorithmFromOid(certificate.GetKeyAlgorithm()) is null)
{
return null;
}

throw new PlatformNotSupportedException();
#endif
}

/// <summary>
/// Combines a private key with a certificate containing the associated public key into a
/// new instance that can access the private key.
/// </summary>
/// <param name="certificate">
/// The X.509 certificate that contains the public key.
/// </param>
/// <param name="privateKey">
/// The Composite ML-DSA private key that corresponds to the Composite ML-DSA public key in this certificate.
/// </param>
/// <returns>
/// A new certificate with the <see cref="X509Certificate2.HasPrivateKey" /> property set to <see langword="true"/>.
/// The current certificate isn't modified.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> or <paramref name="privateKey"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// The specified private key doesn't match the public key for this certificate.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The certificate already has an associated private key.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// Combining a certificate and a Composite ML-DSA private key is not supported on this platform.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, CompositeMLDsa privateKey)
{
ArgumentNullException.ThrowIfNull(certificate);
ArgumentNullException.ThrowIfNull(privateKey);

#if NET10_0_OR_GREATER
return certificate.CopyWithPrivateKey(privateKey);
#elif NETSTANDARD
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLDsa)));
#else
if (!Helpers.IsOSPlatformWindows)
throw new PlatformNotSupportedException();

if (certificate.HasPrivateKey)
throw new InvalidOperationException(SR.Cryptography_Cert_AlreadyHasPrivateKey);

using (CompositeMLDsa? publicKey = GetCompositeMLDsaPublicKey(certificate))
{
if (publicKey is null)
{
throw new ArgumentException(SR.Cryptography_PrivateKey_WrongAlgorithm);
}

if (publicKey.Algorithm != privateKey.Algorithm)
{
throw new ArgumentException(SR.Cryptography_PrivateKey_DoesNotMatch, nameof(privateKey));
}

byte[] pk1 = publicKey.ExportCompositeMLDsaPublicKey();
byte[] pk2 = privateKey.ExportCompositeMLDsaPublicKey();

if (!pk1.SequenceEqual(pk2))
{
throw new ArgumentException(SR.Cryptography_PrivateKey_DoesNotMatch, nameof(privateKey));
}
}

throw new PlatformNotSupportedException();
#endif
}

#if !NET10_0_OR_GREATER
private static ArraySegment<byte> GetCertificateSubjectPublicKeyInfo(X509Certificate2 certificate)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public sealed partial class CmsSigner
{
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.AsymmetricAlgorithm? privateKey) { }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.CompositeMLDsa? privateKey) { }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.MLDsa? privateKey) { }
public CmsSigner(System.Security.Cryptography.Pkcs.SubjectIdentifierType signerIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.RSA? privateKey, System.Security.Cryptography.RSASignaturePadding? signaturePadding) { }
[System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ public CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? c
{
}

#if NET || NETSTANDARD2_1
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public
#else
private
#endif
CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate, CompositeMLDsa? privateKey)
: this(signerIdentifierType, certificate, privateKey, signaturePadding: null)
{
throw new PlatformNotSupportedException();
}

/// <summary>
/// Initializes a new instance of the CmsSigner class with a specified signer
/// certificate, subject identifier type, private key object, and RSA signature padding.
Expand Down
Loading
Loading