Skip to content

Authenticate with client certificates (EAP-TLS)

PEAP-MSCHAPv2 authenticates a username and password inside a TLS tunnel. That works, but the inner MS-CHAPv2 primitive is weak, and the whole thing rests on the tunnel. EAP-TLS removes the password entirely: the endpoint presents a client certificate, the server verifies it against a trust anchor, and the authenticated identity is the certificate’s subject. If you can issue client certificates to your endpoints, this is the stronger method.

l2trace runs EAP-TLS as a peer of PEAP on the same RADIUS server. You do not choose per port; the server offers a method and a supplicant configured for EAP-TLS asks for it (via a Legacy Nak), and the server switches. Nothing else about the setup changes.

The one thing that makes EAP-TLS meaningful is the trust anchor: the CA a client certificate must chain to. l2trace will not run EAP-TLS without one. A server that verified nothing would complete every handshake and authenticate anyone, so if RADIUS_EAP_TLS_CLIENT_CA is unset, EAP-TLS is simply off, and if it is set to a CA, only certificates that chain to it are accepted.

For a lab, scripts/gen_lab_ca.py mints a throwaway CA, the l2trace server leaf, a client leaf, and an empty CRL, and prints which file maps to which setting:

Terminal window
uv run python scripts/gen_lab_ca.py --force --hostname radius.lab.example --ip 127.0.0.1
FileUsed as
ca.crtRADIUS_EAP_TLS_CLIENT_CA (the trust anchor)
server.crt / server.keyRADIUS_TLS_CERT / RADIUS_TLS_KEY (the server leaf)
client.crt / client.keythe endpoint’s EAP-TLS credential
crl.pemRADIUS_EAP_TLS_CRL (optional revocation list)

For production, issue these from your own PKI. Nothing about the lab script is fit for production; it exists to prove the flow.

Set the trust anchor (and, optionally, a CRL) in the server’s environment:

RADIUS_EAP_TLS_CLIENT_CA=/path/to/ca.crt
RADIUS_EAP_TLS_CRL=/path/to/crl.pem # optional, see revocation below

The server leaf it presents inside the tunnel is the same RADIUS_TLS_CERT / RADIUS_TLS_KEY PEAP already uses.

The switch or access point config is ordinary 802.1X, the same RADIUS server, the same shared secret. The only change is on the endpoint: its supplicant is configured for EAP-TLS with the client certificate and key, not a username and password. When it Naks the server’s first offer and asks for EAP-TLS, the server hands the conversation to its EAP-TLS state machine automatically.

On success the auth_event records the identity taken from the certificate subject, plus a credential note naming the certificate’s issuer and serial. That note is what you need to revoke the exact credential that got on the network, without it, “a cert authenticated” is not actionable.

Set RADIUS_EAP_TLS_CRL to a PEM CRL and l2trace checks the client’s leaf against it: a revoked certificate then fails the handshake exactly like an untrusted one.