802.1x using FreeRadius3 and LetsEncrypt on OpenWrt

This is a setup guide (a how to) for FreeRadius3 op Openwrt. The information is obtained from various sources on the internet. https://tmikey.tech/tech_daily/2018/08/23/openwrt_freeradius3.html
https://kb.netgear.com/1209/What-is-802-1x-Security-Authentication-for-Wireless-Networks
https://openwrt.org/docs/guide-user/network/wifi/freeradius
We need to install a number of files: main files, tunnels, modules, plain text passwsord file support, utilities(radtest):

opkg install freeradius3 freeradius3-common freeradius3-default
opkg install freeradius3-mod-eap freeradius3-mod-eap-peap freeradius3-mod-eap-mschapv2 freeradius3-mod-eap-tls
opkg install freeradius3-mod-preprocess freeradius3-mod-files freeradius3-mod-radutmp freeradius3-mod-expiration freeradius3-mod-logintime freeradius3-mod-attr-filter
opkg install freeradius3-mod-passwd
opkg install freeradius3-utils

You may not need all of these actually..

For WPA enterprise we also need wpad full (first remove wpad-basic or wpad-basic-wolfssl, see here )

To give an idea what different authentication options give:

  • EAP-TLS is widely supported. It uses PKI (e.g., a digital certificate) to authenticate the supplicant and authentication server.
  • EAP-MD5 uses standard user name and password. The supplicant’s password is hashed with MD5 and the hash value is being used to authenticate the supplicant.
  • LEAP is Cisco’s Lightweight EAP, and works mainly with Cisco products. It also uses MD5 hash, but both the supplicant and authentication server are authenticated.
  • EAP-TTLS uses PKI to authenticate the authentication server. However, it supports a different set of authenticate methods (e.g. CHAP, PAP, MS-CHAP v2) to authenticate the supplicant.
  • PEAP (Protected EAP), which is built-in to Windows XP, uses PKI to authenticate the authentication server. It supports any type of EAP to authenticate the supplicant including certificate.

Note that not all options are the same in terms of security. Especially EAP-MD5, EAP-LEAP and PAP should be avoided. So to do password based authentication the only options left are PEAP and EAP-TTLS. Which should be ok, the only caveat being the man-in-the-middle attack with an evil twin. The credentials can be intercepted if you attempt to connect to a network with the same SSID but which is not the actual network. This can be avoided with the right client settings, and use a server side certificate (from e.g. LetsEncrypt), but this is prone to error, as the client might be changed to accept any certificate. Better thing to do is use EAP-TLS, as the credentials cannot be intercepted and it is also pretty fast. However, you need to set up a CA to generate client certificates.

So lets setup freeradius3 for eap-tls. First thing to do is setup the clients of FreeRadius (these are the that use FreeRadius, not the wireless stations) using

nano /etc/freeradius3/clients.conf

content:

# Router1
client OpenWrt {       # name 'OpenWrt' can be anything
     ipaddr = 192.168.15.1 # change it according to your needs
     secret = testing123 # you will need it for testing purposes
}
client OpenWrt2 { # name 'OpenWrt2' can be anything
     ipaddr = 192.168.15.2 # change it according to your needs
     secret = testing123 # you will need it for testing purposes
}

Next thing to do is set the eap options. Most of this is taken from https://wiki.mikrotik.com/wiki/Manual:Wireless_EAP-TLS_using_RouterOS_with_FreeRADIUS with a few modification

 nano /etc/freeradius3/sites-enabled/default

Here you can comment out things that you do not want or need. For EAP-TLS you want something like this:

server {
    listen {
        type = auth
        port = 1812
        ipaddr = *
    }
    authorize {
        preprocess
        suffix
        filter_username
        eap {
                ok = return
        }
        expiration
        logintime
    }
    authenticate {
        eap
    }
    preacct {
        preprocess
        acct_unique
        suffix
    }
    accounting {
        detail
        radutmp
        attr_filter.accounting_response
    }
    session {
        radutmp
    }
    post-auth {
        remove_reply_message_if_eap
        Post-Auth-Type REJECT {
                attr_filter.access_reject
                eap
                remove_reply_message_if_eap

        }
    }
}

Delete the whole inner-tunnel (we dont need it for TLS, if we leave it it might be a security risk depending on the content)

 rm /etc/freeradius3/sites-enabled/inner-tunnel

Last thing is to set up eap:

