Javad's mail server guide

Setting up OpenVPN server with XOR patch


Introduction

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.

Server B configuration

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

Server A configuration

You need to perform the above steps on server B too. This time, use another IP range for your clients, I'll be using 10.9.0.0/24.

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 10
As it is assumed that the DPI firewall is located between A and tun1, only the VPN tunnel from A to tun1 needs scrambling.
powered by FreeBSD logo powered by vi logo Valid HTML 4.0!


This page was edited on Sat Jun 11 04:02:03 EEST 2022