The OpenVPN traffic isn't exactly like HTTPS traffic, so a sufficiently advanced firewall can detect OpenVPN traffic and block it, even though you may be running OpenVPN on TCP port 443. Here we describe a method to "scramble" the OpenVPN traffic, making it unrecognizable by such firewalls.
The official OpenVPN release does not support this feature. We need to apply a controversial, yet handy patch in order to add this capability to our OpenVPN setup. The patch in question, commonly known as "XOR patch", was proposed in 2013 and for some reasons that are outside of the scope of this writting, was never accepted into the main branch by the OpenVPN maintainers. We use the Tunnelblick project's version of the patch, which is more robust and resolves some issues with the original patch.
This patch must be applied to both the OpenVPN server and the client.
In this tutorial, we'll be using two servers, A and B, which are located in different geographical regions. It is assumed that a DPI firewall is located between A and B, dropping all OpenVPN traffic. We will present a way to bypass this DPI firewall and connect A to B. The only VPN client for B is A, but an arbitary number of users can connect to A. Ultimately their traffic will be routed to B, as if they are directly connected to B.
First of all, download the Tunnelblick distfile, unpack it, and cd into the specified directory
$ wget -c https://github.com/Tunnelblick/Tunnelblick/archive/refs/heads/master.zip -O tunnelblick.zip $ unzip tunnelblick.zip $ cd Tunnelblick-master/third_party/sources/openvpn/openvpn-2.5.6
Here you can find different versions of OpenVPN releases. We will be using v2.5.6. Unpack it:
$ tar zxvf openvpn-2.5.6.tar.gz
Now cd into the patches subdirectory, and apply the patches, one by one, and in order:
$ cp patches/*.diff openvpn-2.5.6 $ cd openvpn-2.5.6 $ patch -p1 < 02-tunnelblick-openvpn_xorpatch-a.diff $ patch -p1 < 03-tunnelblick-openvpn_xorpatch-b.diff $ patch -p1 < 04-tunnelblick-openvpn_xorpatch-c.diff $ patch -p1 < 05-tunnelblick-openvpn_xorpatch-d.diff $ patch -p1 < 06-tunnelblick-openvpn_xorpatch-e.diff
Before going to the next step, which is compiling the application, we need to install all of the dependencies. You need OpenSSL (or any other compatible SSL library), a C compiler and other build tools.
# apt install -y libssl-dev liblzo2-dev libpam0g-dev make automake gcc libpkcs11 pkg-config libsystemd-dev
You are ready to compile OpenVPN.
$ ./configure --enable-strict --enable-systemd --disable-async-push --disable-lz4 --disable-lzo \ --disable-small --disable-unit-tests --disable-x509-alt-username --enable-shared --disable-debug \ --disable-plugin-auth-pam --disable-dependency-tracking $ make
And no error has been shown on your terminal, you can install the application by running the following command. Unless you have specified a different prefix, it will install the OpenVPN to /usr/local/sbin/openvpn
$ make install
Now install the easy-rsa package, and initialize the configuration directory:
$ apt install -y easy-rsa $ mkdir -p /etc/openvpn/{server,client} $ cp -r /usr/share/easy-rsa /etc/openvpn/ $ cd /etc/openvpn/easy-rsa
Create the config file, and put the following config inside:
$ vi vars
set_var EASYRSA_DN "cn_only" set_var EASYRSA_ALGO ec set_var EASYRSA_CURVE secp384r1 set_var EASYRSA_CA_EXPIRE 3650 set_var EASYRSA_CERT_EXPIRE 3650
Initialize the public key infrastructure and build your CA:
$ ./easyrsa init-pki $ ./easyrsa build-ca
(You will be prompted for a password, use a strong password and keep it somewhere safe. When asked for Common Name, put anything you like. Here for demonestration, I'll use vpnserv.example.com)
$ ./easyrsa build-server-full vpnserv.example.com nopass $ ./easyrsa gen-dh
(Enter the password from the previus step here)
This will generate some files, copy them into a separate directory to make their maintenance more easy:
$ mkdir /etc/openvpn/keys $ cd /etc/openvpn/keys $ cp ../easy-rsa/pki/ca.crt . $ cp ../easy-rsa/pki/dh.pem . $ cp ../easy-rsa/pki/issued/vpnserv.example.com.crt . $ cp ../easy-rsa/pki/private/vpnserv.example.com.key .
Now create /etc/openvpn/server.conf:
port 443 proto tcp dev tun dh /etc/openvpn/keys/dh.pem ca /etc/openvpn/keys/ca.crt cert /etc/openvpn/keys/vpnserv.example.com.crt # change me key /etc/openvpn/keys/vpnserv.example.com.key # change me tls-version-min 1.2 tls-cipher ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384 topology subnet server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt push "redirect-gateway def1 bypass-dhcp" keepalive 10 120 # max simultaneous clients max-clients 20 user nobody group nobody persist-key persist-tun verb 3 mute 10 fast-io
And create /etc/openvpn/client-common.txt:
client dev tun proto tcp remote vpnserv.example.com 443 #change me nobind resolv-retry infinite persist-key persist-tun mute-replay-warnings remote-cert-tls server auth SHA512 cipher AES-256-GCM ignore-unknown-option block-outside-dns verb 3 mute 10
Generate a key for scrambling obfuscation, this will give you a 32-character string, which is a base64 representation of a 24-byte key. Keep it somewhere safe:
$ openssl rand -base64 24 WRM4dF+Ydo6ec1fV3VO1qvzVQrp8NDrE
Add this line to both /etc/openvpn/server.conf and /etc/openvpn/client-common.txt, replace the key from last step:
scramble obfuscate WRM4dF+Ydo6ec1fV3VO1qvzVQrp8NDrE
To better manage the OpenVPN service, we will proceed to create a systemd config. Head back to OpenVPN build directory and run:
$ cp distro/systemd/openvpn-server@.service.in /lib/systemd/system/openvpn.service
Edit the file and on line 13, change @sbindir@ to /usr/local/sbin/
Finally you can create any number of clients with the following script. The first param is an arbitary name for the client, and the second param specifies the number of days the certificate remains valid. You can use this script like this:
$ ./make_cli.sh my_cli 365
Here's the script. The resulting client config will be saved in your home directory.
#!/bin/sh ETCDIR=/etc/ errx() { echo 'ERROR:' $1 exit 1 } if [ $# -nq 2 ]; then echo "ERROR: invalid number of arguments" echo -e "Usage:" echo -e "\t${0} client_name ndays" exit 1; fi EASYDIR=${ETCDIR}/easy-rsa CLIENT=${1} EASYRSA_CERT_EXPIRE=${2} if [ -d ${EASYDIR} ]; then cd ${EASYDIR} else echo "ERROR: ${EASYDIR} doesn't exist" exit 1 fi if [ -x ${EASYDIR}/easyrsa ]; then ${EASYDIR}/easyrsa build-client-full ${CLIENT} nopass || errx else echo "ERROR: Can't find easyrsa binary" exit 1 fi { cat ${ETCDIR}/openvpn/client-common.txt echo "" cat ${ETCDIR}/openvpn/easy-rsa/pki/ca.crt echo " " echo "" sed -ne '/BEGIN CERTIFICATE/,$ p' ${ETCDIR}/openvpn/easy-rsa/pki/issued/"$1".crt echo " " echo "" cat ${ETCDIR}/openvpn/easy-rsa/pki/private/"$1".key echo " " } > ~/"$1".ovpn
Enable IP forwarding and start the server:
# echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf # sysctl -p # service openvpn start
And finally use this configuration for your firewall
#!/bin/sh int_if="tun0" ext_if="ens160" iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o $ext_if -j MASQUERADE iptables -A INPUT -i $int_if -j ACCEPT iptables -A FORWARD -i $ext_if -o $int_if -j ACCEPT iptables -A FORWARD -i $int_if -o $ext_if -j ACCEPT iptables -A INPUT -i $ext_if -p tcp --dport 443 -j ACCEP
Create a tunnel by connecting, this will connect server A to B:
# openvpn -client --config cli.ovpn
Additionally, you need to configure your firewall to route clients of server A to server B through the VPN tunnel. In the following iptables configuration, tun0 is the interface through which users are connected to server A, and tun1 is the tunnell to server B:
#!/bin/sh int_if="tun0" ext_if="tun1" iptables -t nat -A POSTROUTING -s 10.9.0.0/24 -o $ext_if -j MASQUERADE iptables -A INPUT -i $int_if -j ACCEPT iptables -A FORWARD -i $ext_if -o $int_if -j ACCEPT iptables -A FORWARD -i $int_if -o $ext_if -j ACCEPT iptables -A INPUT -i $ext_if -p tcp --dport 443 -j ACCEP
Now you are ready to route the traffic:
# ip rule add from 10.9.0.0/24 table 10 # ip route add default via 10.8.0.1 table 10As it is assumed that the DPI firewall is located between A and tun1, only the VPN tunnel from A to tun1 needs scrambling.