Farbschema: Hell
Sie befinden sich hier: start » en » Documentation » Post-Quantum Cryptography » WKL.System.Security.Cryptography overview » Get started » Create your first hybrid certificate chain

Create your first hybrid certificate chain

In this tutorial, you issue a hybrid root certificate for the fictitious company Contoso, use it to issue a server certificate for www.example.com, and then validate the chain twice: once the way any existing software validates it, and once including the post-quantum part. Hybrid means that every certificate carries its usual RSA signature plus an ML-DSA signature. Software that doesn't know about PQ sees an ordinary RSA certificate.

Prerequisites

  • The .NET 8 SDK or later to run the sample, and the .NET 10 SDK to build the library.
  • OpenSSL 3.5 or later for your process architecture. See Prerequisites.
  • Optional, for the cross-check in step 6: any OpenSSL command-line tool, version 3.0 or later.

Steps

Step 1: Create a project and reference the library

dotnet new console -n ContosoPki -f net8.0
cd ContosoPki
dotnet add reference ..\sys-sec-crypto\WKL.System.Security.Cryptography.Extensions.csproj

Adjust the path to where the library's source code is on your machine.

Step 2: Configure OpenSSL and turn on hybrid mode

Replace the contents of Program.cs with:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using WKL.System.Security.Cryptography;
using WKL.System.Security.Cryptography.X509Certificates;
 
PostQuantumConfig.OpenSslPath = @"C:\OpenSSL\bin";
PostQuantumConfig.EnablePostQuantum(CryptoMode.Hybrid);
Console.WriteLine(PostQuantumConfig.GetOpenSslVersion());

OpenSslPath is the folder that contains libcrypto. EnablePostQuantum loads OpenSSL and throws a CryptographicException if it's missing or doesn't provide ML-DSA and ML-KEM. That way, the library never quietly continues without the PQ part.

Check: dotnet run prints the OpenSSL version, for example OpenSSL 3.6.1 27 Jan 2026.

Step 3: Issue the hybrid root certificate

Append:

using var rootKey = RSA.Create(3072);
var rootRequest = new CertificateRequest(
    "CN=Contoso Root CA", rootKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
rootRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
rootRequest.CertificateExtensions.Add(
    new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));
 
using var root = rootRequest.CreateSelfSigned(
    DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(5), CryptoMode.Hybrid);
Console.WriteLine(root.GetSignatureAlgorithmDescription());

This is the .NET call with one extra argument. Without CryptoMode.Hybrid, you'd get an ordinary RSA certificate. The library generates an ML-DSA-65 key pair for the certificate and keeps the private key available for the lifetime of the process.

Check: The output is sha256RSA + ML-DSA-65 (Hybrid).

Step 4: Issue the server certificate

using var leafKey = RSA.Create(2048);
var leafRequest = new CertificateRequest(
    "CN=www.example.com", leafKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
 
using var leaf = leafRequest.Create(
    root, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1),
    RandomNumberGenerator.GetBytes(16), CryptoMode.Hybrid);
using var server = leaf.CopyWithPrivateKey(leafKey);
Console.WriteLine(server.HasPqSignature());

The root certificate signs with its RSA key and with the ML-DSA key from step 3. If that ML-DSA key isn't in the process, for example because you loaded the root certificate from a PEM file instead of a PFX file, the call fails with a CryptographicException that names the issuer.

Check: The output is True.

Step 5: Validate the chain, including the post-quantum part

using var chain = new X509Chain();
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(root);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
 
bool valid = chain.Build(server, CryptoMode.Hybrid);
Console.WriteLine($"Chain valid: {valid}");
foreach (var element in chain.GetValidationReport().Elements)
{
    Console.WriteLine($"{element.Subject}: {element.PqSignatureStatus}");
}
 
File.WriteAllText("root.pem", root.ExportCertificatePem());
File.WriteAllText("server.pem", server.ExportCertificatePem());

Build with CryptoMode.Hybrid first runs the usual .NET validation and then verifies every ML-DSA signature in the chain. An invalid signature returns false, just as .NET does. chain.ChainStatus gives the classical reason, and GetValidationReport() gives the PQ result for each certificate.

Check: The output is:

Chain valid: True
CN=www.example.com: Valid
CN=Contoso Root CA: Valid

Step 6: Cross-check with a classical verifier

openssl verify -CAfile root.pem server.pem

OpenSSL verifies the RSA signatures and skips the noncritical extensions that hold the PQ part.

Check: The output is server.pem: OK.

Expected result

You now have two certificates that any existing software accepts as RSA certificates, and whose ML-DSA signatures the library also verifies. The private ML-DSA keys live only in the running process. To keep them, export the certificate as a PFX file.

Next steps

en/wiki/pqcrypt/lib/getting-started/first-hybrid-certificate-chain.txt · Zuletzt geändert: von Wolfgang van der Stille