In Part 7 we minted post-quantum certificates and weighed them. But certificates sitting in a folder are only half the fun. Time to do the job they were born for: prove identity. We’ll stand up two containers, each holding a certificate, and watch them prove who they are to each other before the tunnel comes up. That’s mutual authentication, over a real IKEv2 handshake.
And we’ll do it twice, and the progression is the whole point:
- Classical ECDSA (today’s real-world posture), on stable strongSwan.
- Post-quantum ML-DSA (the bleeding edge), on an experimental branch.
This Part uses the authentication/ lab: its own little stack, separate from the key-exchange one. Only Docker required.
Clone the repo
Grab the repo and step into this lab’s directory. All commands below run from ipsec/authentication/:
git clone https://github.com/juliogomez/pqc.git
cd ipsec/pqc/authentication
How the trust works
Both peers trust one tiny Certificate Authority we spin up just for the lab. The CA signs two leaf certificates (one per peer), and each peer gets the CA cert pre-installed so it can verify the other side. During the handshake each peer sends only its own leaf cert; the CA is already known to both. (That keeps the on-the-wire bytes down, which matters a lot once the certs go post-quantum, as Part 7 showed so clearly.) A helper script, gen-certs.sh, does all the minting; you just tell it which algorithm to use.
Exercise A: Classical mutual auth with ECDSA
This is today’s real-world posture, and there’s a nice little detail in it. Bring up the two peers:
docker compose up -d --build
That starts ike-auth-initiator (172.21.0.2) and ike-auth-responder (172.21.0.3). Now mint the CA and both ECDSA leaf certs:
docker compose run --rm --build certgen ecdsa
The peers started before the certs existed, so reload credentials. Reload the responder via a one-shot exec, then drop into the initiator for the rest:
docker exec ike-auth-responder swanctl --load-all
docker exec -it ike-auth-initiator bash
swanctl --load-all
Confirm the initiator picked up its own cert:
swanctl --list-certs --utc | head -n 15
Two tells: the subject/altNames match this peer’s identity (CN=initiator.pqc.lab), and the pubkey line ends with , has private key, e.g. pubkey: ECDSA 256 bits, has private key. That suffix means it’s the peer’s own leaf, not just a cert it learned about.
Now bring up the tunnel:
swanctl --initiate --child auth-child
The line to watch for, proof each side verified the other’s certificate against the shared CA:
[IKE] authentication of 'responder.pqc.lab' with ECDSA_WITH_SHA256_DER successful
Inspect the result with swanctl --list-sas, and read the proposal line carefully:
auth-tunnel: #1, ESTABLISHED, IKEv2, ...
local 'initiator.pqc.lab' @ 172.21.0.2[4500]
remote 'responder.pqc.lab' @ 172.21.0.3[4500]
AES_GCM_16-256/PRF_HMAC_SHA2_256/CURVE_25519/KE1_ML_KEM_768
Spot the beautiful detail? CURVE_25519/KE1_ML_KEM_768: the key exchange is already post-quantum hybrid (our friend from Parts 3–4!), while the identities were proven with classical ECDSA certificates. That’s exactly the posture real deployments ship right now: quantum-safe key exchange, classical authentication.
So the key exchange is future-proof, but authentication is still classical. Let’s fix that second half.
exit
Exercise B: Post-quantum auth with ML-DSA
Time to make the signature quantum-safe too. This means strongSwan’s experimental ml-dsa branch and ML-DSA certificates.
Expectation setting: this is genuinely experimental. The branch changes often, the IKEv2-for-PQC-auth wire format is still an IETF draft, and there are known sharp edges (large
IKE_AUTHmessages that split into many fragments have hit reassembly bugs; see strongSwan issue #2889). To stay on the happy path we use ML-DSA-44 (the smallest variant) and keep the CA off the wire, holding the handshake to 6 fragments. In our testing the tunnel comes up cleanly. Push to bigger variants or longer chains and you can tip it over; that’s the bleeding edge doing bleeding-edge things.
Rebuild the peers from the ml-dsa branch (the override points the build at Dockerfile.mldsa):
docker compose -f docker-compose.yml -f docker-compose.mldsa.yml up -d --build
Reissue the certs as ML-DSA-44, same helper, different algorithm:
docker compose -f docker-compose.yml -f docker-compose.mldsa.yml run --rm --build certgen ml-dsa-44
The
--buildflag is doing real work here: it rebuilds the helper from theml-dsabranch, whosepkitool knows themldsa44/65/87key types. And here’s a genuinely nice property: we didn’t touchswanctl.confat all.auth = pubkeyis algorithm-agnostic, so strongSwan figures out it’s ML-DSA straight from the certificate’s key type. PQC auth slots right into the existing framework.
Reload and initiate (fresh shells, since the rebuild recreated the containers):
docker exec ike-auth-responder swanctl --load-all
docker exec -it ike-auth-initiator bash
swanctl --load-all # expect: loaded ML_DSA_44 key from '...'
swanctl --initiate --child auth-child
The line that proves we’ve gone fully post-quantum:
[IKE] authentication of 'responder.pqc.lab' with ML_DSA_44 successful
Check swanctl --list-sas and you’ll see the same tunnel as before, except the identities were now proven with ML-DSA signatures, over an ML-KEM key exchange:
AES_GCM_16-256/PRF_HMAC_SHA2_256/CURVE_25519/KE1_ML_KEM_768
Take a second to appreciate that: you just brought up a VPN tunnel with nothing in the handshake a quantum computer could break. Both pillars, quantum-safe, on your laptop. SO cool!
exit
Watch the certs blow it up
This is the payoff of the whole authentication pillar. Look at the initiator’s log to see which messages had to be fragmented:
docker logs ike-auth-initiator | grep -iE "splitting IKE message"
[ENC] splitting IKE message (1249 bytes) into 2 fragments
[ENC] splitting IKE message (6904 bytes) into 6 fragments
Two different post-quantum payloads straining the MTU:
- The ~1.2 KB → 2 fragments is the ML-KEM key exchange (in
IKE_INTERMEDIATE). This shows up in the ECDSA run too; key exchange is post-quantum either way. - The ~6.9 KB → 6 fragments is the
IKE_AUTHcarrying the ML-DSA leaf cert plus signature. This split is unique to the post-quantum-auth run: an ML-DSA-44 leaf (~4 KB DER) plus its signature dwarfs the ~400-byte ECDSA equivalent. For comparison, in the ECDSA runIKE_AUTHfit in a single ~900-byte packet and never split.
There it is: the size explosion from Part 7, now turned into real fragments on a real handshake. This is why fragmentation = yes is non-negotiable for PQC auth, and exactly where the current bugs live.
If it doesn’t come up: ML-DSA-44 establishes cleanly in our testing, but bump to ML-DSA-65/87 or add an intermediate CA and you can push the fragment count into the territory of the known reassembly bug (#2889). If a run hangs, check
docker logs ike-auth-responder | tail -n 40for fragment errors, confirmfragmentation = yeson both ends, and stick withml-dsa-44. The goal isn’t a production tunnel; it’s standing at the bleeding edge and seeing exactly where it bends.
Cleanup
docker compose down
# Remove the generated CA + leaf keys/certs (written to host dirs via bind mounts).
# These include private keys; .gitignore keeps them out of commits, but they sit on disk.
rm -rf config/initiator/private config/initiator/x509 config/initiator/x509ca \
config/responder/private config/responder/x509 config/responder/x509ca
Where IKEv2 authentication actually stands today
“Can I authenticate my strongSwan VPN with ML-DSA today?” You just did, but with a big asterisk: only on an experimental branch, not a stable release. Honest lay of the land:
- strongSwan’s ML-DSA support lives on the
ml-dsabranch (PR #2626), the branch our ML-DSA run built from, not in the 6.0.x stable line. - Composite/hybrid authentication is separate again, being developed on the
pq-composite-sigsbranch. - The IKEv2 wire format is still standardising: the IPSECME working group draft
draft-ietf-ipsecme-ikev2-pqc-authis still evolving, and strongSwan’s implementation differs from it in places. Expect details to shift before this stabilises.
So unlike the key-exchange story (where ML-KEM ships in stable strongSwan and just works), post-quantum authentication in IKEv2 is still emerging. That’s not a gap in the lab; it’s the honest state of the world. And it’s exactly why getting hands-on with the building blocks now (the keys, certs, signatures, and that experimental tunnel) is the most useful thing you can do. When the IKE plumbing lands in a stable release, you’ll already get it, and you’ll have run it before most people knew it was possible.
Look how far we’ve come in this blog series
Across both pillars you’ve:
- Understood the two pillars of a secure handshake and the two very different quantum deadlines they face.
- Made a key exchange quantum-safe with ML-KEM, captured the hybrid handshake, and proved the cost is just a couple of KB and one round trip.
- Seen a second road to the same safety with a post-quantum preshared key.
- Weighed the certificate size explosion that defines post-quantum authentication.
- Mutually authenticated a real VPN: classical ECDSA, then bleeding-edge ML-DSA over an ML-KEM key exchange.
You’ve touched every moving part of post-quantum cryptography for VPNs, and you did it on your own laptop. The headlines about quantum computers breaking the internet? You now know exactly which parts are urgent, which are a slow rebuild, what the fixes look like, and how to run them yourself.
Clone it, break it, rerun it, and go explore your own integrations and use cases. Seriously, well done. Now go quantum-proof something!