- Applied Crypto Hardening
- Linode: Set up a hardened OpenVPN Server
- Weak Diffie-Hellman and the Logjam Attack
Install openvpn
. On Debian/Ubuntu, something like this should install all you need:
# apt-get install openvpn easy-rsa # IIRC, easy-rsa is a dependency of openvpn
# cd /etc/openvpn # Pick any directory
# make-cadir easy-rsa # Directory to store easy-rsa CA
Edit the variables in easy-rsa/vars
and set appropriate key sizes, etc. Important vars:
KEY_SIZE
: Size of certificate keys, etc. Set to 4096 or bigger (in 2016). I used 8196, but that’s probably overkill for now :-) Check Applied Crypto Hardening (ACH) for some advice.CA_EXPIRE
: ACH mentions 5 years as a “sane value” (remember that you’ll have to switch all certs signed by the CA when it expires)KEY_EXPIRE
: ACH recommends 1 yearKEY_COUNTRY
,KEY_PROVINCE
,KEY_CITY
,KEY_ORG
,KEY_EMAIL
,KEY_OU
: Set to whatever you want. This is certificate information and you’re only setting up a private VPN, not something mission critical, right?
Change into the easy-rsa
directory and source the vars file, since those are used by most scripts:
# cd easy-rsa
# source vars
Start with a clean slate:
# ./clean-all # Removes any existing certs. Don’t do it if you want to save anything.
Create the (self-signed) certificate authority (Use a password to protect the CA):
# ./build-ca
Generating a 8192 bit RSA private key
...
Generate the server’s certificate (no password, since we want the server to start automatically when restarting the computer).
# ./build-key-server RPi2 # Identifier can be whatever you want
Generating a 8192 bit RSA private key
...
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
Generate Diffie-Hellman parameters. This makes it harder to crack keys en-masse over the Internet (explanation for this falls out of the scope of this tutorial. Check WeakDH). Warning: on iOS 9 with OpenVPN1.0.7, DH parameters of 8192 resulted in not being able to connect. Generating DH parameters takes a long time. Feel free to leave a terminal running this command and do the rest of the tutorial on another one (remember to cd
to the proper directory and source vars
).
# ./build-dh
This command takes a long time. 8192 takes days on a Raspberry Pi2 (even on a tricked out Mac Pro from 2014, it takes >24h). 4096 should take a while, but be tolerable. 4096 bit keys take ~8h on an RPi2. From experiments, it seems 8192 doesn't work on iOS OpenVPN app. Feel free to override KEY_SIZE
for this command:
# KEY_SIZE=4096 ./build-dh # allows for an 8192 default for everything else
Generate a shared secret key, which is useful to protect against DoS attacks:
# openvpn --genkey --secret ta.key
Create/edit the server configuration file. /etc/openvpn/server.conf
is a good place. Example configuration for a UDP server (commented):
mode server
tls-server
# From Applied Crypto Hardening (3.2.3. Recommended cipher suites)
# To have a shared cipher with iOS 9 we need TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 (no ECDHE)
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384
auth SHA512
# Make sure we only use TLS 1.2
tls-version-min 1.2
# Set up CRL (not needed for now)
#crl-verify /etc/openvpn/crl.pem
# Make the replay window slightly larger due to mobile clients
replay-window 256 60
# IP/hostname to bind to
# Not needed if you only have one network interface, or if you don’t mind binding to all of them.
local 10.27.58.91
port 1194 ## default openvpn port
proto udp
# Can't use tap (network bridging. Would allow broadcast traffic) with iOS clients (and probably others)
#dev tap0 ## If you need multiple tap devices, add them here
dev tun # “tunnel”. Only supported dev for iOS clients.
# Certificates and encryption
ca ca.crt
cert RPi2.crt
key RPi2.key # This file should be kept secret
#dh dh8192.pem # dh8192 is too much for the iOS client :-(
dh dh4096.pem
tls-auth ta.key 0 # Shared secret among server and clients
# Encrypt data channel with AES 256. Compress data with LZO
cipher AES-256-CBC
comp-lzo
# Tunnel configuration: Assign a subnet for VPN clients.
server 10.55.43.0 255.255.255.0
# Persist IP assignments to clients in case of server restart
ifconfig-pool-persist ipp.txt
# Force all traffic through VPN
push "redirect-gateway def1"
push "remote-gateway 10.27.58.1”
# Push your DNS server IP
push "dhcp-option DNS 10.27.58.1”
push "dhcp-option DOMAIN vpn.mydomain.com”
max-clients 10 # Not very important if it’s a private server
# Drop privileges after setting up (be sure to create the user first)
user openvpn
group nogroup
# chroot to this directory after initialization (less access to the system if compromised)
chroot /etc/openvpn/chroot
# Persist keys and tunnels over `SIGUSR1` (“soft restarts”)
# Useful to use when dropping privileges
persist-key
persist-tun
# Set appropriate timeouts (check the man page)
keepalive 10 120
# Keep current status on this file.
status openvpn-status.log
# Log verbosity (logs can be read with journalctl)
verb 3
If desired, create a TCP config file by copying over the UDP version and editing the lines with the server
(pick a different range) proto
(tcp
) and port
(if you want) directives (Having a TCP server on port 80 or 443 is very useful to bypass firewalls, since those ports are usually open. Feel free to try UDP on port 53 (DNS) or 123 (NTP)).
Copy over files created with easy-rsa to /etc/openvpn
to have everything in the same place (If you don’t want to copy, adjust the paths on the server configuration file):
# cp ca.crt dh4096.pem RPi2.crt RPi2.key ta.key /etc/openvpn
Create a client template config file. I usually keep it in /etc/openvpn/clients/iOS-config-template.ovpn
:
### iOS configuration file for OpenVPN
# From Applied Crypto Hardening
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384
auth SHA512
# https://openvpn.net/index.php/open-source/documentation/howto.html#mitm
remote-cert-tls server
# Host name and port for the server (default port is 1194)
# note: replace with the correct values your server set up
remote vpn.example.net 1194
#proto tcp-client # Uncomment this if you’re configuring for a TCP server
# SSL/TLS parameters - files created previously
ca ca.crt
# Since we specified the tls-auth for server, we need it for the client
# note: 0 = server, 1 = client
tls-auth ta.key 1
# Specify same cipher as server
cipher AES-256-CBC
# Use compression
comp-lzo
Write this script to /etc/openvpn/openvpn-unify
(or somewhere else, it’s not sensitive). It turns a regular configuration file plus the certificates and keys into a “unified” configuration file, so we don’t need to copy over several files. This unified file will contain the shared secret between the server and the clients (same for every client), so should be protected.
#!/usr/bin/env python
from __future__ import print_function
import re
import sys
def process_config(f, out):
for line in f:
print(process_line(line.rstrip()), file=out)
def process_line(line):
if re.match('^ *#', line):
return line
s = line.split(' ', 2)
if s[0] == 'ca' or s[0] == 'cert' or s[0] == 'key':
comment = ' %s' % s[2] if len(s) > 2 else ''
line = generate_inline(s[0], s[1], comment)
elif s[0] == 'tls-auth':
m = re.match('^tls-auth ([^ ]+) ([01])(.*)$', line)
if not m:
print('Error in tls-auth line: %s' % line)
sys.exit(1)
path = m.group(1)
type = m.group(2)
comment = m.group(3)
line = 'key-direction %d\n' % int(type)
line += generate_inline('tls-auth', path, comment)
return line
def generate_inline(name, path, comment):
ret = '<%s>%s\n' % (name, comment)
with open(path, 'r') as f:
ret += f.read()
ret += '</%s>\n' % name
return ret
def main():
if len(sys.argv) == 1:
print('Error: usage: %s input.ovpn [output.ovpn]' % sys.argv[0])
sys.exit(1)
with open(sys.argv[1], 'r') as f:
if len(sys.argv) > 2:
with open(sys.argv[2], 'w') as out:
process_config(f, out)
else:
process_config(f, sys.stdout)
if __name__ == '__main__':
main()
Make it executable:
# chmod +x /etc/openvpn/openvpn-unify
Create the unified config file for this server’s clients:
# /etc/openvpn/openvpn-unify /etc/openvpn/clients/iOS-config-template.ovpn > /etc/openvpn/clients/iOS-config.ovpn
tls-verify
and others, which allow the server/clients to verify parts of each other’s certificates before allowing the connection to go through.
OpenVPN installs /lib/systemd/system/openvpn@.service
(at least on Debian)
To get systemd to start the VPN server with a config file in /etc/openvpn/myconfigfile.conf
, do:
# sudo ln -s /lib/systemd/system/openvpn@.service /etc/systemd/system/multi-user.target.wants/openvpn@myconfigfile.service
systemd extracts the part after the @
and before .service, and uses that to find the config file (check out the openvpn@.service
file)
For server.conf
and server-tcp.conf
:
# sudo ln -s /lib/systemd/system/openvpn@.service /etc/systemd/system/multi-user.target.wants/openvpn@server.service
# sudo ln -s /lib/systemd/system/openvpn@.service /etc/systemd/system/multi-user.target.wants/openvpn@server-tcp.service
Feel free to start the servers now:
# sudo systemctl restart 'openvpn*.service'
To be able to route everything through the VPN you’ll need to enable IP forwarding and tell the firewall to forward traffic.
Add these lines to /etc/rc.local
or your favourite boot up script:
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables-restore /etc/iptables-saved
Write this (after substituting the IP range and network interface) to /etc/iptables-saved
:
# iptables-saved start (replace iface name with yours, IP ranges for the OpenVPN servers too)
*nat
:PREROUTING ACCEPT [4368:1166153]
:INPUT ACCEPT [3735:1124005]
:OUTPUT ACCEPT [1010:98992]
:POSTROUTING ACCEPT [1010:98992]
-A POSTROUTING -s 10.0.4.0/24 -o enxb827eb6b34f7 -j MASQUERADE
-A POSTROUTING -s 10.0.3.0/24 -o enxb827eb6b34f7 -j MASQUERADE
COMMIT
Add the OpenVPN user:
# adduser --system --shell /usr/sbin/nologin --no-create-home openvpn_server
Set up the chroot directory (needs a tmp
directory inside it):
# mkdir -p /etc/openvpn/chroot/tmp
To add a VPN client, you’ll need to generate a certificate and sign it with the server CA. Ideally, the client will provide a certificate request, but this tutorial doesn’t do that. Each client should have their own certificate so we can revoke them one by one if needed.
Generate a PKCS#12 file with client-name
as the common name, and sign it with the server’s certificate:
# ./build-key-pkcs12 client-name # Builds a key with label "client-name”.
Provide a strong password. Take the client-name.p12
file and store it somewhere safe.
Get the client-name.p12
file onto the iOS device. Easiest ways are:
- Create a temporary web server on your local network and use Safari to get the PKCS#12
- Email it to yourself and open the attachment
Get the iOS-config.ovpn
file onto the OpenVPN app on your iOS device using iTunes (safer), or email/Safari (OpenVPN should get the file when you open it).
Open the OpenVPN iOS app, import the configuration, and connect. All should be good :-) If not:
- Did you add the DNS entry, or double-check the IP?
- Check the logs (on the iOS app or with
journalctl -xf
on the server (yes, assuming Linux and system))
Seriously, the systemd steps here are horrendous.
First of all, you will never need to run
ln -s
manually. For that you just dosystemctl enable $UNIT
where$UNIT
can beopenvpn@CONFIG_NAME
. So if you have a file name /etc/openvpn/mytunnel.conf, then you usesystemctl enable openvpn@mytunnel
(without the .conf extension).Secondly, the OpenVPN upstream recommended way is to use the newer systemd unit files we ship. That is:
openvpn-server@.service
andopenvpn-client@.service
.Put your server configs into:
/etc/openvpn/server
and client configurations into/etc/openvpn/client
. Then you usesystemctl {start|stop|status|enable|disable} openvpn-{client,server}@CONFIG_NAME
to manage that service. To let systemd take care of the logging, do not use the--log
option the OpenVPN configs at all ... and then usejournalctl -u openvpn-{client,server}@CONFIG_NAME --since today
.The reason for splitting out server and client is related to that these new unit files locks down the OpenVPN process even further, but client and servers requires slightly different capabilities. In addition, the client configurations greatly benefits of having
--nobind
, so that is added by default for client configurations too.But there's more:
--status
line in the config example is superfluous using these new unit files.--ifconfig-pool-persist
isn't as great as many believes, it's an best-effort solution but gives no guarantees at all. If you want a consistent IP address on your client, use--client-config-dir
and set the IP address explicitly in the CCD config file for each client.--tls-crypt
instead of--tls-auth
--tls-cipher
yourself. The default in recent OpenVPN versions are mostly up-to-date to what is needed, and it removes the need to update this if the official recommendations changes later on. Updating OpenVPN will be enough.--tls-min-version
is reasonable, but as long as OpenVPN and OpenSSL/mbed TLS are up-to-date, TLSv1.2 is what it will default to regardless.--comp-lzo
. If you want to compression, use--compress
. But there are also concerns about coupling compression with encryption, there are many pitfalls with this which can more easily make it possible to identify packet types being transmitted inside the tunnel by just looking at the encrypted traffic. The general recommendation is to not do compression. In addition, most files being transported these days have already some compression, so the benefit of compressing compressed data is mostly missing. What--compress
adds is that it compresses more of the data retrieved from the TUN/TAP packets (IP headers, Ethernet frames, etc). Bottom line is, it probably doesn't give you as much as you'd like but adds a potential cost on the confidentiality of the tunnel.--remote-cert-tls client
.--verify-x509-name
- this restricts clients to connect to servers providing an expected X.509 subject. If you just want to do a match on the CN (typically hostname) field, use--verify-x509-name $HOSTNAME name
And most gravely: DO NOT EVER STORE YOUR PRIVATE CA KEY FILE ON THE OPENVPN SERVER
With
easy-rsa
(but also other CA solutions), the CA key is often not password encrypted. Which means anyone can issue new certificates offline - all they need is a copy of your private CA key. By doing that, they will also be capable of connecting to your VPN server, or even setup their own VPN server and make clients connecting to their server instead - and the clients wouldn't notice. Bottom line is: Put your CA files (in particular the private CA key) on a storage medium which is mostly offline. The files needed on the server and clients are:--ca
--cert
--key
--dh
--tls-auth
or--tls-crypt
--pkcs12
--ca
,--cert
and--key
into a single file. Protect this file as a private secret key.All of these files can also be embedded into the configuration file. If that is done, the configuration file must be protected as you would do with the private keys.
All this is why we in the upstream OpenVPN community have started to point users at this wiki page, the guidance you get on the Interwebs can be quite full of flaws. We don't believe in a "standard" setup, as there are too many variables which makes up a VPN. It is more important to understand which options you need to get a safe start and why you need to use them. Configuring a VPN isn't easy if you want to do it correctly and securely.