OpenVPN

From Segfault
Jump to: navigation, search

Installation

LZO

NOTE: OpenVPN 2.4 has support for lz4[1], which is said to be faster than lzo. If supported by the server, we could use lz4 instead.

To compile with LZO support, we have to build liblzo first:

wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz
tar -xzf lzo* && cd lzo*
./configure --prefix=/opt/lzo
make
sudo make install

We'll also need the PKCS#11 helper library:

git clone https://github.com/alonbl/pkcs11-helper.git pkcs11-helper-git
cd pkcs11-helper-git
libtoolize --copy --install --force
aclocal -I m4
autoheader && autoconf && automake --add-missing

./configure --prefix=/opt/pkcs11-helper
make
sudo make install

Or we could just install some packages (along with OpenSSL and GnuTLS):

sudo apt-get install autoconf libssl-dev liblzo2-dev libpkcs11-helper1-dev libsnappy-dev opensc
sudo yum install autoconf openssl-devel lzo-devel pkcs11-helper-devel snappy-devel

Note: opensc might be needed to provide pkcs11-tool later on.

OpenVPN

Now we're ready for OpenVPN:

git clone https://github.com/OpenVPN/openvpn.git openvpn-git
cd openvpn-git

autoreconf --install --verbose
PKCS11_HELPER_CFLAGS="-I/opt/pkcs11-helper/include" PKCS11_HELPER_LIBS="-L/opt/pkcs11-helper/lib" \
LZO_CFLAGS="-I/opt/lzo/include" LZO_LIBS="-L/opt/lzo/lib" \
./configure --prefix=/opt/openvpn --enable-iproute2 --enable-password-save \
            --enable-lzo --enable-snappy --enable-pkcs11 --enable-ssl --disable-plugin-auth-pam

make
sudo make install

The easy-rsa scripts have been moved to another Git repository:

git clone https://github.com/OpenVPN/easy-rsa.git easy-rsa-git
git archive --format=tar master easyrsa3 | tar -C /opt/openvpn/ -xf -

OpenVPN relies on a TUN/TAP device to work. While the Linux kernel is providing /dev/tun* (with CONFIG_TUN enabled in the kernel), other operating systems might need some help:

  • Tunnelblick - OpenVPN for MacOS 10.3 and later
  • TUN/TAP driver for MacOS 10.3 and later
  • VTUN - Tunnel suite for various *nix systems (last update 2016-09-18)

PKI

Copy the easy-rsa to a working directory, so we can use them to build a basic PKI:

cp -a /usr/share/easy-rsa .                                    # When installed from a package
cp -a /usr/share/doc/openvpn/examples/easy-rsa/2.0 easy-rsa    # When installed from a package
cp -a /opt/openvpn/share/doc/openvpn/easy-rsa/2.0  easy-rsa    # When installed from source

CA

With that in place we can build the CA:

$ cd easy-rsa
$ sed -i 's/KEY_SIZE=1024/KEY_SIZE=2048/' vars            # Increase keysize, if needed.

$ . ./vars
$ ./clean-all                                             # This will create keys/index.txt 
                                                          # and initialize keys/serial
$ ./build-ca
Generating a 2048 bit RSA private key
writing new private key to 'ca.key'
[...]
Country Name (2 letter code) [US]:
State or Province Name (full name) [CA]:
Locality Name (eg, city) [San Francisco]:
Organization Name (eg, company) []: VPN-Test
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []: ca.example.com
Name []:
Email Address [ca@example.com]: 

Note that the default values are set in ./vars and only the Common Name (CN) had to be entered manually.

Instead of calling build-ca we could do this manually:

./pkitool --interact --initca

which in turn would call:

export KEY_DIR=keys/ KEY_SIZE=2048 KEY_COUNTRY=US KEY_PROVINCE=CA KEY_CITY="San Francisco" \
       KEY_ORG=VPN-Test KEY_OU="" KEY_CN=ca.example.com KEY_NAME="" \
       KEY_EMAIL=ca@example.com PKCS11_MODULE_PATH="" PKCS11_PIN=""

