HOWTO: WPA2-Enterprise with FreeRadius

In this tutorial you will be able to successfully set up a home wireless network using FreeBSD and WPA2-Enterprise option on your wireless router. Notably, you will be able to use certificate based authentication to secure communication between your devices.

There are a couple of solid tutorials out there which show you how to do this (just google). Unfortunately I wasn't able to successfully set up a network following a single one only, and most of them seem to miss some critical information, notably, how to set up certificate revocation list.
So, I will do a small write-up on how I managed to do it (i.e. an amalgam of all them together).

There are no real reasons to set up a certificate based home network - a strong WPA2 password on your router will do as much for security with no hassle whatsoever. This is overkill if there was ever one. On the other hand, if you think this is cool and want this kind of security in your network, then you might benefit from this tutorial.

In this setup you will have wireless devices authenticate via wireless router to FreeBSD server running FreeRadius.

Before you begin, make sure your router is able to pass authentication requests with WPA2-Enterprise option. Most if not all new routers should have this option.

All of this is done on FreeBSD 9-RC3, but it can be done on earlier versions provided openssl and freeradius2 implementations don't differ (that, you will have to check).
 
SETTING UP KEYS
---------------------

I will use openssl to create three pieces: CA, server key pair, and clients' key pairs.

Make a directory where you will be generating keys. I used root's home dir, but you can use any:

Code:
cd ~
mkdir .certs
mkdir .certs/CA
mkdir .certs/CA/private
mkdir .certs/new
mkdir .certs/crl
mkdir .certs/export

Then copy openssl config file to this dir:
Code:
cd .certs
cp /etc/ssl/openssl.cfg .

Open this copied file and edit following lines:

Code:
...
 36 [ CA_default ]
 37
 38 dir             = /root/.certs          # Where everything is kept
 39 certs           = $dir                  # Where the issued certs are kept
 40 crl_dir         = $dir/crl              # Where the issued crl are kept
 41 database        = $dir/index.txt        # database index file.
 42 #unique_subject = no                    # Set to 'no' to allow creation of
 43                                         # several ctificates with same subject.
 44 new_certs_dir   = $dir/new              # default place for new certs.
 45
 46 certificate     = $dir/CA/cacert.pem    # The CA certificate
 47 serial          = $dir/serial           # The current serial number
 48 crlnumber       = $dir/crlnumber        # the current crl number
 49                                         # must be commented out to leave a V1 CRL
 50 crl             = $dir/crl.pem          # The current CRL
 51 private_key     = $dir/CA/private/cakey.pem# The private key
 52 RANDFILE        = $dir/private/.rand    # private random number file
...
 101 [ req ]
 102 default_bits = 2048

At the end of file add lines for Windows compatibility:

Code:
...
315 [ xpclient_ext ]
316 extendedKeyUsage = 1.3.6.1.5.5.7.3.2
317
318 [ xpserver_ext ] 
319 extendedKeyUsage = 1.3.6.1.5.5.7.3.1

Create index file which will keep list of all issued/revoked certs:

# touch index.txt

Optionally create serial file to specify starting numbering for new certificates:

# echo '1001 ' > serial

Now, we will create CA we need to sign other public keys:

# openssl req -new -x509 -extensions v3_ca -keyout CA/private/cakey.pem -out CA/cacert.pem -config ./openssl.cnf

You will be prompted for password for private key. You will need this password every time you want to sign other keys. You will also be presented with a series of different entries which will be saved in certificate. Most of these are not important, just use whatever you think it's appropriate. When asked for "Common Name" enter "CA". You should use different Common Name for each certificate in the future (eg. server, FreeBSD_laptop, etc).

You have just created cacert.pem and cakey.pem.

Windows platform can't read certificates in .pem format. Windows needs .der format for CA. To "export" out our cacert.pem use:

# openssl x509 -inform PEM -outform DER -in CA/cacert.pem -out export/cacert.der

Additionally, my phone can only read certificates in .p12 format, so we again export .pem, now to .p12:

# openssl pkcs12 -export -in CA/cacert.pem -inkey CA/private/cakey.pem -out export/cacert.p12 -cacerts

When converting to .p12 format, you will be prompted for CA private key password you already set up in previous step, and then asked to set export password which is password you will enter on device (in my case phone) when installing CA.
These are just different format containers that have same information as original .pem file.
Remember we do this because all clients need access to CA in order to compare the validity of public key signatures.

Next, create server keys:

# openssl req -new -keyout new/server_key.pem -out new/server_req.pem -config openssl.cnf -nodes

Sign and create certificate:

# openssl ca -out new/server_cert.pem -infiles new/server_req.pem -config ./openssl.cnf -extensions xpserver_ext

