dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
16.04k stars 4.93k forks source link

Add SLH-DSA #113506

Open bartonjs opened 1 week ago

bartonjs commented 1 week ago

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:

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);
    }
}
dotnet-policy-service[bot] commented 1 week ago

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

bartonjs commented 1 week ago

This is a clone of #113502 except

COSE hasn't yet cared about SLH-DSA (I think), but I expect they will at some point, so it's worth having in the API proposal (when it's ready); even if we end up cutting it for 10 because there's no spec to base it on.