Avatar

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:

  1. Classical ECDSA (today’s real-world posture), on stable strongSwan.
  2. 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_AUTH messages 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 --build flag is doing real work here: it rebuilds the helper from the ml-dsa branch, whose pki tool knows the mldsa44/65/87 key types. And here’s a genuinely nice property: we didn’t touch swanctl.conf at all. auth = pubkey is 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_AUTH carrying 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 run IKE_AUTH fit 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 40 for fragment errors, confirm fragmentation = yes on both ends, and stick with ml-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-dsa branch (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-sigs branch.
  • The IKEv2 wire format is still standardising: the IPSECME working group draft draft-ietf-ipsecme-ikev2-pqc-auth is 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!

Authors

Julio Gomez

Principal Engineer, AI & Quantum

Office of the CTO