Next, let's create two key pairs for out FreeBSD and Windows7 hosts:

# openssl req -new -keyout new/FreeBSD_laptop_key.pem -out new/FreeBSD_laptop_req.pem -config ./openssl.cnf
# openssl req -new -keyout new/Windows_laptop_key.pem -out new/Windows_laptop_req.pem -config ./openssl.cnf

Again, to make these public keys valid certificates we sign them with CA:

# openssl ca -out new/FreeBSD_laptop_cert.pem -infiles new/FreeBSD_laptop_req.pem -config ./openssl.cnf
# openssl ca -out new/Windows_laptop_cert.pem -infiles new/Windows_laptop_req.pem -config ./openssl.cnf -extensions xpclient_ext

Notice additional x509 extension flag we used for windows based host key, as well as for server key.

Additionally, for windows host we need to export client certificate to .p12 format:

# openssl pkcs12 -export -in newcerts/Windows_laptop_cert.pem -inkey newcerts/Windows_laptop_key.pem -out export/Windows_laptop_cert.p12 -clcerts

You will be prompted for password used to create this host's key pair, and then you will setup export password you'll need when you install this certificate on windows host. You don't need to enter password, but it's recommended you do; you'll only need to enter it once, when you install keys.

Note that I didn't create a key pair for my phone since it doesn't support EAP-TLS and client certificates. It will use other password-based authentication, but it will still need CA in .p12 format.

Certificate Revocation List
--------------------------------

If you ever need to revoke a certificate before it expires by itself (and the way I created all certificates and CA will expire in one year from moment they are created), you need to let radius server known where to look for. After lots of digging I managed to find solution described here, as the documentation on this is lacking.

At this point you don't have any certificates you need to revoke, but create the list anyway, it will make sense later. To create the revocation list use:

# openssl ca -gencrl -keyfile CA/private/cacert.key -cert CA/cacert.pem -out crl/crl.pem -config ./openssl.cnf


Copy keys and certs
-------------------------

Copy server keys, CA, and crl list to new location (I like /var/db/certs):

# mkdir /var/db/certs
# cp CA/cacert.pem new/server_cert.pem new/server_key.pem crl/crl.pem /var/db/certs

Finally, create new file which will hold both CA and revoked certificates:

# cd /var/db/certs
# cat cacert.pem crl.pem > cacrl.pem


