-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
This is the API proposal for #113505. Composite ML-DSA is a Post-Quantum Cryptography algorithm that combines ML-DSA with a traditional algorithm. The latest draft specification is here: https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs.
API Proposal
namespace System.Security.Cryptography
{
// S.S.C and M.B.C.
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public abstract class CompositeMLDsa : IDisposable
{
protected CompositeMLDsa(CompositeMLDsaAlgorithm algorithm);
public CompositeMLDsaAlgorithm Algorithm { get; }
public static bool IsSupported { get; }
public static bool IsAlgorithmSupported(CompositeMLDsaAlgorithm algorithm);
public byte[] SignData(byte[] data, byte[]? context = null);
public int SignData(ReadOnlySpan<byte> data, Span<byte> destination, ReadOnlySpan<byte> context = default(ReadOnlySpan<byte>));
protected abstract int SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination);
public bool VerifyData(byte[] data, byte[] signature, byte[]? context = null);
public bool VerifyData(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature, ReadOnlySpan<byte> context = default(ReadOnlySpan<byte>));
protected abstract bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature);
public static CompositeMLDsa GenerateKey(CompositeMLDsaAlgorithm algorithm);
public static CompositeMLDsa ImportCompositeMLDsaPrivateKey(CompositeMLDsaAlgorithm algorithm, byte[] source);
public static CompositeMLDsa ImportCompositeMLDsaPrivateKey(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
public static CompositeMLDsa ImportCompositeMLDsaPublicKey(CompositeMLDsaAlgorithm algorithm, byte[] source);
public static CompositeMLDsa ImportCompositeMLDsaPublicKey(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
public byte[] ExportCompositeMLDsaPrivateKey();
public bool TryExportCompositeMLDsaPrivateKey(Span<byte> destination, out int bytesWritten);
protected abstract bool TryExportCompositeMLDsaPrivateKeyCore(Span<byte> destination, out int bytesWritten);
public byte[] ExportCompositeMLDsaPublicKey();
public bool TryExportCompositeMLDsaPublicKey(Span<byte> destination, out int bytesWritten);
protected abstract bool TryExportCompositeMLDsaPublicKeyCore(Span<byte> destination, out int bytesWritten);
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
public byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters);
public byte[] ExportEncryptedPkcs8PrivateKey(string password, PbeParameters pbeParameters);
public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> password, PbeParameters pbeParameters);
public string ExportEncryptedPkcs8PrivateKeyPem(string password, PbeParameters pbeParameters);
public byte[] ExportPkcs8PrivateKey();
public string ExportPkcs8PrivateKeyPem();
public bool TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
public bool TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
public bool TryExportEncryptedPkcs8PrivateKey(string password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
public bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten);
protected abstract bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);
public byte[] ExportSubjectPublicKeyInfo();
public string ExportSubjectPublicKeyInfoPem();
public bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
public static CompositeMLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source);
public static CompositeMLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source);
public static CompositeMLDsa ImportEncryptedPkcs8PrivateKey(string password, byte[] source);
public static CompositeMLDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<byte> passwordBytes);
public static CompositeMLDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<char> password);
public static CompositeMLDsa ImportFromEncryptedPem(string source, byte[] passwordBytes);
public static CompositeMLDsa ImportFromEncryptedPem(string source, string password);
public static CompositeMLDsa ImportFromPem(ReadOnlySpan<char> source);
public static CompositeMLDsa ImportFromPem(string source);
public static CompositeMLDsa ImportPkcs8PrivateKey(byte[] source);
public static CompositeMLDsa ImportPkcs8PrivateKey(ReadOnlySpan<byte> source);
public static CompositeMLDsa ImportSubjectPublicKeyInfo(byte[] source);
public static CompositeMLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source);
}
// S.S.C and M.B.C.
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public sealed class CompositeMLDsaAlgorithm : IEquatable<CompositeMLDsaAlgorithm>
{
internal CompositeMLDsaAlgorithm() { }
public int MaxSignatureSizeInBytes { get; }
public static CompositeMLDsaAlgorithm MLDsa44WithECDsaP256 { get; }
public static CompositeMLDsaAlgorithm MLDsa44WithEd25519 { get; }
public static CompositeMLDsaAlgorithm MLDsa44WithRSA2048Pkcs15 { get; }
public static CompositeMLDsaAlgorithm MLDsa44WithRSA2048Pss { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithECDsaBrainpoolP256r1 { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithECDsaP256 { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithECDsaP384 { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithEd25519 { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithRSA3072Pkcs15 { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithRSA3072Pss { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithRSA4096Pkcs15 { get; }
public static CompositeMLDsaAlgorithm MLDsa65WithRSA4096Pss { get; }
public static CompositeMLDsaAlgorithm MLDsa87WithECDsaBrainpoolP384r1 { get; }
public static CompositeMLDsaAlgorithm MLDsa87WithECDsaP384 { get; }
public static CompositeMLDsaAlgorithm MLDsa87WithECDsaP521 { get; }
public static CompositeMLDsaAlgorithm MLDsa87WithEd448 { get; }
public static CompositeMLDsaAlgorithm MLDsa87WithRSA3072Pss { get; }
public static CompositeMLDsaAlgorithm MLDsa87WithRSA4096Pss { get; }
public string Name { get; }
public override bool Equals([NotNullWhen(true)] object? obj);
public bool Equals([NotNullWhen(true)] CompositeMLDsaAlgorithm? other);
public override int GetHashCode();
public static bool operator ==(CompositeMLDsaAlgorithm? left, CompositeMLDsaAlgorithm? right);
public static bool operator !=(CompositeMLDsaAlgorithm? left, CompositeMLDsaAlgorithm? right);
public override string ToString();
}
// S.S.C and M.B.C.
// M.B.C is .NET Framework and .NET only. No .NET Standard.
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public sealed class CompositeMLDsaCng : CompositeMLDsa
{
[SupportedOSPlatform("windows")]
public CompositeMLDsaCng(CngKey key);
public CngKey GetKey();
}
}
namespace System.Security.Cryptography.X509Certificates
{
// S.S.C
public sealed partial class CertificateRequest
{
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public CertificateRequest(X500DistinguishedName subjectName, CompositeMLDsa key);
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public CertificateRequest(string subjectName, CompositeMLDsa key);
}
// S.S.C
public sealed partial class PublicKey
{
public PublicKey(CompositeMLDsa key);
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
[UnsupportedOSPlatform("browser")]
public CompositeMLDsa? GetCompositeMLDsaPublicKey();
}
// S.S.C
public partial class X509Certificate2 : X509Certificate
{
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public X509Certificate2 CopyWithPrivateKey(CompositeMLDsa privateKey);
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public CompositeMLDsa? GetCompositeMLDsaPrivateKey();
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public CompositeMLDsa? GetCompositeMLDsaPublicKey();
}
// M.B.C
public static partial class X509CertificateKeyAccessors
{
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public static CompositeMLDsa? GetCompositeMLDsaPublicKey(this X509Certificate2 certificate);
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public static CompositeMLDsa? GetCompositeMLDsaPrivateKey(this X509Certificate2 certificate);
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, CompositeMLDsa privateKey);
}
// S.S.C
public abstract partial class X509SignatureGenerator
{
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public static X509SignatureGenerator CreateForCompositeMLDsa(CompositeMLDsa key);
}
}
namespace System.Security.Cryptography.Pkcs
{
public sealed partial class CmsSigner
{
// S.S.C.P - NetCoreApp only.
[Experimental("SYSLIB5006", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
public CmsSigner(SubjectIdentifierType signerIdentifierType, X509Certificate2? certificate, CompositeMLDsa? privateKey);
}
}There's no OpenSSL since we don't know if or how it will be implemented. OpenSSL uses a SafeEvpPKeyHandle for keys, so if they do the same for Composite ML-DSA, then we would want the following:
// S.S.C
[Experimental("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public sealed partial class CompositeMLDsaOpenSsl : CompositeMLDsa
{
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("osx")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public CompositeMLDsaOpenSsl(SafeEvpPKeyHandle pkeyHandle);
public SafeEvpPKeyHandle DuplicateKeyHandle();
}Alternative Designs
Since e is bounded in FIPS 186-5 and in the common implementations, we can expose the max key lengths and enforce them in the public APIs;
public sealed class CompositeMLDsaAlgorithm : IEquatable<CompositeMLDsaAlgorithm>
{
public int MaxSignatureSizeInBytes { get; }
+ public int MaxPrivateKeySizeInBytes { get; }
+ public int MaxPublicKeySizeInBytes { get; }
}
public abstract class CompositeMLDsa : IDisposable
{
public byte[] ExportCompositeMLDsaPrivateKey();
- public bool TryExportCompositeMLDsaPrivateKey(Span<byte> destination, out int bytesWritten);
- protected abstract bool TryExportCompositeMLDsaPrivateKeyCore(Span<byte> destination, out int bytesWritten);
+ public int ExportCompositeMLDsaPrivateKey(Span<byte> destination);
+ protected abstract int ExportCompositeMLDsaPrivateKeyCore(Span<byte> destination);
public byte[] ExportCompositeMLDsaPublicKey();
- public bool TryExportCompositeMLDsaPublicKey(Span<byte> destination, out int bytesWritten);
- protected abstract bool TryExportCompositeMLDsaPublicKeyCore(Span<byte> destination, out int bytesWritten);
+ public int ExportCompositeMLDsaPublicKey(Span<byte> destination);
+ protected abstract int ExportCompositeMLDsaPublicKeyCore(Span<byte> destination);
}a middle ground is to just expose them to derived classes:
public abstract class CompositeMLDsa : IDisposable
{
public byte[] ExportCompositeMLDsaPrivateKey();
public bool TryExportCompositeMLDsaPrivateKey(Span<byte> destination, out int bytesWritten);
- protected abstract bool TryExportCompositeMLDsaPrivateKeyCore(Span<byte> destination, out int bytesWritten);
+ protected abstract int ExportCompositeMLDsaPrivateKeyCore(Span<byte> destination);
public byte[] ExportCompositeMLDsaPublicKey();
public bool TryExportCompositeMLDsaPublicKey(Span<byte> destination, out int bytesWritten);
- protected abstract bool TryExportCompositeMLDsaPublicKeyCore(Span<byte> destination, out int bytesWritten);
+ protected abstract int ExportCompositeMLDsaPublicKeyCore(Span<byte> destination);
}Key/signature sizes
Knowing the length of the private/public key and signature might be useful for the API review. The Composite ML-DSA spec defines 18 ML-DSA/traditional algorithm pairs. From these listed algorithms, the following table shows the size of various structures, grouped by algorithm family:
| Public Key | Fixed Size | Variable size with upper bound | Variable size without upper bound |
|---|---|---|---|
| ML-DSA | x | ||
| RSA | x* | ||
| ECDSA | x | ||
| EdDSA | x | ||
| Composite ML-DSA | x* |
| Private Key | Fixed Size | Variable size with upper bound | Variable size without upper bound |
|---|---|---|---|
| ML-DSA | x | ||
| RSA | x* | ||
| ECDSA | x | ||
| EdDSA | x | ||
| Composite ML-DSA | x* |
* The parameter in RSA that can be arbitrarily large is e. However, since increasing the size of e does not increase the security, in practice this is always e = 65537. The remaining parameters are bounded.
| Signature | Fixed Size | Variable size with upper bound | Variable size without upper bound |
|---|---|---|---|
| ML-DSA | x | ||
| RSA | x | ||
| ECDSA | x | ||
| EdDSA | x | ||
| Composite ML-DSA | x |
Risks
Windows (and OpenSSL if we implement CompositeMLDsaOpenSsl) may not follow existing patterns for key handles, so those APIs might not end up being the correct abstraction. On the other hand, if we don't introduce these APIs before GA and partners ask for this feature, then it will be too late to add. The proposal includes the APIs since their experimental nature gives us some flexibility. And further, these APIs at GA would throw exceptions, so it is unlikely that anyone would take a dependency on them and be broken by updates to them.