Color scheme: Light
You are here: start » en » Documentation » Post-Quantum Cryptography » WKL.System.Security.Cryptography overview » How-to guides » Create a certificate request

Create a certificate request

A server needs a hybrid certificate from a certification authority (CA), and its private keys must never leave the server. The server creates a certificate request (PKCS #10), and the CA issues the certificate from it.

Solution overview

SideElementPurpose
RequesterCertificateRequest.CreateSigningRequest with CryptoMode.Hybridrequest with an RSA key and an ML-DSA key, with both proofs of possession signed
CAX509CertificateBuilder.LoadSigningRequestreads the request and verifies both signatures
CAX509CertificateBuilder.Create with CryptoMode.Hybridissues the hybrid certificate
RequesterCopyWithPrivateKey, AttachAlternativePrivateMLDsaKeyconnects the certificate with both of its private keys

Implementation

On the server:

using var serverRsa = RSA.Create(3072);
using var serverPq = MLDsaKey.Generate(MLDsaParameterSet.MLDsa65);
var request = new CertificateRequest(
    "CN=www.example.com", serverRsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
 
byte[] csr = request.CreateSigningRequest(CryptoMode.Hybrid, serverPq);
File.WriteAllBytes("www.example.com.csr", csr);

At the CA. Here, ca is a hybrid issuer certificate whose RSA and ML-DSA keys are in the process, for example after a PFX import:

using var caKey = RSA.Create(3072);
var caRequest = new CertificateRequest(
    "CN=Contoso Hybrid CA", caKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
caRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
caRequest.CertificateExtensions.Add(
    new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));
using var ca = caRequest.CreateSelfSigned(
    DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(5), CryptoMode.Hybrid);
 
var incoming = X509CertificateBuilder.LoadSigningRequest(
    File.ReadAllBytes("www.example.com.csr"), HashAlgorithmName.SHA256,
    signerSignaturePadding: RSASignaturePadding.Pkcs1, mode: CryptoMode.Hybrid);
incoming.CertificateExtensions.Add(new X509KeyUsageExtension(
    X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true));
 
using var issued = incoming.Create(
    ca, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1),
    RandomNumberGenerator.GetBytes(16), CryptoMode.Hybrid);
File.WriteAllText("www.example.com.pem", issued.ExportCertificatePem());

Back on the server:

using var certificate = X509Certificate2.CreateFromPem(File.ReadAllText("www.example.com.pem"));
using var server = certificate.CopyWithPrivateKey(serverRsa);
server.AttachAlternativePrivateMLDsaKey(serverPq);

Remarks

  • Both proofs of possession are verified. LoadSigningRequest with CryptoMode.Hybrid verifies the request's RSA signature and its ML-DSA signature. If either is invalid, the method throws a CryptographicException.
  • The ML-DSA key comes from the request. The issued certificate carries exactly the requester's public ML-DSA key. The CA doesn't generate a key of its own.
  • Generate the ML-DSA key yourself. Without the serverPq argument, CreateSigningRequest generates a key that you can't reach later. Always pass your own key.
  • Keep the private ML-DSA key. The server needs serverPq again when the certificate arrives. Keep the key until then, for example in a protected file written with ExportPkcs8PrivateKey, and afterward move the finished certificate as a PFX file.
  • Pure ML-DSA request. CryptoMode.PostQuantum with a request from MLDsaX509SignatureGenerator.CreateCertificateRequest produces a request that contains ML-DSA only. Like every operation in PostQuantum mode, it requires PostQuantumExemptions.PurePostQuantum; see Post-quantum algorithm policy.
en/wiki/pqcrypt/lib/how-to/request-certificates.txt · Last modified: by Wolfgang van der Stille