Edited: As mentioned by @JW0914, the documentation that was used to do this write-up has a number of security vulnerabilities which the proper remediation are discussed at the bottom of this posting.
Hello Community,
I have noticed there were a lot of people who were experiencing the same issues I had when trying to configure my Linksys WRT 3200acm router as an OpenVPN server.
some of the articles I used as reference were the following
I was working on OpenWRT/LEDE 18 version found here.
I was following the number of how-to manuals from communities and the OpenWRT's site. I tried the RSA method which gave me some issues with certificate validations and I read that easyrsa was not the most secure of methods. So in the end the procedures that worked for me was here.
OpenVPN Setup Guide for Beginners
Authentication with username and password
The directions were a bit confusing at first and luckily I powered through it and I was able to get it working. In turn, I felt I should contribute to the community to provide the steps and procedures I used based off of the above guides. If this is not the proper use of the forum or I am not following proper protocol with this post, please let me know and I will make the changes necessary to abide by LEDE Project Forum standards and guidelines.
Another reason I wanted to do this write up was to have the community review it and provide their feedback.
I followed the OpenSSL commands since it was labeled as the "most secure".
- update and install openvpn (the image I had, already had openvpn on it so I did not need to do this step).
opkg update; opkg install openvpn-openssl openvpn-easy-rsa
- I followed the guide and setup all my variables, created the index.txt file, created folders, and set permissions. These were all commands entered on the command line as root.
PKI_DIR="/etc/openvpn/ssl"
echo ${PKI_DIR}
rm -r ${PKI_DIR}
mkdir -p ${PKI_DIR}
ls -larth ${PKI_DIR}
chmod -R 0600 ${PKI_DIR}
cd ${PKI_DIR}
ls
touch index.txt; echo 1000 > serial
mkdir newcerts # certs crl csr private
- I then copied the openssl.cnf file to the directory just created as a reference.
cp /etc/ssl/openssl.cnf ${PKI_DIR}
ls
- I created another variable that points to the .cnf file.
PKI_CNF=${PKI_DIR}/openssl.cnf
- I then copied and pasted the following lines into the command line (before blindly copying and pasting the commands below, I went into the file using the "vi" command i.e.
vi ${PKI_CNF}
to see what items these "sed" commands would be searching and replacing).
sed -i '/^dir/ s:=.*:= /etc/openvpn/ssl:' ${PKI_CNF}
sed -i '/.*Name/ s:= match:= optional:' ${PKI_CNF}
sed -i '/organizationName_default/ s:= .*:= Anon:' ${PKI_CNF}
sed -i '/stateOrProvinceName_default/ s:= .*:= Hawaii:' ${PKI_CNF}
sed -i '/countryName_default/ s:= .*:= US:' ${PKI_CNF}
sed -i '/default_days/ s:=.*:= 3650:' ${PKI_CNF} ## default usu.: -days 365
sed -i '/default_bits/ s:=.*:= 4096:' ${PKI_CNF} ## default usu.: -newkey rsa:2048
sed -i '/default_md/ s:=.*:= default:' ${PKI_CNF} ## default usu.: sha256
- I then appended the same file with the additional configurations.
cat >> ${PKI_CNF} <<"EOF"
###############################################################################
### Check via: openssl x509 -text -noout -in *.crt | grep 509 -A 1
[ my-server ]
# X509v3 Key Usage: Digital Signature, Key Encipherment
# X509v3 Extended Key Usage: TLS Web Server Authentication
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[ my-client ]
# X509v3 Key Usage: Digital Signature
# X509v3 Extended Key Usage: TLS Web Client Authentication
keyUsage = digitalSignature
extendedKeyUsage = clientAuth
EOF
- I then created the certificates as stated in the OpenWRT documentation. I was confused about the #comments and then realized that the author was making the comparison to easyrsa commands that performed the equivalent.
openssl req -batch -nodes -new -keyout "ca.key" -out "ca.crt" -x509 -config ${PKI_CNF} ## x509 (self-signed) for the CA
openssl req -batch -nodes -new -keyout "my-server.key" -out "my-server.csr" -subj "/CN=my-server" -config ${PKI_CNF}
openssl ca -batch -keyfile "ca.key" -cert "ca.crt" -in "my-server.csr" -out "my-server.crt" -config ${PKI_CNF} -extensions my-server
openssl req -batch -nodes -new -keyout "my-client.key" -out "my-client.csr" -subj "/CN=my-client" -config ${PKI_CNF}
openssl ca -batch -keyfile "ca.key" -cert "ca.crt" -in "my-client.csr" -out "my-client.crt" -config ${PKI_CNF} -extensions my-client
- I then configured the certs' permissions and verified them:
chmod 0600 "ca.key"
chmod 0600 "my-server.key"
chmod 0600 "my-client.key"
ls -larth ca.key
ls -larth my-server.key
ls -larth my-client.key
- this cert was a big one but luckly the Linksys WRT 3200acm processor was faster when generating this one.
openssl dhparam -out dh2048.pem 2048 ## equivalent to the 'build-dh' script
- in this command, the author was trying to speak on checking what would be backed up if the user was to run the backup config command.
sysupgrade -l | grep rsa
The output will display what items would be backed up when your run the backup command.
/etc/dropbear/dropbear_rsa_host_key
/etc/easy-rsa/pki/.rnd
/etc/easy-rsa/pki/ca.crt
/etc/easy-rsa/pki/certs_by_serial/01.pem
/etc/easy-rsa/pki/dh.pem
/etc/easy-rsa/pki/index.txt
/etc/easy-rsa/pki/index.txt.attr
/etc/easy-rsa/pki/index.txt.old
/etc/easy-rsa/pki/issued/vpnserver.crt
/etc/easy-rsa/pki/private/ca.key
/etc/easy-rsa/pki/private/vpnserver.key
/etc/easy-rsa/pki/reqs/vpnserver.req
/etc/easy-rsa/pki/serial
/etc/easy-rsa/pki/serial.old
/etc/easy-rsa/pki/ta.key
/etc/easy-rsa/vars
This will help the end user determine what needs to be backed up or added to the back up list. If the output does not match what is listed above, add the items with the following command and confirm it was updated:
echo ${PKI_DIR}/* > /lib/upgrade/keep.d/my-pki
sysupgrade -l | grep rsa
- Next step is to create the .ovpn profile that are files to be distributed to your clients devices i.e. mobile devices, computers, etc. To start, create the following variable from the command line.
OVPN_FILE="/etc/openvpn/unitelife.ovpn"
- copy and paste the following string into the command line:
Important notice: it would be best to use a static ip (if your isp provider account is setup for this) but my isp is dhcp based. If it is dhcp, I highly recommend duckdns.org as they are a free service, they have great documentation on how to install the auto update api to your domain when your ip changes, and the new image of OpenWRT/LEDE has the DDNS service preinstalled.
Note: the "cat" commands at the end refer to the certs and key files created in steps 7 and 8 (make sure to be in the same directory as the cert and keys when executing this command or "<" to the full/path/to/file.crt ).
tee /etc/openvpn/unitelife.ovpn >/dev/null <<EOF2
client ## implies pull, tls-client
dev tun
proto udp ## udp is the default
fast-io
remote your-domain-at.duckdns.org 1194
remote-cert-tls server
nobind
persist-key
persist-tun
comp-lzo no
verb 3
EOF2
echo '<ca>' >> ${OVPN_FILE}
cat >> ${OVPN_FILE} < ca.crt
echo '</ca>' >> ${OVPN_FILE}
echo '<cert>' >> ${OVPN_FILE}
cat >> ${OVPN_FILE} < my-client.crt
echo '</cert>' >> ${OVPN_FILE}
echo '<key>' >> ${OVPN_FILE}
cat >> ${OVPN_FILE} < my-client.key
echo '</key>' >> ${OVPN_FILE}
- export the file with a tool like winscp set to "scp" file protocol set. If you are *nix based OS the the following commands should work:
ssh -y root@192.168.1.234 'mkdir -p /etc/openvpn'
scp ${OVPN_FILE} root@192.168.1.234:/etc/openvp #replace 192.168.1.234 with the ip address of the device the .ovpn file will be uploaded/downloaded to for distribution
cp /etc/openvpn/ssl/ca.crt /etc/openvpn/ssl/my-server.* /etc/openvpn/ssl/dh2048.pem /etc/openvpn
scp /etc/openvpn/ssl/ca.crt /etc/openvpn/ssl/my-client.* root@CLIENT_IP_ADDRESS:/etc/openvpn
- The steps from the OpenWRT site used the "uci" command to configure the openvpn server config file. I preferred to manually type it in the server config file because "uci" would wipe out previous configurations that may exist in the file.
vi /etc/config/openvpn
Review the following configs, make changes to meet your use-case, etc. I decided to use the tunnel method v.s. the tap.
Note: Please keep in mind the following options within the file under config option script_security '2' and option auth_user_pass_verify '/bin/openvpn-auth.sh via-file' I will reference this difference from the OpenWRT playbook later in this document. In addition, notice the similar referrences to the keys and certs created from steps 7 and 8.
config openvpn 'vpnserver'
option enabled '1'
option verb '3'
option port '1194'
option proto 'udp'
option dev_type 'tun'
option dev 'ovpns0'
option server '192.168.200.0 255.255.255.0'
option keepalive '10 120'
option ca '/etc/openvpn/ca.crt'
option cert '/etc/openvpn/my-server.crt'
option key '/etc/openvpn/my-server.key'
option dh '/etc/openvpn/dh2048.pem'
list push 'route 192.168.0.0 255.255.255.0'
list push 'dhcp-option DNS 192.168.0.1'
list push 'redirect-gateway def1'
option tun_mtu '1500'
option topology 'subnet'
option route_gateway 'dhcp'
option log '/tmp/openvpn.log'
option client_to_client '1'
option persist_key '1'
option persist_tun '1'
option mode 'server'
option script_security '2'
option auth_user_pass_verify '/bin/openvpn-auth.sh via-file'
The following instructions is what I included to the .ovpn file to include password authentication to prompt the end-users/clients to input their credentials before connecting to the network. The OpenWRT instructions made it so that the .ovpn file will automatically connect the client to the network. The security-side in me didn't like this, in the event that some badies got a hold of my .ovpn profile and exploited my network.
- Open the .ovpn file in a text editor on the device you exported the .ovpn file from the router i.e. notepad, vi, nano, notepad++, etc. and add the following
auth-user-pass
auth-nocache
The .ovpn file should finally look similar to this:
client ## implies pull, tls-client
dev tun
proto udp ## udp is the default
fast-io
remote your-domain-at.duckdns.org 1194
remote-cert-tls server
nobind
persist-key
persist-tun
verb 3
key-direction 1
redirect-gateway def1
auth-user-pass
auth-nocache
<ca>
-----BEGIN CERTIFICATE-----
MIIFLTCCAxWgAwIBAgIJAJg0Oxj7Wdj ... <EXCERPT> ...
-----END CERTIFICATE-----
</ca>
<cert>
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4097 (0x1001)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CU, ST=State, O=Anon
Validity
Not Before: Jun 23 07:01:28 2018 GMT
Not After : Jun 20 07:01:28 2028 GMT
Subject: CN=my-client
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
00:cc:ec:f9:92:cc:5a:be:5d:81:14:bd:78:62:8a:
.... <EXCERPT> ...
27:0a:23:11:6c:d3:bc:5f:67:af:2b:54:0a:6b:a8:
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage:
Digital Signature
X509v3 Extended Key Usage:
TLS Web Client Authentication
Signature Algorithm: sha256WithRSAEncryption
8c:46:25:46:fc:71:ee:47:b9:9a:ff:bb:1b:d3:d6:dd:09:44:
..... <EXCERPT> ....
52:5a:5c:2f:cc:69:e5:83
-----BEGIN CERTIFICATE-----
MIIE4TCCAsmgAwIBAgICEAEwDQYJK... <EXCERPT>
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAA... <EXCERPT>
-----END PRIVATE KEY-----
</key>
- The following script creates a username and password. The script hashes the password and stores it in a file to referrence by another script we named openvpn-auth.sh. This script was mentioned in step 14 under the openVPN server config file under the option script_security '2' and option auth_user_pass_verify '/bin/openvpn-auth.sh via-file' syntax.
vi /etc/openvpn/createvpnuser.sh
copy and paste the following:
Note: Keep in mind the output file referenced at the end of this createvpnuser.sh script, auth. this is the file the authentication script will use to check the existence of the client/end-user.
#!/bin/sh
#genhash.sh
#Generate Hash for username/password
#Case sensitive and only allows a certain set of characters to be used
function hashround() {
local hash rest
read hash rest
printf '%s%s' "$hash" "$hash" | /usr/bin/md5sum
}
case "$1" in *[!-_a-zA-Z0-9]*) exit 1 ;; esac
case "$2" in *[!-_a-zA-Z0-9]*) exit 1 ;; esac
hashpass=$(printf '%s%s' "$1" "$2" | /usr/bin/md5sum \
| hashround | hashround | hashround | hashround | hashround \
| hashround | hashround | hashround | hashround | hashround \
| /usr/bin/cut -d' ' -f1)
echo "Username: $1"
echo "Password: $2"
echo "Password (MD5 Hashed): $hashpass"
echo "User added to approved user file"
echo "$1:$hashpass" >> /etc/openvpn/auth
- Create your user account and password. The password is rudimentary at best as the script only allows letters, numbers, "-", "_", etc. and no special characters.
./createvpnuser.sh <username> <password>
Verify the user and the hash equivalent of the password was created
cat /etc/openvpn/auth
Output should be similar to the following:
unitelife:92s8e2yDn8sET620fa9f7f1427bd0e5s
- Create the username and password authentication script.
Notice the users="/etc/openvpn/auth" referenced.
#!/bin/sh
#verify.sh
#This script was made with via-file in mind
PATH=/usr/bin:/bin
#Function used for generating MD5 hash value of password multiple times
function hashround() {
local hash rest
read hash rest
printf '%s%s' "$hash" "$hash" | /usr/bin/md5sum
}
#Location of the Approved Username/Password File
users="/etc/openvpn/auth"
#Check to see if generated OpenVPN login file has any special characters
#Terminate script if special characters are used
if /bin/grep -q '[^-_a-zA-Z0-9]' "$1"
then
echo "Illegal characters found in username/password." >&2
exit 1
fi
#1st line is the username
username=`/usr/bin/awk 'NR==1' "$1"`
#2nd line is the password
password=`/usr/bin/awk 'NR==2' "$1"`
#Generate MD5 hash of given password and loop it 10 times before comparing with hash value in users file
hashpass=$(printf '%s%s' "$username" "$password" | /usr/bin/md5sum \
| hashround | hashround | hashround | hashround | hashround \
| hashround | hashround | hashround | hashround | hashround \
| /usr/bin/cut -d' ' -f1)
if /bin/grep -Fxq "$username:$hashpass" "$users"
then
echo "User Authenticated." >&2
exit 0
fi
echo "Login credentials failed." >&2
exit 1
- Now we can test the .ovpn connection on a clients machine. Check the logs to see if any errors appear.
tail -f /tmp/openvpn.log
If all is well, the output should reflect the following:
Tue Jun 26 01:14:32 2018 172.56.44.236:36052 peer info: IV_GUI_VER=OpenVPN_GUI_11
User Authenticated.
Tue Jun 26 01:14:32 2018 172.56.44.236:36052 TLS: Username/Password authentication succeeded for username 'unitelife'
Tue Jun 26 01:14:32 2018 172.56.44.236:36052 WARNING: 'keydir' is present in remote config but missing in local config, remote='keydir 1'
Tue Jun 26 01:14:33 2018 172.56.44.236:36052 Control Channel: TLSv1.2, cipher TLSv1/SSLv3 ECDHE-RSA-AES256-GCM-SHA384, 4096 bit RSA
Tue Jun 26 01:14:33 2018 172.56.44.236:36052 [my-client] Peer Connection Initiated with [AF_INET]172.56.44.236:36052
Tue Jun 26 01:14:33 2018 my-client/172.56.44.236:36052 MULTI_sva: pool returned IPv4=192.168.200.2, IPv6=(Not enabled)
Tue Jun 26 01:14:33 2018 my-client/172.56.44.236:36052 MULTI: Learn: 192.168.200.2 -> my-client/172.56.44.236:36052
Tue Jun 26 01:14:33 2018 my-client/172.56.44.236:36052 MULTI: primary virtual IP for my-client/172.56.44.236:36052: 192.168.200.2
Tue Jun 26 01:14:34 2018 my-client/172.56.44.236:36052 PUSH: Received control message: 'PUSH_REQUEST'
Tue Jun 26 01:14:34 2018 my-client/172.56.44.236:36052 SENT CONTROL [my-client]: 'PUSH_REPLY,route 192.168.0.0 255.255.255.0,dhcp-option DNS
Tue Jun 26 01:14:34 2018 my-client/172.56.44.236:36052 Data Channel: using negotiated cipher 'AES-256-GCM'
And that is pretty much what it took to get my vpn up and running. Please feel free to leave any feedback, comments, remarks to help improve or correct any configurations I may have done or best practices that should be followed.
Sincerely,
A humbly committed student