Copy cacert.der and Windows_laptop_cert.p12 from ~/.certs/export directory to your Windows laptop.
Copy cacert.pem, FreeBSD_laptop_cert.pem and FreeBSD_laptop_key.pem to your FreeBSD laptop (or Mac, it's same format).
Additionally, I copy cacert.p12 to my phone.
 
INSTALLING RADIUS
------------------------

Use ports to compile from sources:

# cd /usr/ports/net/freeradius2 && make install clean

or, download from packages:

# pkg_add -r freeradius2

Make copy of installation dir in case you need to revert to original configuration files:

# cp -Rvp /usr/local/etc/raddb /usr/local/etc/raddb.orig

Finally, create Diffie-Hellman and random crypto nounce file, both needed for secure communication:

# cd /var/db/certs
# dd if=/dev/urandom of=random count=2
# openssl dhparam -check -text -5 1024 -out dh


Configuring radius
------------------

Default options for radiusd.conf are ok. The ones I changed myself:

Make radius listen on specific address only:

Code:
...
273 ipaddr = 192.168.2.254
...
316 ipaddr = 192.168.2.254
...

Increase number of potential clients (n * 256):

Code:
...
224 max_requests = 2560
...

Number of seconds trying to authenticate client:

Code:
...
186 max_request_time = 30
...

Next, open up clients.conf and add at the end:

Code:
client 192.168.2.2 {
        secret = super_secret_sauce
        shortname = MyWireless_AP
        nastype = other
}

A radius "client" is your router which will pass authentication requests to server. Client IP is therefore IP of your router; a "secret" is password you will enter on your router; "shortname" is for logging, and leave your router as "nastype" other, appropriately.

Open up users file and add at the end:

Code:
FreeBSD_laptop
Windows_laptop
MyPhone         Cleartext-Password := "PHEr33k#@_!.sS2!$"

DEFAULT         Auth-type := Reject
                Reply-Message := "Weak sauce, try harder..."

Users are actual clients.Server is smart enough to figure out what method client is using to authenticate. In first two cases clients are using certificates so there's no need to add anything. Last client uses password based authentication.

Finally, open eap.conf:
Code:
...
17         eap { 
18                 #  Invoke the default supported EAP type when
19                 #  EAP-Identity response is received.
20                 #
21                 #  The incoming EAP messages DO NOT specify which EAP
22                 #  type they will be using, so it MUST be set here.
23                 #
24                 #  For now, only one default EAP type may be used at a time.
25                 #
26                 #  If the EAP-Type attribute is set by another module,
27                 #  then that EAP type takes precedence over the
28                 #  default type configured here.
29                 #
30                 default_eap_type = [B]tls[/B]
...
151                 tls {
152                         #
153                         #  These is used to simplify later configurations.
154                         #
155                         certdir = [B]/var/db/certs[/B]
156                         cadir = [B]/var/db/certs[/B]
157
158                         #private_key_password = whatever
159                         private_key_file = [B]${certdir}/server_key.pem[/B]
160
161                         #  If Private key & Certificate are located in
162                         #  the same file, then private_key_file &
163                         #  certificate_file must contain the same file
164                         #  name.
165                         #
166                         #  If CA_file (below) is not used, then the
167                         #  certificate_file below MUST include not
168                         #  only the server certificate, but ALSO all
169                         #  of the CA certificates used to sign the
170                         #  server certificate.
171                         certificate_file = [B]${certdir}/server_cert.pem[/B]
172
173                         #  Trusted Root CA list
174                         #
175                         #  ALL of the CA's in this list will be trusted
176                         #  to issue client certificates for authentication.
177                         #
178                         #  In general, you should use self-signed
179                         #  certificates for 802.1x (EAP) authentication.
180                         #  In that case, this CA file should contain
181                         #  *one* CA certificate.
182                         #
183                         #  This parameter is used only for EAP-TLS,
184                         #  when you issue client certificates.  If you do
185                         #  not use client certificates, and you do not want
186                         #  to permit EAP-TLS authentication, then delete
187                         #  this configuration item.
188                         CA_file = [B]${cadir}/cacrl.pem[/B]
189
190                         #
191                         #  For DH cipher suites to work, you have to
192                         #  run OpenSSL to create the DH file first:
193                         #
194                         #       openssl dhparam -out certs/dh 1024
195                         #
196                         dh_file = [B]${certdir}/dh[/B]
197                         random_file = [B]${certdir}/random[/B]
...
227                         [B]check_crl = yes[/B]
...
262                         # Set this option to specify the allowed
263                         # TLS cipher suites.  The format is listed
264                         # in "man 1 ciphers".
265                         [B]cipher_list = "HIGH"[/B]
...

Notice line 188 where the pointer is to file containing both CA and crl, not just CA, and line 227 where we enable crl checking.

This pretty much covers radius configuration. You can do some "hardening" if you want, but keep in mind it's your home network...

# chown -R freeradius:freeradius /var/db/certs
# chmod 0400 /var/db/certs/*.pem
# chmod 0600 /var/db/certs/dh
# chmod 0600 /var/db/certs/random


Update your /etc/rc.conf with

Code:
radiusd_enable="YES"

At this point run your radius server with:

# radiusd -X

If everything went right, you should see debug saying that server is now ready and listening. Otherwise, read up on errors. make install clean[/CMD]
 
SETTING UP CLIENTS
-------------------------

With server now ready, all that is left is to install certificates and authentication method on clients.

1) FreeBSD host (EAP-TLS)

Edit your /etc/wpa_supplicant.conf file like so:

Code:
network={
        ssid="Unbreakable"
        proto=RSN
        key_mgmt=WPA-EAP
        eap=TLS
        identity="FreeBSD_laptop"
        ca_cert="/var/db/certs/cacert.pem"
        client_cert="/var/db/certs/FreeBSD_laptop_cert.pem"
        private_key="/var/db/certs/FreeBSD_laptop_key.pem"
        private_key_passwd="wickedPa55"
}

Just like on server, I copied my certs on /var/db/certs.
Identity must match entry in users file on server that you configured.
Private key is a password you entered when you created your key pair for this host.

2) FreeBSD host (EAP-PEAP)

Configuration for EAP-TTLS and EAP-PEAP is similar (consult handbook it's all there). Lets say you want certs-less setup with EAP-PEAP. Then you would enter:

Code:
network={
        ssid="Unbreakable"
        proto=RSN
        key_mgmt=WPA-EAP
        [B]eap=PEAP[/B]
        identity="FreeBSD_laptop"
        password="supersecret"
        ca_cert="/var/db/certs/cacert.pem"
        [B]phase1[/B]="peaplabel=0"
        [B]phase2[/B]="auth=MSCHAPV2"
}

Notice that this time we only have CA we need to check for server authentication. We use password for our credentials - make sure users is updated properly. phase1 && phase2 specify communication inside tunnel.

That is what I would use on my phone since it doesn't support client certificates. I would copy cacert.p12 to phone root dir, install, and the rest is easy.

2) Windows host (EAP-TLS)

Windows needs CA in .der format and client cert in .p12 which contains both public/private key.
First, double click on cacert.der, confirm you want to install, and when prompted where to install, select "Place all certificates in the following store" and browse into "Trusted Root Certification Authorities".

Next, install .p12 cert, this time choosing different location for your client cert. Other options should be self explanatory.
Done! You can now delete .pem and .p12 files.

A word on CRL
----------------

When you want to revoke a certificate, you need to update your crl file, and notify radius (you must restart it). You need this every time you want to revoke a cert. I'm not aware of other easier methods to do this. Since this is your home network, you wouldn't do this often anyway, unless something bad happens (eg. your phone gets stolen).

To revoke a cert:

# cd ~/.certs
# openssl ca -revoke new/Windows_laptop.pem -keyfile CA/private/cacert.key -cert CA/cacert.pem -config ./openssl.cnf

Now, take a moment and open ~/.certs/index.txt and you should see "R" next to cert index number. If you ever need to make this cert valid again, you would edit
line with "R" to match other certs format.

Now you need to create crl list again, just like it was done at the beginning of tutorial:

# openssl ca -gencrl -keyfile CA/private/cacert.key -cert CA/cacert.pem -out crl/crl.pem -config ./openssl.cnf

Finally, concatenate this file with original CA file:

# cp crl/crl.pem /var/db/certs
# cd /var/db/certs
# cat cacert.pem crl.pem > cacrl.pem
# /usr/local/etc/rc.d/radiusd restart

You must restart radius, updating crl file is not enough.


Final words
--------------

So there you go, I hope this helps at least one person.

Let me know of any errors you find.
 
Hi - I am following this to setup radius and made a few subtle changes. The placement of -config matters to me for some reason, if I don't put it in the right place, then the default openssl conf is used. I ended up putting it into a script like this. I'm not sure the crlnumber is correct.

Code:
#!/bin/sh
mkdir -p CA/private new crl export
touch index.txt
echo '1001 ' > serial
cp serial crlnumber
echo "generate CA private key (web/radius/ca/private-key/passphrase)"
openssl req -new -x509 -extensions v3_ca -keyout CA/private/cakey.pem -out CA/cacert.pem  -config ./openssl.cnf
echo "export CA certificate to DER"
openssl x509 -inform PEM -outform DER -in CA/cacert.pem -out export/cacert.der
echo "export CA certificates to P12 (web/radius/ca/certificate/passphrase)"
openssl pkcs12 -export -in CA/cacert.pem -inkey CA/private/cakey.pem -out export/cacert.p12 -cacerts
echo "generate server keys (web/radius/ca/server-key/passphrase)"
openssl req -new -keyout new/server_key.pem -out new/server_req.pem -config openssl.cnf -nodes
echo "sign/create certificate"
openssl ca -out new/server_cert.pem -infiles new/server_req.pem -config ./openssl.cnf -extensions xpserver_ext
# client
echo "generate client request"
openssl req -new -keyout new/client1_key.pem -out new/client1_req.pem -config ./openssl.cnf
echo "sign client request"
openssl ca -config ./openssl.cnf -out new/client1_cert.pem -infiles new/client1_req.pem
echo "generate CRL"
openssl ca -config ./openssl.cnf -gencrl -keyfile CA/private/cakey.pem -cert CA/cacert.pem -out crl/crl.pem

I put a comment (note to self), the secret I'm using for each prompt. This way, I can easily lookup the correct secret for each. In the first case, the secret I am using is: web/radius/ca/private-key/passphrase.
 
Will add my 5 cents.

Modern freeradius3 will not automatically reply all the attributes from inner-tunnel if EAP authentication enabled. For example in my case, setting user specific VLAN can be seen in radtest utility, but doesn't appear in reply to the wireless AP.

In earlier versions this issue could be addressed by setting in raddb/mods-enabled/eap
Code:
use_tunneled_reply = yes
, the option is deprecated now. Instead of that, uncomment following line in the raddb/sites-available/inner-tunnel:

Code:
post-auth {
 - - - - 8< - - - -
 #
 #  Instead of "use_tunneled_reply", change this "if (0)" to an
 #  "if (1)".
 #
 #if (0) {
 if (1) {
   #
   #  These attributes are for the inner-tunnel only,
   #  and MUST NOT be copied to the outer reply.
   #
 - - - - 8< - - - -
}

Spent entire day trying to find out how to address that issue, someone may find this useful.
 
Back
Top