Goal: Configure the FreeRADIUS server to authenticate users using EAP-PEAP, then verify it works by sending and receiving test packets.
Time: 20–35 minutes.
Files:
-
mods-available/eap— controls which EAP methods are enabled -
mods-config/files/authorize— the flat file where test users likebobare defined -
sites-available/inner-tunnel— the virtual server that handles Phase 2 authentication and looks up the user -
eapol_test/peap-mschapv2.conf— configuration for the test client
EAP-PEAP protects user credentials by wrapping the login exchange inside an encrypted TLS tunnel. Think of it like sending a letter inside a locked box — even if someone intercepts it, they cannot read what is inside.
Diagram:
-
Phase 1 — The server presents its TLS certificate and a secure encrypted tunnel is created between the client and the server. At this point, only the anonymous identity (e.g.
anonymous@example.org) is visible on the network. The real username is hidden. -
Phase 2 — Inside that tunnel, the client sends the real username and password using EAP-MSCHAPv2. The server then looks up the user in its user database, checks the password, and either accepts or rejects the request.
The main advantage over EAP-TLS is that the client does not need a certificate — only the server does. This makes deployment much simpler in large environments.
Understanding where bob gets looked up helps you troubleshoot problems and extend the configuration later.
When a PEAP authentication request arrives, FreeRADIUS processes it in two separate stages:
Stage 1 — Outer request (Phase 1):
The server only sees the anonymous identity (e.g. anonymous@example.org). This is handled by the default virtual server in sites-available/default. No user lookup happens here — the server simply establishes the TLS tunnel and passes control to Stage 2.
Stage 2 — Inner tunnel request (Phase 2):
Once the TLS tunnel is established, the real username bob is decrypted from inside the tunnel. This inner request is handled by a separate virtual server defined in sites-available/inner-tunnel.
Inside the inner-tunnel virtual server, FreeRADIUS runs through the authorize section, which calls the files module. The files module reads mods-config/files/authorize line by line, looking for a matching username. When it finds bob, it retrieves the stored password and passes it to the mschapv2 module for verification.
-
FreeRADIUS automatically generates the server certificates needed for PEAP when it first starts. You do not need to create them manually.
-
You need both the
mschapmodule and themschapv2module enabled. Themschapmodule handles MSCHAP authentication in general, whilemschapv2specifically handles the EAP-MSCHAPv2 exchange inside the PEAP tunnel. Both are required. -
Make sure the
inner-tunnelvirtual server is enabled. This is what processes the real username and password inside the tunnel.
Open raddb/mods-config/files/authorize and add the following line:
bob Password.Cleartext := "hello"Verify the following entry added.
authenticate mschap {
mschap
}This tells FreeRADIUS that when a request arrives for user bob inside the inner tunnel, the expected cleartext password is hello. The files module reads this file top to bottom and stops at the first match. If you have multiple users, each goes on its own line.
Open sites-available/inner-tunnel and confirm the authorize and authenticate sections look like this:
recv Access-Request {
mschap
eap {
ok = return
}
files
}The files entry in authorize is what triggers the lookup of bob in mods-config/files/authorize. If files is missing here, the server will never find the user and authentication will always fail.
Ensure the inner-tunnel site is enabled by checking that the symlink exists:
$ ls sites-enabled/inner-tunnelIf it does not exist, create it:
$ ln -s ../sites-available/inner-tunnel sites-enabled/inner-tunnelOpen src/tests/eapol_test/peap-mschapv2.conf and update it with the following:
network={
ssid="example"
key_mgmt=WPA-EAP
eap=PEAP
identity="bob@example.org"
anonymous_identity="anonymous@example.org"
password="hello"
phase2="auth=MSCHAPV2"
phase1="peapver=0"
}Notice that both identity and anonymous_identity are set. The anonymous_identity is what gets sent in Phase 1 and is visible on the network. The real identity (bob) is only sent inside the encrypted tunnel in Phase 2. The server looks up bob — not anonymous@example.org — when checking the password.
Start FreeRADIUS in debug mode so you can watch every step of the authentication:
$ radiusd -XOpen a second terminal and run:
./eapol_test -c peap-mschapv2.conf -a 127.0.0.1 -p 1812 -s testing123 -nThe client and server exchange several packets back and forth as they negotiate the TLS tunnel and verify credentials. This is normal and takes a few seconds.
A successful authentication ends with an Access-Accept containing these two keys, which are used to encrypt wireless traffic:
-
MS-MPPE-Recv-Key -
MS-MPPE-Send-Key
If you see Access-Reject instead, the most likely causes are:
-
bobis not inmods-config/files/authorize, or the username/password has a typo -
The
filesmodule is missing from theauthorizesection ofinner-tunnel -
The
mschapv2module is not listed in theauthenticatesection ofinner-tunnel
Phase 1 — The anonymous identity is sent to start the TLS handshake. Note that bob is not visible here:
RADIUS message: code=1 (Access-Request) identifier=0 length=148
Attribute 1 (User-Name) length=23
Value: 'anonymous@example.org'The TLS tunnel is being negotiated:
EAP-PEAP: Start (server ver=0, own ver=0)
SSL: SSL_connect:SSLv3/TLS write client hello
SSL: SSL_connect:SSLv3/TLS read server hello
TLS: Trusted root certificate(s) loaded
EAP: Status notification: remote certificate verification (param=success)The tunnel is ready. Phase 2 begins and bob is sent inside the tunnel:
OpenSSL: Handshake finished - resumed=0
EAP-PEAP: TLS done, proceed to Phase 2
EAP-PEAP: Phase 2 EAP-MSCHAPv2 Request
EAP-PEAP: Encrypting Phase 2 data
EAP-PEAP: Authentication completed successfullyFinal confirmation:
MPPE keys OK: 0 mismatch: 0
SUCCESSPhase 1 — The server receives the outer request. Only the anonymous identity is visible:
Received Access-Request ID 0
User-Name = "anonymous@example.org"
eap - Calling submodule eap_peap
eap.peap - Initiating new TLS session
Sending Access-ChallengeThe TLS tunnel is established after several handshake rounds:
Handshake state - SSL negotiation finished successfully (1)
Cipher suite: ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2Phase 2 — The inner-tunnel virtual server now handles the real username bob. This is where the files module looks up bob in mods-config/files/authorize:
eap.peap - Session established. Decoding inner EAP attributes
eap.peap - Running request through virtual server "inner-tunnel"
User-Name = "bob"
MS-CHAP-Response = 0x...
MS-CHAP-Challenge = 0x...
files - Looking for key "bob"
files - Found entry for "bob"
files - Password.Cleartext := "hello"
mschap - User authenticated successfullyThe server sends back an Access-Accept with the session keys:
Vendor-Specific {
Microsoft {
MPPE-Recv-Key = 0x13cb974fc....
MPPE-Send-Key = 0x7ad0412ba....
}
}
Packet-Type = ::Access-Accept
User-Name = "anonymous@example.org"Notice that the User-Name in the Access-Accept is the anonymous identity, not bob. This is by design — the outer identity is used for the RADIUS session, while the inner identity is only used for the actual credential check inside the tunnel.
Once you see Access-Accept on the server and SUCCESS on the client, EAP-PEAP is working correctly. You can verify that a real wireless client has network access using ping.
Different wireless clients may implement different tunneled authentication protocols inside EAP-PEAP. Not all of these are compatible with every RADIUS server. FreeRADIUS has only been tested using EAP-MSCHAPv2 as the inner tunneled protocol. If you need to use a different inner protocol, be aware that source code changes to FreeRADIUS may be required to support it.
If EAP-PEAP is not working on a Windows client, you can enable detailed tracing logs to help identify the problem. Add the following registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Tracing\RASTLS\EnableTracingSet the value of that key to 1. Windows will then create a file called RASTLS.LOG containing detailed tracing information about the EAP negotiation. This log is one of the most useful tools for diagnosing Windows-specific PEAP failures, as it shows exactly where the handshake breaks down on the client side.
To read the log, open it in any text editor and look for lines containing ERROR or FAIL. These will point you directly to the step that is failing, such as a certificate validation error or an unsupported inner method.