nano  /etc/freeradius3/mods-enabled/eap
eap {
        default_eap_type = tls
        timer_expire = 60
        ignore_unknown_eap_types = no
        cisco_accounting_username_bug = no
        max_sessions = ${max_requests}
        tls-config tls-common {
## this is so the client can validate the wifi
                private_key_password =
                private_key_file = /etc/acme/domain.com/domain.com.key
                certificate_file =   /etc/acme/domain.com/fullchain.cer
##  this is for the wifi to validate the client:
                ca_file = /root/ca/intermediate/certs/ca-chain-crl.pem
                tls_min_version = "1.2"
                tls_max_version = "1.2"
                check_crl = yes
                dh_file = /root/dh
                random_file = /dev/random
                ca_path = /root/ca/intermediate/certs/
                cipher_list = "HIGH"
                ecdh_curve = "prime256v1"
                cipher_server_preference = yes
                verify {
                        tmpdir = /tmp/radiusd
                        client = "/usr/bin/openssl verify -CAfile ${..ca_file} %{TLS-Client-Cert-Filename}"
                }
        }
        tls {
                tls = tls-common
        }
}

Use this to generate the dh file:

 openssl dhparam -out /root/dh 2048

Install Acme:

opkg install acme
opkg install luci-app-acme

Make sure you enable it and uncheck the staging server, and add the public domain name of the router. I do not have a static IP, but its static enough. So just do a nslookup of the public IP address and use that as domain name.

next thing to do is set up the CA
OpenSSL CA guides:

https://jamielinux.com/docs/openssl-certificate-authority/introduction.html

I reproduced it here, for easy copy/pasting:

