July 20th, 2025
More and more organizations
are moving towards adopting
post-quantum cryptography (PQC). In order to
verify whether your systems, clients, libraries, and
toolkits are able to speak PQC, it's useful to have
sample implementations that provide the bare minimum
functionality to let you validate compatibility. For
the most part, the current focus for many is primarily
on hybrid
key exchange in TLS 1.3 using X25519MLKEM768, and so I had a
need to determine just what that looks like in
practice.
To this end, I stood up a few HTTP endpoints that speak PQC using different implementations:
- http://boringssl-nginx.pqc.dotwtf.wtf/
- https://golang.pqc.dotwtf.wtf
- https://java-bc.pqc.dotwtf.wtf
- https://openssl-nginx.pqc.dotwtf.wtf
- https://openssl-oqs-apache.pqc.dotwtf.wtf
- https://wolfssl-nginx.pqc.dotwtf.wtf
The names should be reasonably self-explanatory.
Where possible, I tried to extract the negotiated
group1, HTTP protocol spoken, and
whether or not a HelloRetryRequest (HRR) was
performed. However, not all implementations or
servers provide that level of detail.
A few notes on each of the endpoints:
boringssl-nginx
For this service, I built BoringSSL
following these
instructions using -DBUILD_SHARED_LIBS=1, then built
nginx as described
here.
To reflect the negotiated group and other TLS parameters, I use a simple PHP script with variables exposed as FastCGI parameters. The nginx config file can be found here.
golang
For Go, I wrote a simple TLS server myself, code here.
The one thing of interest here is that as of Go 1.24, the negotiated group is not properly exposed, and I'm extracting it from the stringified object representation. Likewise for the HRR determination. It's ugly.
The good news is that Go 1.25 will
get ConnectionState.CurveID exposed,
and there's no opposition to further exposing
HRR in the future.
java-bc
This is another simple proof-of-concept server, this time written in Java. In order to get hybrid TLS key exchange with PQC, this requires the use of BouncyCastle version >= 1.81.
Writing this was a pain, because I normally don't write Java. I was unable to find a way to get the negotiated group from the connection without writing my own TLS stack here. After fighting for a few hours with AI that kept trying to convince me to use non-existent functions, I eventually gave up.
The endpoint at https://java-bc.pqc.dotwtf.wtf currently only offers PQC, so if you're trying to hit that with, e.g., Safari or any other client not linked against a PQC enabled TLS library, you should get an error. (By only supporting a single key group, I can also fake HRR detection, but that is, of course, a bit silly.) It also only speaks HTTP/1.x. And by "speak", I mean: "fake".
Anyway, the code is here; I included a client PoC there as well.
openssl-nginx
This endpoint uses OpenSSL 3.5 with nginx, pretty much following this write-up.
openssl-oqs-apache
This endpoint is running on the same server as this website, using 's OpenSSL oqs-provider.
For server side detection of the negotiated group, I'm cheating a bit: since I didn't see a good way to expose the TLS details from Apache, I'm using the same approach that Cloudflare uses for their https://pq.cloudflareresearch.com/ endpoint: the client capabilities are determined by fetching a resource from another endpoint via JavaScript and then parsing the response. (In my case, I'm simply hitting my own Golang endpoint; Cloudflare fetches https://pq.cloudflareresearch.com/cdn-cgi/trace.)
This isn't quite accurate: I can't determine what HTTP protocol the client used for this server, nor whether it required an HRR, and of course it's possible that the client didn't use PQC to this server but did use it for the async fetch, but ¯\_(ツ)_/¯.
wolfssl-nginx
This endpoint uses WolfSSL. I downloaded the wolfssl-5.8.2 tarball from their website (note: you do not have to provide your personal information), and then used the "wolfssl-nginx" patcheѕ found here to patch nginx.
Unfortunately, the patches there are only for very old versions of nginx, so I tried to apply them to nginx-1.28.0. That worked for the most part, but needed a few additional tweaks and still failed to compile due to some issues relating to Quic and CRLs; since I don't need those for this PoC, I just patched the code to drop the functionality in question altogether.
The other things that is worth noting here is that with WolfSSL, nginx doesn't let me select the key groups / curves to configure, for which I opened an issue. As a result, nginx can't dynamically reflect the negotiated group, either. Other than that, though, it appears to work.
Client verification
In addition to the server examples and the Java
client noted above, I also have a trivial OpenSSL client
to illustrate the use of the SSL_CTX_set1_groups_list
library function. Other options include BoringSSL's
bssl command, or curl(1), if linked against a PQC
enabled TLS library:
$ java -cp "./bc/*:." PQTlsClient golang.pqc.dotwtf.wtf | grep "<br>"
HTTP Version: HTTP/1.1<br>
Protocol: TLS 1.3<br>
TLS HRR: false<br>
Cipher: TLS_AES_128_GCM_SHA256<br>
Named Group: X25519MLKEM768<br>
$ </dev/null bssl client -curves X25519MLKEM768 -connect golang.pqc.dotwtf.wtf:443
Version: TLSv1.3
Resumed session: no
Cipher: TLS_AES_128_GCM_SHA256
ECDHE group: X25519MLKEM768
[...]
$ curl --curves X25519MLKEM768 https://golang.pqc.dotwtf.wtf/ | grep "<br>"
HTTP Version: HTTP/2.0<br>
Protocol: TLS 1.3<br>
TLS HRR: true<br>
Cipher: TLS_AES_128_GCM_SHA256<br>
Named Group: X25519MLKEM768<br>
$ Anyway, I hope that these example are useful for others. Obligatory "PRs welcome!" here: https://github.com/jschauma/pqcpoc/
July 20th, 2025
Footnotes:
[1] This is often times referred to as the negotiated curve (for, well, Elliptic Curve cryptography). In TLS 1.3, this was renamed to the support (and then: negotiated) group to also refer to a potential finite field group. With hybrid PQC key exchange, this is the only place where we can identify the X25519MLKEM768 key group.↩
Links:
- Post-Quantum Certificates
- Post-Quantum Cryptography on NetBSD (2025-05-02)
- Sites using PQC (March 2025) (2025-03-24)
- Post-Quantum Cryptography in February 2025 (2025-02-19)
- TLS 1.3 Hybrid Key Exchange using X25519Kyber768 / ML-KEM (2024-10-31)
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.