openssl req -days 3650 -nodes -new -newkey rsa:2048 -sha1 -x509 \
        -keyout keys/ca.key -out keys/ca.crt -config ./openssl.cnf
chmod 0400 keys/ca.key

Note:

  • The environment variables are set because of references in openssl.cnf. One could build a new openssl.cnf with all these variables already set.
  • To unset these variables (which are also set when ./vars is sourced) use:
unset `env | awk -F= '/^KEY_/ {print $1}'`

Server

With the CA in place we can generate a certificate and a key for the server:

$ ls -lgotr keys/
total 12
-rw------- 1    0 Jul 15 13:01 index.txt
-rw------- 1    3 Jul 15 13:01 serial
-rw------- 1 1675 Jul 15 13:02 ca.key                            # Root CA key (secret)
-rw------- 1 1545 Jul 15 13:02 ca.crt                            # Root CA certificate

$ ./build-key-server alice
Generating a 2048 bit RSA private key
writing new private key to 'alice.key'
[...]
Country Name (2 letter code) [US]:
State or Province Name (full name) [CA]:
Locality Name (eg, city) [San Francisco]:
Organization Name (eg, company) [VPN-Test]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []: alice.example.com
Name []:
Email Address []: alice@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:                                         # Optional, can be empty
An optional company name []:                                     # Optional, can be empty