mkdir /root/ca
cd /root/ca
mkdir certs crl newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial
nano /root/ca/openssl.cnf
# OpenSSL root CA configuration file.
# Copy to `/root/ca/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = /root/ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_strict

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See .
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = NL
stateOrProvinceName_default     = NB
localityName_default            = Somewhere
0.organizationName_default      = NoCorp
organizationalUnitName_default  =
emailAddress_default            =
[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

Generate root certificate:

cd /root/ca
openssl genrsa -aes256 -out private/ca.key.pem 4096
chmod 400 private/ca.key.pem

Invent password and write down.

cd /root/ca
openssl req -config openssl.cnf -key private/ca.key.pem \
      -new -x509 -days 73000 -sha256 -extensions v3_ca \
      -out certs/ca.cert.pem

Fill in something appropriate:

Country Name (2 letter code) [XX]:NL
State or Province Name []:NB
Locality Name []:
Organization Name []:NoCorp
Organizational Unit Name []:NoCorp Certificate Authority
Common Name []:NoCorp Root CA
Email Address []:
 chmod 444 certs/ca.cert.pem

Now make intermediate certificate:

mkdir /root/ca/intermediate
cd /root/ca/intermediate
mkdir certs crl csr newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > /root/ca/intermediate/crlnumber

copy above conf file to /root/ca/intermediate/openssl.cnf, but change:

[ CA_default ]
dir             = /root/ca/intermediate
private_key     = $dir/private/intermediate.key.pem
certificate     = $dir/certs/intermediate.cert.pem
crl             = $dir/crl/intermediate.crl.pem
policy          = policy_loose
cd /root/ca
openssl genrsa -aes256 \
      -out intermediate/private/intermediate.key.pem 4096
chmod 400 intermediate/private/intermediate.key.pem

Invent 2nd password and write it down!

 cd /root/ca
openssl req -config intermediate/openssl.cnf -new -sha256 \
      -key intermediate/private/intermediate.key.pem \
      -out intermediate/csr/intermediate.csr.pem

Fill in something Appropriate:

Country Name (2 letter code) [XX]:NL
State or Province Name []:NB
Locality Name []:
Organization Name []:NoCorp
Organizational Unit Name []:NoCorp Certificate Authority
Common Name []:NoCorp Intermediate CA
Email Address []:
cd /root/ca
openssl ca -config openssl.cnf -extensions v3_intermediate_ca \
      -days 3650 -notext -md sha256 \
      -in intermediate/csr/intermediate.csr.pem \
      -out intermediate/certs/intermediate.cert.pem
chmod 444 intermediate/certs/intermediate.cert.pem
openssl verify -CAfile certs/ca.cert.pem \
      intermediate/certs/intermediate.cert.pem

Should give OK
Add the certificates to a chain:

cat intermediate/certs/intermediate.cert.pem \
      certs/ca.cert.pem > intermediate/certs/ca-chain.cert.pem
chmod 444 intermediate/certs/ca-chain.cert.pem

Now for eack client we need to generate a key and certificate and sign them ( you may want to remove the -aes256 to leave out the password, e.g. when you add a password when you pack them together):

cd /root/ca
openssl genrsa  -aes256 \
      -out intermediate/private/station.key.pem 2048
chmod 400 intermediate/private/station.key.pem
cd /root/ca
openssl req -config intermediate/openssl.cnf \
      -key intermediate/private/station.key.pem \
      -new -sha256 -out intermediate/csr/station.csr.pem

Fill in something appropriate, especially make sure common name is ok, that will be the user name for the wifi:

Country Name (2 letter code) [XX]:NL
State or Province Name []:NB
Locality Name []:Somewhere
Organization Name []:NoCorp
Organizational Unit Name []:NoCorp services
Common Name []:station
Email Address []:

Now depending on wether we want a client or server certificate use usr_cert or server_cert in the command below. For 802.1X we want to authenticate a user hence:

cd /root/ca
openssl ca -config intermediate/openssl.cnf \
      -extensions usr_cert -days 3750 -notext -md sha256 \
      -in intermediate/csr/station.csr.pem \
      -out intermediate/certs/station.cert.pem
chmod 444 intermediate/certs/station.cert.pem

Verify the cert:

openssl verify -CAfile intermediate/certs/ca-chain.cert.pem \
      intermediate/certs/station.cert.pem

Create CRL:

cd /root/ca
openssl ca -config intermediate/openssl.cnf \
      -gencrl -out intermediate/crl/intermediate.crl.pem
openssl crl -in intermediate/crl/intermediate.crl.pem -noout -text
cat intermediate/certs/ca-chain.cert.pem \
   intermediate/crl/intermediate.crl.pem \
  > intermediate/certs/ca-chain-crl.pem

Deploy these:

ca-chain-crl.pem
station.key.pem
station.cert.pem

Or use them packed together (for e.g. android)

cd /root/ca
openssl pkcs12 -export -in intermediate/certs/station.cert.pem -inkey intermediate/private/station.key.pem \
          -certfile intermediate/certs/ca-chain-crl.pem -name MyClient -out station.p12

To check the content of any certificate:

openssl x509 -noout -text -in XXXXXXXX.cert.pem

To revoke a cert:

cd /root/ca
openssl ca -config intermediate/openssl.cnf \
      -revoke intermediate/certs/station.cert.pem
openssl ca -config intermediate/openssl.cnf \
      -gencrl -out intermediate/crl/intermediate.crl.pem
openssl crl -in intermediate/crl/intermediate.crl.pem -noout -text
cat intermediate/certs/ca-chain.cert.pem \
   intermediate/crl/intermediate.crl.pem \
  > intermediate/certs/ca-chain-crl.pem

To test:

service radiusd stop
radiusd -X

Once everything is running fine use:

service radiusd start

or

service radiusd restart

I added a number of the steps above into a script:

#!/bin/sh


if [ $# == 0 ]
then
echo "Use $0 certname"
        exit 1
fi

echo "Using $1"


cd /root/ca
openssl genrsa  -out intermediate/private/$1.key.pem 2048
chmod 400 intermediate/private/$1.key.pem
cd /root/ca
openssl req -config intermediate/openssl.cnf -subj '/C=NL/ST=NB/L=Somewhere/O=NoCorp/OU=NoCorp Services/CN='$1'/emailAddress=' \
              -key intermediate/private/$1.key.pem -new -sha256 -out intermediate/csr/$1.csr.pem

cd /root/ca
openssl ca -config intermediate/openssl.cnf -extensions usr_cert -days 3750 -notext -md sha256 -in intermediate/csr/$1.csr.pem -out intermediate/certs/$1.cert.pem
chmod 444 intermediate/certs/$1.cert.pem

openssl verify -CAfile intermediate/certs/ca-chain.cert.pem intermediate/certs/$1.cert.pem
cd /root/ca
openssl pkcs12 -export -in intermediate/certs/$1.cert.pem -inkey intermediate/private/$1.key.pem \
          -certfile intermediate/certs/ca-chain-crl.pem -name $1Wifi -out $1.p12

P.S. Getting to connect may not be straightforward: see https://www.aventistech.com/2020/03/setup-nps-with-eap-tls-for-aruba-wifi/
P.P.S. It seems that with the Windows 10 version 2004 it is not working anymore. Still trying to work out a solution. Switching of protected management frames may help…