src/libraries/System.Security.Cryptography/docs/PostQuantumCrypto.SecurityDesign.md
This document covers the following types:
Any future public/private-key algorithms are expected to be designed along these same principles.
public virtual members (or public abstract).
protected virtual or protected abstract members for processing. (This is an aspect of the "Template Method Pattern")internal methods accepting Span or ReadOnlySpan, and only break it down to pointer and length at the interop boundary.virtual or abstract member will be invoked on a disposed instance.
public abstract class ALGORITHM : IDisposable
{
public static ALGORITHM GenerateKey(ALGORITHM_GENERATION_OPTIONS);
public static ALGORITHM ImportPkcs8PrivateKey(...);
public static ALGORITHM ImportEncryptedPkcs8PrivateKey(...);
public static ALGORITHM ImportSubjectPublicKeyInfo(...);
public static ALGORITHM ImportFromPem(...);
public static ALGORITHM ImportFromEncryptedPem(...);
public static ALGORITHM ImportALGORITHMSPECIFICPRIVATEKEYFORMAT(...);
public static ALGORITHM ImportALGORITHMSPECIFICPUBLICKEYFORMAT(...);
protected ALGORITHM(ALGORITHM_IDENTIFICATION);
public void Dispose();
protected virtual void Dispose(bool disposing);
public ALGORITHM_IDENTIFICATION ...;
// e.g. public MLDsaAlgorithm Algorithm { get; } for ML-DSA
// to identify ML-DSA-44, ML-DSA-65, or ML-DSA-87, and information such as
// the size of a signature.
public ALGORITHM_PUBLIC_ANSWER ALGORITHM_PUBLIC_OP(...);
protected abstract ALGORITHM_PUBLIC_ANSWER ALGORITHM_PUBLIC_OPCore(...);
public ALGORITHM_PRIVATE_ANSWER ALGORITHM_PRIVATE_OP(...);
protected abstract ALGORITHM_PRIVATE_ANSWER ALGORITHM_PRIVATE_OPCore(...);
public byte[] ExportSubjectPublicKeyInfo();
public byte[] ExportPkcs8PrivateKey();
public byte[] ExportEncryptedPkcs8PrivateKey(...);
public byte[] ExportALGORITHMSPECIFICPRIVATEKEYFORMAT(...);
public byte[] ExportALGORITHMSPECIFICPUBLICKEYFORMAT(...);
protected abstract void ExportALGORITHMSPECIFICPRIVATEKEYFORMATCore(Span<byte> destination, ...);
protected abstract void ExportALGORITHMSPECIFICPUBLICKEYFORMATCore(Span<byte> destination, ...);
}
public sealed class ALGORITHMCng : ALGORITHM
{
public ALGORITHMCng(CngKey key);
public CngKey GetCngKey();
}
public sealed class ALGORITHMOpenSsl : ALGORITHM
{
public ALGORITHMOpenSsl(SafeEvpPKeyHandle key);
public SafeEvpPKeyHandle DuplicateKeyHandle();
}
The PEM import routines for the PQC primitives follow the precedent of PEM import for AsymmetricAlgorithm types:
ImportFromPem considers ENCRYPTED PRIVATE KEY as a known label.ImportFromPem will fail if the only known label encountered is ENCRYPTED PRIVATE KEY.
ImportFromEncryptedPem considers only ENCRYPTED PRIVATE KEY to be valid.ImportFromEncryptedPem defers work to ImportEncryptedPkcs8PrivateKey after decode.ImportFromPem defers work to an appropriate method, based on the label, after decode.
Encrypted PKCS#8 imports follow in the precedent from AsymmetricAlgorithm.
id-ce-keyUsage) to restrict RSA keys to sign-only or encrypt-only, or ECC keys to sign-only (to distinguish EC-DSA from EC-DH).
public abstract partial class MLKem : IDisposable
{
protected MLKem(MLKemAlgorithm algorithm);
public MLKemAlgorithm Algorithm { get; }
protected virtual void Dispose(bool disposing) { }
protected abstract void DecapsulateCore(ReadOnlySpan<byte> ciphertext, Span<byte> sharedSecret);
protected abstract void EncapsulateCore(Span<byte> ciphertext, Span<byte> sharedSecret);
protected abstract void ExportDecapsulationKeyCore(Span<byte> destination);
protected abstract void ExportEncapsulationKeyCore(Span<byte> destination);
protected abstract void ExportPrivateSeedCore(Span<byte> destination);
protected abstract bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);
public static MLKem GenerateKey(MLKemAlgorithm algorithm);
public static MLKem ImportDecapsulationKey(MLKemAlgorithm algorithm, ReadOnlySpan<byte> source);
public static MLKem ImportEncapsulationKey(MLKemAlgorithm algorithm, ReadOnlySpan<byte> source);
public static MLKem ImportPrivateSeed(MLKemAlgorithm algorithm, ReadOnlySpan<byte> source);
}
public sealed partial class MLKemAlgorithm : IEquatable<MLKemAlgorithm>
{
internal MLKemAlgorithm();
public int CiphertextSizeInBytes { get; }
public int DecapsulationKeySizeInBytes { get; }
public int EncapsulationKeySizeInBytes { get; }
public static MLKemAlgorithm MLKem1024 { get; }
public static MLKemAlgorithm MLKem512 { get; }
public static MLKemAlgorithm MLKem768 { get; }
public string Name { get; }
public int PrivateSeedSizeInBytes { get; }
public int SharedSecretSizeInBytes { get; }
public override bool Equals([NotNullWhen(true)] object? obj);
public bool Equals([NotNullWhen(true)] MLKemAlgorithm? other);
public override int GetHashCode();
public static bool operator ==(MLKemAlgorithm? left, MLKemAlgorithm? right);
public static bool operator !=(MLKemAlgorithm? left, MLKemAlgorithm? right);
public override string ToString();
}
public abstract partial class MLDsa : IDisposable
{
protected MLDsa(MLDsaAlgorithm algorithm);
public MLDsaAlgorithm Algorithm { get; }
protected virtual void Dispose(bool disposing) { }
protected abstract void ExportMLDsaPrivateSeedCore(Span<byte> destination);
protected abstract void ExportMLDsaPublicKeyCore(Span<byte> destination);
protected abstract void ExportMLDsaPrivateKeyCore(Span<byte> destination);
protected abstract void SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination);
protected abstract void SignMuCore(ReadOnlySpan<byte> externalMu, Span<byte> destination);
protected abstract void SignPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, Span<byte> destination);
protected abstract bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);
protected abstract bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature);
protected abstract bool VerifyMuCore(ReadOnlySpan<byte> externalMu, ReadOnlySpan<byte> signature);
protected abstract bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature);
public static MLDsa GenerateKey(MLDsaAlgorithm algorithm);
public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
public static MLDsa ImportMLDsaPrivateKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
}
public sealed partial class MLDsaAlgorithm : IEquatable<MLDsaAlgorithm>
{
internal MLDsaAlgorithm();
public static MLDsaAlgorithm MLDsa44 { get; }
public static MLDsaAlgorithm MLDsa65 { get; }
public static MLDsaAlgorithm MLDsa87 { get; }
public string Name { get; }
public int MuSizeInBytes { get; }
public int PrivateSeedSizeInBytes { get; }
public int PublicKeySizeInBytes { get; }
public int PrivateKeySizeInBytes { get; }
public int SignatureSizeInBytes { get; }
public override bool Equals([NotNullWhen(true)] object? obj);
public bool Equals([NotNullWhen(true)] MLDsaAlgorithm? other);
public override int GetHashCode();
public static bool operator ==(MLDsaAlgorithm? left, MLDsaAlgorithm? right);
public static bool operator !=(MLDsaAlgorithm? left, MLDsaAlgorithm? right);
public override string ToString();
}
public abstract partial class SlhDsa : IDisposable
{
protected SlhDsa(SlhDsaAlgorithm algorithm);
public SlhDsaAlgorithm Algorithm { get; }
protected virtual void Dispose(bool disposing) { }
protected abstract void ExportSlhDsaPublicKeyCore(Span<byte> destination);
protected abstract void ExportSlhDsaPrivateKeyCore(Span<byte> destination);
protected abstract void SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination);
protected abstract void SignPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, Span<byte> destination);
protected abstract bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature);
protected abstract bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, string hashAlgorithmOid, ReadOnlySpan<byte> signature);
protected virtual bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);
public static SlhDsa GenerateKey(SlhDsaAlgorithm algorithm);
public static SlhDsa ImportSlhDsaPublicKey(SlhDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
public static SlhDsa ImportSlhDsaPrivateKey(SlhDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
}
public sealed partial class SlhDsaAlgorithm : IEquatable<SlhDsaAlgorithm>
{
internal SlhDsaAlgorithm();
public string Name { get; }
public int PublicKeySizeInBytes { get; }
public int PrivateKeySizeInBytes { get; }
public int SignatureSizeInBytes { get; }
public static SlhDsaAlgorithm SlhDsaSha2_128f { get; }
public static SlhDsaAlgorithm SlhDsaSha2_128s { get; }
public static SlhDsaAlgorithm SlhDsaSha2_192f { get; }
public static SlhDsaAlgorithm SlhDsaSha2_192s { get; }
public static SlhDsaAlgorithm SlhDsaSha2_256f { get; }
public static SlhDsaAlgorithm SlhDsaSha2_256s { get; }
public static SlhDsaAlgorithm SlhDsaShake128f { get; }
public static SlhDsaAlgorithm SlhDsaShake128s { get; }
public static SlhDsaAlgorithm SlhDsaShake192f { get; }
public static SlhDsaAlgorithm SlhDsaShake192s { get; }
public static SlhDsaAlgorithm SlhDsaShake256f { get; }
public static SlhDsaAlgorithm SlhDsaShake256s { get; }
public override bool Equals([NotNullWhen(true)] object? obj);
public bool Equals([NotNullWhen(true)] SlhDsaAlgorithm? other);
public override int GetHashCode();
public static bool operator ==(SlhDsaAlgorithm? left, SlhDsaAlgorithm? right);
public static bool operator !=(SlhDsaAlgorithm? left, SlhDsaAlgorithm? right);
public override string ToString();
}
public abstract partial class CompositeMLDsa : IDisposable
{
protected CompositeMLDsa(CompositeMLDsaAlgorithm algorithm);
public CompositeMLDsaAlgorithm Algorithm { get; }
protected virtual void Dispose(bool disposing) { }
protected abstract int ExportCompositeMLDsaPrivateKeyCore(Span<byte> destination);
protected abstract int ExportCompositeMLDsaPublicKeyCore(Span<byte> destination);
protected abstract bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten);
protected abstract int SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination, out int bytesWritten);
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, ReadOnlySpan<byte> source);
public static CompositeMLDsa ImportCompositeMLDsaPublicKey(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
}
public sealed partial 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();
}
Certificates in .NET have two concurrent representations: the .NET projection and the underlying "native" representation. Users on any platform that restricts certificate loading/parsing to known subject public key (and/or CA signature) algorithms, and does not support these PQC types, will be unable to interact with certificates utilizing these algorithms. Users on platforms that do not support these PQC algorithms, but do support loading certificates with unknown algorithms, will be able to load such a certificate, but will not be able to use the certificates in X.509 Chain Building, CMS, et cetera.
Chain building is performed by the underlying OS. If a chain involves a signature based on any of these algorithms, and the OS supports them, the OS will validate the signature; there is no mechanism by which to limit the signature algorithms that can be utilized during a chain build.
Platforms that support the algorithms in certificates generally support loading their keys from a PFX. PFX private key matching involves loading the key and comparing its public key representation against the candidate certificate's SubjectPublicKeyInfo. There is no mechanism by which to limit the loadable algorithms during PFX key matching.
Callers who wish to avoid the use of these algorithms in a PKCS#12/PFX load can use the Pkcs12Info class to
read the metadata out of a PFX as a pre-load filter.
Additionally, callers can avoid private key loading and matching by asserting Pkcs12LoaderLimits.IgnorePrivateKeys as true.
The .NET SignedCms class can find signer certificates from the embedded certificates collection. The signature verification methods will evaluate the signature using whatever certificate matches the target SignerInfo value, whether that certificate come from a caller provided collection or the embedded collection.
Callers who wish to avoid the use of these algorithms in SignedCms can either inspect the SignedCms.Certificates collection,
or see the automatically matched certificate on SignerInfo.Certificate before calling a CheckSignature routine.
The only algorithm in this document relevant to CMS EnvelopedData is ML-KEM. ML-KEM is represented in EnvelopedData via a RecipientInfo of type OtherRecipientInfo, with an oriType value of id-ori-kem (1.2.840.113549.1.9.16.13.3).
On Windows, the .NET EnvelopedCms class defers work to the Windows CryptMsg* routines. However, the EnvelopedCms projection has a representation of each RecipientInfo to track certificate matching for KEK decryption. The Windows PAL is only capable of representing CMSG_KEY_TRANS_RECIPIENT (KeyTransRecipient) and CMSG_KEY_AGREE_RECIPIENT (KeyAgreeRecipient), a recipient of any other kind will fail to load.
On other platforms, the .NET EnvelopedCms class uses a managed implementation. The managed implementation will fail to load an EnvelopedData structure containing a RecipientInfo other than KeyTransRecipientInfo or KeyAgreeRecipientInfo.
As no .NET platforms can even load an EnvelopedCms/CMS EnvelopedData value without an exception, there is no possible indirect use of these algorithms with this type.
When .NET is acting in the TLS Client role, and mutual authentication is not being employed, .NET is mostly "not involved" in the TLS handshake. Therefore, the underlying connection could be authenticated by a TLS Server Certificate which either (or both) contains a PQC SubjectPublicKeyInfo value, or is signed by a CA that utilizes a PQC algorithm.
Callers who wish to block such a handshake must make use of the server certificate verification callback and inspect the chain themselves.
When .NET is acting in the TLS Server role, or mutual authentication is being employed in the client role, .NET is "slightly involved" in conveying the certificate and private key to the underlying system TLS libraries. Low-touch platforms, like Windows, it is possible to have an X509Certificate2 instance that has a known private key where .NET itself is not capable of understanding the private key, yet the certificate works in TLS. High-touch platforms, like Linux, require .NET to understand the algorithm to understand how to forward on the private key.
Either way, the caller provides the identity certificate, and it is up to the caller to decide what certificate(s) is/are acceptable before doing so.