This article is going to walk you through the steps needed to setup a simple web application that is fully protected by post-quantum cryptography (PQC). If you just want to test your connection to a PQC server you can go to mldsa.digicert.com with an appropriate client.
One of the most common PQC questions we receive from customers is about the difficulty in getting a PQC setup to work in general.
One way we’ve tried to answer that question is to provide a list of libraries that currently support PQC and talk about various standards related to PQC usage in transport layer security (TLS) and certificates. That answer usually falls pretty flat. Not because it’s a wrong answer, but because most of the customers we deal with aren’t focusing on low-level crypto libraries. They have web applications to serve, IoT devices to connect, or banks to run. Talking about ML-KEM and ML-DSA in OpenSSL, AWS-LC, BoringSSL, and BouncyCastle doesn’t map well to their products.
We want to provide a more high level example of what is required to get a PQC solution up and running. We have one example of how to get PQC working with MQTT here. For this article we thought we’d show another example of how easy it can be to get a full PQC solution working. In this case it’s going to be a web server using PQC key agreement and digital signatures. It's the same application running on mldsa.digicert.com.
Although many current Linux distributions already ship with web servers that are capable of using PQC without modifications we’re not going to use them. There are two reasons for this. One, most of our customers aren’t using these distributions, they are using long-term stable (LTS) versions that are running older software. Two, it would be too simple to warrant an article. For the sake of completeness we’ve listed the steps needed below, it’s wonderfully simple.
Since we’re serving a simple web page, you need all of the components you’d normally need to serve up a simple web page. There needs to be a computer somewhere to run your server, an operating system on said computer and you need to have a web server and it’s associated TLS provider. The concrete choices we made for this test are listed below. Note that there are a lot of different choices one can make when picking these components. It’s very flexible.
Compute - AWS Lightsail OS - Amazon Linux 2023 Server - nginx 1.29.3 TLS Library - OpenSSL 3.6 Client - Curl and/or Links
Since we need to do some cross-compilation we will also be using Docker.
To provide some insight and justify our choices we’ll give reasons for each. AWS Lightsail simplifies our compute setup substantially, and we already have an AWS setup. It also doesn’t support a lot of the “new and fancy” AMIs that EC2 supports. We are working in the same legacy systems our customers have. The choice of Amazon Linux 2023 is both a classic stable LTS and universally supported by AWS. If we wanted to throw this in a Lambda (we don’t), it would work there too.
We picked nginx for the simple fact that it’s the server we use internally for almost everything. OpenSSL 3.6 is used since it was the most recent release of OpenSSL. See the discussion for the reason a different crypto library wasn’t used.
As nice as it would be for our client to be Firefox or Chrome, they don’t support ML-DSA. So we use curl since curl is awesome and supports just about everything. Links is also an option since it actually renders the html file we send.
The part where we build things on Docker might not be needed if you are building on the machine you want to deploy on. For instance, if you are running this test in a VM.
We’ll walk through the 10-step process and describe what needs to be done in general. Following that, we give the concrete example commands to be run with our provided files. Obligatory note: don’t trust random files from the internet unless you read and understand them.
-
Setup a server that you will put nginx on.
For this we configure AWS Lightsail with the AWS CDK. It’s possible to configure Lightsail with the AWS console as well. -
Build a docker image.
The Dockerfile specifies the OS and installs the dependencies for building. -
Build OpenSSL 3.6
Install into a non-system directory to not interfere with the system OpenSSL. -
Build nginx 1.29.3
Build against OpenSSL from the previous step. -
Put nginx on a server you can access.
In our case deploy it to the Lightsail instance. -
Create ML-DSA certificates
We used Trust Lifecycle Manager to create root, intermediate, and leaf certificates. For testing you can use DigiCert LABS to create a test self-signed ML-DSA certificate. Note: If you want to create a test hierarchy send us an email at: pqc.labs@digicert.com -
Put the certs on the server
If you have a root, intermediate, and leaf they need to be concatenated in one file for nginx to consume them. The order of their concatenation is important:
Leaf Cert First
Followed by Intermediate
Optionally followed by the root.
For this example we are putting them in a mldsa.crt file. The private key goes in an mldsa.key file. -
Configure nginx to use the PQC
The areas of the configuration file to focus on are:# This tells you where your certs and key are located. ssl_certificate mldsa.crt; ssl_certificate_key mldsa.key; # Needed for PQC key agreement ssl_protocols TLSv1.3; ssl_ecdh_curve X25519MLKEM768; -
Add your app
We have a single index.html. -
Run the server.
Using the scripts provided in this repository and assuming your AWS account is set up properly, you need to run the following:
# If you aren't using lightsail with the AWS CDK skip the first two commands.
# You can setup a lightsail instance manually through the console as well.
# In your directory that has the AWS CDK
cd /dir/for/aws/cdk
cdk synth
cdk deploy
cd /script/directory
./build.sh
# Copy your mldsa certs and key or modify deploy.sh.
cp /dir/of/certs/mldsa.crt artifacts/conf
cp /dir/of/key/mldsa.key artifacts/conf
./deploy.sh
The 10-step process becomes even more simple if you already have an nginx instance running on a current Linux (Debian 13, Ubuntu 25) system:
- Create ML-DSA certificates.
- Put the certs on the server.
To connect to the server use a version of curl or links built with a recent version of OpenSSL (3.5+). If you are using curl you will need to pass in a --cacert file that contains the root. If you want to test it with our site, mldsa.digicert.com, you can get the root from the transaction if you know how, or from mldsa.root.pem.
The command I ran on MacOS 26 was:
/opt/homebrew/opt/curl/bin/curl --cacert mldsa.root.pem https://mldsa.digicert.com
Using curl you should be served the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ML-DSA Test Page</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', Courier, monospace;
background-color: #1a1a1a;
color: #e0e0e0;
min-height: 100vh;
display: flex;
flex-direction: column;
}
header {
padding: 2rem;
text-align: center;
}
main {
flex: 1;
padding: 2rem;
text-align: center;
}
footer {
padding: 1rem 2rem;
text-align: right;
}
</style>
</head>
<body>
<header>
<h1>
<pre>
███╗ ███╗██╗ ██████╗ ███████╗ █████╗
████╗ ████║██║ ██╔══██╗██╔════╝██╔══██╗
██╔████╔██║██║█████╗██║ ██║███████╗███████║
██║╚██╔╝██║██║╚════╝██║ ██║╚════██║██╔══██║
██║ ╚═╝ ██║███████╗ ██████╔╝███████║██║ ██║
╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝
</pre>Test Page</h1>
</header>
<main>
<p>This is a test page for ML-DSA.</p>
</main>
<footer>
digicert® LABS
</footer>
</body>
</html>
The main lesson learned was that it’s very easy to get ML-DSA certificates to work if you can control your version of nginx and OpenSSL.
There are concerns that the transimission overhead of ML-DSA certificates will cause issues for some systems. From our testing this wasn't an issue.
The main pain point for connecting with ML-DSA right now is it's lack of availability in root stores. A good solution to this issues is to start using ML-DSA in leaf and intermediate certificates. This would be a good start and we can respond to any issues that arise in an agile way that doesn’t impact root stores.
We had hypothesized that this wouldn’t be too difficult a task if we used OpenSSL as the crypto library. To make things more challenging, we initially tried to use AWS-LC instead. This didn’t work. Although AWS-LC has ML-DSA support, it doesn’t allow ML-DSA for TLS connections as of 1.65.0.
In the example connection command, keen eyes will notice that the homebrew version of curl was used. That’s because curl that comes with MacOS 26 uses LibreSSL, which does not have PQC support.
We learned that everything takes a bit more work on Windows at the moment. Waiting for native support is probably the best bet for most users. We didn’t investigate the Windows Insiders PQC support, but hopefully that will be the solution for our Windows customers.
A final observation is that you may run into conflicts with your corporate IT policies. One example we ran into was that links was not an approved browser for our system. As well, if you have a man in the middle box, this isn’t going to work.