Using configuration from ../openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'US'
stateOrProvinceName   :PRINTABLE:'CA'
localityName          :PRINTABLE:'San Francisco'
organizationName      :PRINTABLE:'VPN-Test'
commonName            :PRINTABLE:'alice.example.com'
emailAddress          :IA5STRING:'alice@example.com'
Certificate is to be certified until Jul 13 20:10:00 2022 GMT (3650 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

Let's see what has been added:

$ ls -lgotr keys/

-rw------- 1    0 Jul 15 13:01 index.txt.old
-rw------- 1    3 Jul 15 13:01 serial.old
-rw------- 1 1675 Jul 15 13:02 ca.key
-rw------- 1 1545 Jul 15 13:02 ca.crt
-rw------- 1 1675 Jul 15 13:09 alice.key       # Server key (secret)
-rw------- 1 1021 Jul 15 13:09 alice.csr       # Server certificate request
-rw------- 1    3 Jul 15 13:10 serial
-rw------- 1   21 Jul 15 13:10 index.txt.attr
-rw------- 1  105 Jul 15 13:10 index.txt
-rw------- 1 5214 Jul 15 13:10 alice.crt       # Server certificate
-rw------- 1 5214 Jul 15 13:10 01.pem

Instead of calling build-key-server we could do this manually:

./pkitool --interact --server alice

which in turn would call:

export KEY_DIR=keys/ KEY_SIZE=2048 KEY_COUNTRY=US KEY_PROVINCE=CA KEY_CITY="San Francisco" \
       KEY_ORG=VPN-Test KEY_OU="" KEY_CN=alice.example.com KEY_NAME="" \ 
       KEY_EMAIL=alice@example.com PKCS11_MODULE_PATH="" PKCS11_PIN=""

openssl req -days 3650 -nodes -new -newkey rsa:2048 -keyout keys/alice.key -out keys/alice.csr \
        -extensions server -config ./openssl.cnf
chmod 0400 keys/alice.key

touch keys/index.txt
echo 01 > keys/serial

openssl ca -days 3650 -out keys/alice.crt -in keys/alice.csr -extensions server \
        -md sha1 -config ./openssl.cnf

Client

The clients need also certificates and keys:

$ ./build-key client-1                  # Replace with build-key-pass to password-protect the client keys!
Generating a 2048 bit RSA private key
writing new private key to client-1.key'

Enter PEM pass phrase: ***              # Only if build-key-pass is used!
Verifying - Enter PEM pass phrase: ***

Country Name (2 letter code) [US]:
State or Province Name (full name) [CA]:
Locality Name (eg, city) [San Francisco]:
Organization Name (eg, company) [VPN-Test]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) [client-1.example.com]:
Name []: 
Email Address []: client-1@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Using configuration from ../openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'US'
stateOrProvinceName   :PRINTABLE:'CA'
localityName          :PRINTABLE:'San Francisco'
organizationName      :PRINTABLE:'VPN-Test'
commonName            :PRINTABLE:'client-1.example.com'
emailAddress          :IA5STRING:'client-1@example.com'
Certificate is to be certified until Jul 13 20:19:21 2022 GMT (3650 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

Let's see what has been added/changed:

$ ls -lgotr keys/
total 80

-rw------- 1 1675 Jul 15 13:02 ca.key
-rw------- 1 1545 Jul 15 13:02 ca.crt
-rw------- 1 1675 Jul 15 13:09 alice.key
-rw------- 1 1021 Jul 15 13:09 alice.csr
-rw------- 1    3 Jul 15 13:10 serial.old
-rw------- 1  105 Jul 15 13:10 index.txt.old
-rw------- 1   21 Jul 15 13:10 index.txt.attr.old
-rw------- 1 5214 Jul 15 13:10 alice.crt
-rw------- 1 5214 Jul 15 13:10 01.pem
-rw------- 1 1679 Jul 15 13:19 client-1.key    # Client key (secret)
-rw------- 1 1025 Jul 15 13:19 client-1.csr    # Client certificate request
-rw------- 1    3 Jul 15 13:19 serial
-rw------- 1   21 Jul 15 13:19 index.txt.attr
-rw------- 1  212 Jul 15 13:19 index.txt
-rw------- 1 5099 Jul 15 13:19 client-1.crt    # Client certificate
-rw------- 1 5099 Jul 15 13:19 02.pem

Instead of calling build-key we could do this manually:

./pkitool --interact client-1

which in turn would call:

export KEY_DIR=keys/ KEY_SIZE=2048 KEY_COUNTRY=US KEY_PROVINCE=CA KEY_CITY="San Francisco" \
       KEY_ORG=VPN-Test KEY_OU="" KEY_CN=client-1.example.com KEY_NAME="" \
       KEY_EMAIL=client-1@example.com PKCS11_MODULE_PATH="" PKCS11_PIN=""

openssl req -days 3650 -nodes -new -newkey rsa:2048 -keyout keys/client-1.key -out keys/client-1.csr \
        -config ./openssl.cnf
chmod 0400 keys/client-1.key

openssl ca -days 3650 -out keys/client-1.crt -in keys/client-1.csr -md sha1 -config ./openssl.cnf

Or, when using build-key-pass:

./pkitool --interact --pass client-1

which in turn would call:

export [...]
openssl req -days 3650 -new -newkey rsa:2048 -keyout keys/client-1.key -out keys/client-1.csr \
        -config ./openssl.cnf
chmod 0400 keys/client-1.key

openssl ca -days 3650 -out keys/client-1.crt -in keys/client-1.csr -md sha1 -config ./openssl.cnf

DH

OpenVPN also needs Diffie Hellman parameters:

$ ./build-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time

$ ls -ltrgo keys/
[...]
-rw------- 1  424 Jul 15 14:01 dh2048.pem

Instead of calling build-dh we could do this manually:

openssl dhparam -out keys/dh2048.pem 2048

TLS-Auth

When using tls-auth, all SSL/TLS handshake packets get an additional HMAC signature (hash-based message authentication code) for integrity verification. For this to work we need a shared-secret key:

openvpn --genkey --secret keys/ta.key
chmod 0400 keys/ta.key

The resulting ta.key needs to be copied to the involved clients and servers over a secure channel.

Configuration

Server

The following is a many-clients ←→ one-server configuration (server.conf):

;local      192.168.0.10           # Can be used to listen on a specific interface
port        1194
proto       udp                    # Use udp as it provides better protection against 
                                   # DoS attacks and port scanning than tcp.
dev         tun                    # TUN works with IP, TAP works with Ethernet frames.
                                   # See Documentation/networking/tuntap.txt
ca          ca.crt
cert        alice.crt
key         alice.key
dh          dh2048.pem
tls-auth    ta.key 0               # Set to 0 on the server and 1 on the clients

cipher      AES-128-GCM            # See --show-ciphers
auth        SHA256                 # See --show-digests
 
server      10.1.0.0 255.255.255.0 # The server will take 10.1.0.1
;ifconfig-pool-persist ipp.txt 0   # Maintain a (readonly) list of client <=> virtual IP addresses

keepalive   10 120                 # Send keepalive messages every 10 seconds, assume timeout after 120 seconds
compress    lz4
max-clients 10                     # Allow 10 clients to connect concurrently

user        nobody                 # Drop root privileges after initialization
group       nogroup 

persist-key                        # After restarting the tunnel, try to avoid accessing files that
persist-tun                        # may no longer be available after dropping privileges
;client-to-client                  # Route client-to-client traffic internally, not over the TUN/TAP interface

# Push default route, name servers
;push       "redirect-gateway def1 bypass-dhcp"
;push       "dhcp-option DNS 208.67.222.222"
;push       "dhcp-option DNS 208.67.220.220"

status      /var/log/openvpn/openvpn-status.log
log         /var/log/openvpn/server.log    # Use log-append to append rather than truncating the logfile
verb        3                      # Verbosity (0-9)
;mute       20                     # Silence repeating (20) messages

Client

The following is a client configuration to connect to a multi-client server (client-1.conf):

client
dev         tun                    # Same as the server
proto       udp                    # Same as the server
remote      192.168.0.10 1194      # See local and port on the server

ca          ca.crt
cert        client.crt
key         client.key
tls-auth    ta.key 1               # Set to 1 as we are a client

cipher      AES-128-GCM            # See --show-ciphers
auth        SHA256                 # See --show-digests

remote-cert-tls server             # Verify the remote certificate has been issued with
                                   # extendedKeyUsage=serverAuth[2]

;ns-cert-type server               # Verify that nsCertType is set to server[3] to prevent
                                   # MITM attacks

compress    lz4
nobind                             # We don't need to bind to a specific port number
;resolv-retry infinite             # Try indefinitely to resolve the server's hostname

;user       nobody                 # Drop root privileges after initialization
;group      nogroup

persist-key                        # Try to preserve some state across restarts.
persist-tun

;dhcp-option DNS    192.168.0.1    # Add a local DNS server, so that clients can be resolved
;dhcp-option DOMAIN mydomain.org   # Add a local domain name, so that clients can be resolved w/o their FQDN 

log         client.log             # Use log-append to append rather than truncating the logfile
writepid    client.pid             # Ignored on Android
verb        3                      # Verbosity (0-9)
;mute       20                     # Silence repeating (20) messages
;mute-replay-warnings              # Silence duplicate packet warnings (for WiFi networks)

Note: while these dhcp-option[4] directives appear to be useful, they will cause (if used as above) DNS leaks[5][6]. Be sure you know what you're doing before using these options!

Usage

On the server:

openvpn /etc/openvpn/server.conf

This will create a tunnel device:

$ ip addr show dev tun0
6: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
   link/none 
   inet 10.1.0.1 peer 10.1.0.2/32 scope global tun0

On the client:

openvpn client.conf

The tunnel device on the client:

$ ifconfig tun0
tun0: flags=8851<UP,POINTOPOINT,RUNNING,SIMPLEX,MULTICAST> mtu 1500
       inet 10.1.0.6 --> 10.1.0.5 netmask 0xffffffff 
       open (pid 8584)

Notes:

  • The peer address (10.1.0.2 resp. 10.1.0.5) seems to be a virtual address only and cannot be reached via ping(8).
  • On a systemd machine, one has to enable and start the correct service:
systemctl enable openvpn@server
systemctl start openvpn@server

Links


References