Skip to content

Add Composite ML-DSA #113505

@bartonjs

Description

@bartonjs

As part of the Post-Quantum Cryptography effort, we should add support for the IETF Composite Modular-Lattice Digital Signature Algorithm (Composite ML-DSA).

As a signature algorithm, Composite ML-DSA should be available as a primitive, as well as integrated into:

  • X509Certificate2
  • SignedXml
  • COSE
  • TLS (no API required, carried with the certificate)

The name of the Composite ML-DSA algorithm is the name of the ML-DSA algorithm prepended with "Composite": CompositeMLDsa.

namespace System.Security.Cryptography
{
    [Experimental("SYSLIB5006")]
    public abstract class CompositeMLDsa : IDisposable
    {
        public static bool IsSupported { get; }
        public static bool IsAlgorithmSupported(CompositeMLDsaAlgorithm algorithm);

        protected CompositeMLDsa(CompositeMLDsaAlgorithm algorithm);

        // TODO: Are the EC-DSA and EdDSA signatures fixed length? If not, this needs "Max"
        public int SignatureSizeInBytes { get; }

        public void Dispose();

        public int SignData(
            ReadOnlySpan<byte> data,
            Span<byte> destination,
            ReadOnlySpan<byte> context = default);
        public bool VerifyData(
            ReadOnlySpan<byte> data,
            ReadOnlySpan<byte> signature,
            ReadOnlySpan<byte> context = default);

        public int SignPreHash(
            ReadOnlySpan<byte> hash,         
            Span<byte> destination,
            HashAlgorithmName preHashAlgorithm,
            ReadOnlySpan<byte> context = default);
        public bool SignPreHash(
            ReadOnlySpan<byte> hash,         
            ReadOnlySpan<byte> signature,
            HashAlgorithmName preHashAlgorithm,
            ReadOnlySpan<byte> context = default);

        public byte[] ExportSubjectPublicKeyInfo();
        public bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
        public string ExportSubjectPublicKeyInfoPem();

        public byte[] ExportPkcs8PrivateKey();
        public bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten);
        public string ExportPkcs8PrivateKeyPem();

        public byte[] ExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters);
        public byte[] ExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters);
        public bool TryExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters,
            Span<byte> destination,
            out int bytesWritten);
        public bool TryExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters,
            Span<byte> destination,
            out int bytesWritten);
        public string ExportEncryptedPkcs8PrivateKeyPem(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters);
        public string ExportEncryptedPkcs8PrivateKeyPem(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters);

        /* TODO: Should we export the Composite ML-DSA formats not in SPKI/PKCS8? */

        public static CompositeMLDsa GenerateKey(CompositeMLDsaAlgorithm algorithm);

        public static CompositeMLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source);
        public static CompositeMLDsa ImportPkcs8PrivateKey(ReadOnlySpan<byte> source);
        public static CompositeMLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source);
        public static CompositeMLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source);
        public static CompositeMLDsa ImportFromPem(ReadOnlySpan<char> source);
        public static CompositeMLDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<char> password);
        public static CompositeMLDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<byte> passwordBytes);

        protected void ThrowIfDisposed();
        protected virtual void Dispose(bool disposing);

        protected abstract void SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination);
        protected abstract bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature);
        protected abstract void SignPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, HashAlgorithmName preHashAlgorithm, Span<byte> destination);
        protected abstract bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, HashAlgorithmName preHashAlgorithm, ReadOnlySpan<byte> signature);

        /* TODO: Define protected abstracts for export */
    }

    [DebuggerDisplay("{Name,nq}")]
    [Experimental("SYSLIB5006")]
    public sealed class CompositeMLDsaAlgorithm
    {
        private CompositeMLDsaAlgorithm();

        public static CompositeMLDsaAlgorithm MLDsa44WithRSA2048Pss { get; }
        public static CompositeMLDsaAlgorithm MLDsa44WithRSA2048Pkcs15 { get; }
        public static CompositeMLDsaAlgorithm MLDsa44WithEd25519 { get; }
        public static CompositeMLDsaAlgorithm MLDsa44WithECDsaP256 { get; }
        public static CompositeMLDsaAlgorithm MLDsa65WithRSA3072Pss { get; }
        public static CompositeMLDsaAlgorithm MLDsa65WithRSA3072Pkcs15 { get; }
        public static CompositeMLDsaAlgorithm MLDsa65WithRSA4096Pss { get; }
        public static CompositeMLDsaAlgorithm MLDsa65WithRSA4096Pkcs15 { get; }
        public static CompositeMLDsaAlgorithm MLDsa65WithECDsaP384 { get; }
        public static CompositeMLDsaAlgorithm MLDsa65WithECDsaBrainpoolP256r1 { get; }
        public static CompositeMLDsaAlgorithm MLDsa65WithEd25519 { get; }
        public static CompositeMLDsaAlgorithm MLDsa87WithECDsaP384 { get; }
        public static CompositeMLDsaAlgorithm MLDsa87WithECDsaBrainpoolP384r1 { get; }
        public static CompositeMLDsaAlgorithm MLDsa87WithEd448 { get; }

        public string Name { get; }
        // TODO: Are the EC-DSA and EdDSA signatures fixed length? If not, this needs "Max"
        public int SignatureSizeInBytes { get; }
    }

    [Experimental("SYSLIB5006")]
    public class CompositeMLDsaCng : CompositeMLDsa
    {
         public CompositeMLDsaCng(CngKey key);
         // On ECDsaCng this is an allocating property. Changed to a method here.
         public CngKey GetCngKey();
    }

    [Experimental("SYSLIB5006")]
    public class CompositeMLDsaOpenSsl : CompositeMLDsa
    {
         public CompositeMLDsaOpenSsl(SafeEvpPKeyHandle keyHandle);
         public SafeEvpPKeyHandle DuplicateKeyHandle();
    }
}

namespace System.Security.Cryptography.X509Certificates
{
    // Extension class to enable porting to Microsoft.Bcl.Cryptography
    [Experimental("SYSLIB5006")]
    public sealed class CompositeMLDsaCertificateExtensions
    {
        public static CompositeMLDsa GetCompositeMLDsaPublicKey(this X509Certificate2 certificate);
        public static CompositeMLDsa GetCompositeMLDsaPrivateKey(this X509Certificate2 certificate);
    }

    public partial class CertificateRequest
    {
         [Experimental("SYSLIB5006")]
         public CertificateRequest(string subjectName, CompositeMLDsa key);
         [Experimental("SYSLIB5006")]
         public CertificateRequest(X500DistinguishedName subjectName, CompositeMLDsa key);
    }

#if NET10_OR_GREATER
    public partial class X509SignatureGenerator
    {
        [Experimental("SYSLIB5006")]
        public static X509SignatureGenerator CreateForCompositeMLDsa(CompositeMLDsa key);
    }
#endif
}

#if NET10_OR_GREATER
namespace System.Security.Cryptography.Pkcs
{
    public partial class CmsSigner
    {
        [Experimental("SYSLIB5006")]
        public CmsSigner(
            X509Certificate2 certificate,
            CompositeMLDsa privateKey,
            SubjectIdentifierType signerIdentifierType = SubjectIdentifierType.SubjectKeyIdentifier);
    }
}
#endif

namespace System.Security.Cryptography.Cose
{
    public partial class CoseSigner
    {
        // As PrivateKey is marked as non-nullable, this will set it to an instance of a non-public type that wraps the key.
        [Experimental("SYSLIB5006")]
        public CoseSigner(CompositeMLDsa key);
    }
}

Sub-issues

Metadata

Metadata

Assignees

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions