Add SLH-DSA

Open
#113,506 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
csharp

Research direction

Start with the linked Post-Quantum Cryptography effort and the proposed SlhDsa and SlhDsaAlgorithm API in this issue. Review how X509Certificate2, SignedXml, COSE, and TLS currently integrate signature algorithms. Done means SLH-DSA is available as a primitive and integrated into the listed certificate, XML, COSE, and TLS paths.

Written by the indexing model from the issue text.

Description

area-System.Security cryptographic-docs-impact

As part of the Post-Quantum Cryptography effort, we should add support for Stateless Hash-Based Digital Signature Algorithm (SLH-DSA).

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

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

As the algorithm (family) name is generally written as SLH-DSA, the .NET name is composed of the concatenation of two separate three-letter initialisms, so two capital letters and 4 lowercase: SlhDsa

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

        protected SlhDsa(SlhDsaAlgorithm algorithm);

        public SlhDsaAlgorithm Algorithm { get; }
        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);

        public int ExportSlhDsaPublicKey(Span<byte> destination);
        public int ExportSlhDsaSecretKey(Span<byte> destination);
        public int ExportSlhDsaPrivateSeed(Span<byte> destination);

        public static SlhDsa GenerateKey(SlhDsaAlgorithm algorithm);

        public static SlhDsa ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source);
        public static SlhDsa ImportPkcs8PrivateKey(ReadOnlySpan<byte> source);
        public static SlhDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source);
        public static SlhDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source);
        public static SlhDsa ImportFromPem(ReadOnlySpan<char> source);
        public static SlhDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<char> password);
        public static SlhDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<byte> passwordBytes);
        public static SlhDsa ImportSlhDsaPublicKey(SlhDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
        public static SlhDsa ImportSlhDsaSecretKey(SlhDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
        public static SlhDsa ImportSlhDsaPrivateSeed(SlhDsaAlgorithm algorithm, ReadOnlySpan<byte> source);

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

        protected abstract void ExportSlhDsaPublicKeyCore(Span<byte> destination);
        protected abstract void ExportSlhDsaSecretKeyCore(Span<byte> destination);
        protected abstract void ExportSlhDsaPrivateSeedCore(Span<byte> destination);
    }

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

        public static SlhDsaAlgorithm SlhDsaSha2_128s { get; }
        public static SlhDsaAlgorithm SlhDsaSha2_128f { get; }
        public static SlhDsaAlgorithm SlhDsaSha2_192s { get; }
        public static SlhDsaAlgorithm SlhDsaSha2_192f { get; }
        public static SlhDsaAlgorithm SlhDsaSha2_256s { get; }
        public static SlhDsaAlgorithm SlhDsaSha2_256f { get; }
        public static SlhDsaAlgorithm SlhDsaShake128s { get; }
        public static SlhDsaAlgorithm SlhDsaShake128f { get; }
        public static SlhDsaAlgorithm SlhDsaShake192s { get; }
        public static SlhDsaAlgorithm SlhDsaShake192f { get; }
        public static SlhDsaAlgorithm SlhDsaShake256s { get; }
        public static SlhDsaAlgorithm SlhDsaShake256f { get; }

        public string Name { get; }
        public int PublicKeySizeInBytes { get; }
        public int SecretKeySizeInBytes { get; }
        public int SignatureSizeInBytes { get; }
    }

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

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

namespace System.Security.Cryptography.X509Certificates
{
    // Extension class to enable porting to Microsoft.Bcl.Cryptography
    [Experimental("SYSLIB5006")]
    public sealed class SlhDsaCertificateExtensions
    {
        public static SlhDsa GetSlhDsaPublicKey(this X509Certificate2 certificate);
        public static SlhDsa GetSlhDsaPrivateKey(this X509Certificate2 certificate);
    }

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

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

#if NET10_OR_GREATER
namespace System.Security.Cryptography.Pkcs
{
    public partial class CmsSigner
    {
        [Experimental("SYSLIB5006")]
        public CmsSigner(
            X509Certificate2 certificate,
            SlhDsa 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(SlhDsa key);
    }
}
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/runtime

All issues in dotnet/runtime

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.