How to connect your Android devices to an OpenVPN VPN
There are various OpenVPN configuration tutorials around the Internet, this post aims to fill in the gaps on how to configure the OpenVPN server, and OpenVPN for Android clients, while managing a simple firewall configured with UFW running an Arch Linux system.
Introduction
OpenVPN is a robust and highly flexible VPN daemon. OpenVPN supports SSL/TLS security, Ethernet bridging, TCP or UDP tunnel transport through proxies or NAT, support for dynamic IP addresses and DHCP, scalability to hundreds or thousands of users, and portability to most major OS platforms. Further details about OpenVPN (and advanced tips on how to to configure it) can be found in the ArchWiki OpenVPN entry.
Although OpenVPN is relatively popular among FOSS enthusiasts, the Android project does not officially support OpenVPN natively. Hopefully, the ics-openvpn (Google Play) use the Android VPN APIs to provide this functionality.
Background
This post assumes an Arch Linux system will be used to configure the OpenVPN server. It also assumes the Uncomplicated Firewall (UFW) will be used as an iptables front-end.
For information on how to configure OpenVPN on dd-wrt-enabled routers, be sure to read dd-wrt's wiki.
Installing the dependencies
The openvpn package provides the OpenVPN binary, corresponding libraries and sample configuration files. The easy-rsa package provides a set of scripts that make it easy to maintain a Public-Key Infrastructure (PKI) system, which will be required for generating the certificates for the OpenVPN server and clients. The ufw package provides ufw, the uncomplicated firewall.
Installing all these packages is as easy as typing (as root)1:
# pacman -S openvpn easy-rsa ufw
resolving dependencies...
looking for inter-conflicts...
Packages (3): easy-rsa-2.2.2-2 openvpn-2.3.6-1 ufw-0.33-3
Total Download Size: 0.49 MiB
Total Installed Size: 1.54 MiB
Net Upgrade Size: 0.00 MiB
:: Proceed with installation? [Y/n]
:: Retrieving packages ...
openvpn-2.3.6-1-x86_64 344.1 KiB 1110K/s 00:00 [############################] 68%
easy-rsa-2.2.2-2-any 363.9 KiB 994K/s 00:00 [############################] 72%
ufw-0.33-3-any 504.0 KiB 951K/s 00:01 [############################] 100%
(3/3) checking keys in keyring [############################] 100%
(3/3) checking package integrity [############################] 100%
(3/3) loading package files [############################] 100%
(3/3) checking for file conflicts [############################] 100%
(3/3) checking available disk space [############################] 100%
(1/3) installing ufw [############################] 100%
(2/3) installing openvpn [############################] 100%
(3/3) installing easy-rsa [############################] 100%
Enabling IP forwarding
Since we want traffic from the VPN to be forwarded by the server, we have to
configure IP forwarding. To do so, we have to create the file
/etc/sysctl.d/ipforward.conf
with the contents
net.ipv4.ip_forward = 1
And either reboot or call
# sysctl -w net.ipv4.ip_forward=1
Configuring UFW
You will probably want UFW to deny unmatched packets by default, and to allow only OpenVPN and other traffic you might be interested in (like ssh). The following commands will enable OpenVPN and http traffic, for example:
# ufw allow 1194/udp
# ufw allow ssh
We will also have to edit the file /etc/ufw/before.rules
to include (after
the header comments and before the *filter
line) the following contents:
# nat Table rules
*nat
:POSTROUTING ACCEPT [0:0]
# Forward traffic through ppp0 - Change to match you out-interface
-A POSTROUTING -s 10.8.254.0/24 -o eth0 -j MASQUERADE
# don't delete the 'COMMIT' line or these nat table rules won't be processed
COMMIT
Last, but not least, we'll have to change the default forward policy from DROP
to ACCEPT by editing the file /etc/default/ufw
and setting
DEFAULT_FORWARD_POLICY
to ACCEPT
.
Starting and enabling UFW
After UFW is properly configured, we will have to enable it, and to tell the system to start it by default by executing the following commands (only once):
# ufw enable
# systemctl start ufw
# systemctl enable ufw
Configuring the Public-Key Infrastructure
Initializing the PKI
Some tutorials recommend setting up easy-rsa in its installation directory. I'd recommend actually copying it to a user's directory and setting it up there.
First we'll copy easy-rsa to a directory we control:
$ cp -fr /usr/share/easy-rsa/ ~
$ chmod 700 ~/easy-rsa/
$ cd ~/easy-rsa
The vars
file holds various configuration entries for using easy-rsa. Because
of that, it may be worth editing it to use your preferred values as a default.
The variables you may want to edit to change the certificates’ default fields
are. Other fields are related to key sizes and may be left at their default
values:
KEY_COUNTRY # The country the Certificate Authority is in
KEY_PROVINCE # The province or state the Certificate Authority is in
KEY_CITY # The city the Certificate Authority is in
KEY_ORG # Your organization
KEY_EMAIL # The Certificate Authority's administrator's email
KEY_OU # Your organizational unit
After vars
is configured, we can source it,
$ source vars
NOTE: If you run ./clean-all, I will be doing a rm -rf on ~/easy-rsa/keys
Initilize it,
$ ./clean-all
And create the Certificate Authority,
$ ./build-ca
Generating a 2048 bit RSA private key
..........................+++
..............+++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [BR]:
State or Province Name (full name) [SP]:
Locality Name (eg, city) [SaoPaulo]:
Organization Name (eg, company) [ExampleCom]:
Organizational Unit Name (eg, section) [ACME]:
Common Name (eg, your name or your server's hostname) [ExampleCom CA]:
Name [EasyRSA]:
Email Address [[email protected]]:
And the Diffie-Hellman key exchange parameters, which can take a long time or idle-ish computers (consider using an entropy harvesting daemon, such as haveged, to speed things up here):
$ ./build-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time # rest of output omitted
After this step we have a Certificate Authority. Nice! Now on to generating the various keys and certificates we need…
Generating server keys & certificates
Generating a server key is as easy as typing ./pkitool --server $SERVER_NAME
.
So, if we want to generate a key for server example.com, all we have to do is:
$ ./pkitool --server example.com
Generating a 2048 bit RSA private key
....+++
writing new private key to 'example.com.key'
-----
Using configuration from ~/easy-rsa/openssl-1.0.0.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName :PRINTABLE:'BR'
stateOrProvinceName :PRINTABLE:'SP'
localityName :PRINTABLE:'SaoPaulo'
organizationName :PRINTABLE:'ExampleCom'
organizationalUnitName:PRINTABLE:'ACME'
commonName :PRINTABLE:'example.com'
name :PRINTABLE:'EasyRSA'
emailAddress :IA5STRING:'[email protected]'
Certificate is to be certified until Dec 25 17:23:17 2024 GMT (3650 days)
Write out database with 1 new entries
Data Base Updated
Generating client keys & certificates
Generating client keys and certificates is even easier than generating server
keys. In this post we will create only such pair, but the process can be
repeated for as many clients as you want (if you don't use OpenVPN's
duplicate-cn
configuration option).
$ ./pkitool example-client
Generating a 2048 bit RSA private key
.+++
writing new private key to 'example-client.key'
-----
Using configuration from ~/easy-rsa/openssl-1.0.0.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName :PRINTABLE:'BR'
stateOrProvinceName :PRINTABLE:'SP'
localityName :PRINTABLE:'SaoPaulo'
organizationName :PRINTABLE:'ExampleCom'
organizationalUnitName:PRINTABLE:'ACME'
commonName :PRINTABLE:'example-client'
name :PRINTABLE:'EasyRSA'
emailAddress :IA5STRING:'[email protected]'
Certificate is to be certified until Dec 25 17:27:58 2024 GMT (3650 days)
Write out database with 1 new entries
Data Base Updated
Enabling an “HMAC firewall”
By default, OpenVPN, may be vulnerable to DoS2 attacks and UDP port flooding. Generating a random key to be used as a shared secret helps in increasing OpenVPN's resiliency to these attacks. To generate such a key, do:
$ openvpn --genkey --secret keys/ta.key
Now we have everything we need to configure OpenVPN. Let's do it!
Configuring OpenVPN
OpenVPN comes with an extensive set of well-documented set of example
configuration files. All of which can be found on
/usr/share/openvpn/examples/
. In that directory, the server.conf
file can
be found. I recommend you read the comments in that file (and try to understand
what they mean), as we'll take a more succinct approach in this post.
We will assume in this post that the OpenVPN daemon will run under the nobody
user and that the keys and certificates will be saved under
/etc/openvpn/keys
. We'll also assume the keys were generated in the same
server OpenVPN will be running. This may not be the case, so you might have to
replace the cp
calls below to some combination of scp
and ssh
.
Copying the keys & certificates
The following “script”, when executed by root, will copy the files OpenVPN needs to operate as a server.
# for file in ca.crt example.com.crt example.com.key ta.key dh2048.pem
do
cp ~/easy-rsa/keys/${file} /etc/openvpn/keys/
done
$ chown -R nobody.nogroup /etc/openvpn/keys
$ chmod 700 /etc/openvpn/keys
$ chmod 600 /etc/openvpn/keys/*
Configuring the OpenVPN server
You can either copy /usr/share/openvpn/examples/server.conf
to
/etc/openvpn
, as mentioned above, and configure as you see fit, or configure
it as below:
port 1194 # Listen on OpenVPN's default port
proto udp # Use UDP (instead of TCP)
dev tun # Use a TUN device (instead of TAP)
ca keys/ca.crt # Path to the Certificate Authority's public certificate
cert keys/example.com.crt # This server's certificate
key keys/example.com.key # This server's private key
dh keys/dh2048.pem # The Diffie-Hellman key-exchange parameters
topology subnet # Subnet topology with IP addressing
server 10.8.254.1 255.255.255.0 # Server IP and netmask
ifconfig-pool-persist ipp.txt # persistent client <-> IP "database"
push "redirect-gateway def1 bypass-dhcp" # define this as the default gateway
;duplicate-cn # uncomment if you want clients with duplicate certificates
keepalive 10 120 # ping every 10 seconds, keep alive for up to 120 seconds
tls-auth keys/ta.key 0 # Shared secret, server mode
cipher BF-CBC # Use blowfish in CBC mode as encryption algorithm
comp-lzo # Use LZO compression
user nobody # Run as the "nobody" user
group nobody # Run as the "nobody" group
persist-key # Persist data in memory upon restart
persist-tun # Ditto
status openvpn-status.log # Short status file location
verb 3 # Verbosity of the logs
Configuring the Android clients
Configuring an Android client is no different than configuring a regular client.
The OpenVPN unified configuration format
The key here is to make the user's lives easier by bundling all key and
certificate files into a single OpenVPN (*.ovpn
) file. The key is to use the
OpenVPN unified format, which allows the embedding of these files’ contents.
So, if a traditional OpenVPN configuration file has entries like3
ca ca.crt
cert client.crt
key client.key
tls-auth ta.key 1
A unified OVPN file will have the following entries:
<ca>
-----BEGIN CERTIFICATE-----
MIIBszCCARygAwIBAgIE...
. . .
Lq9iNBNgWg==
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
. . .
</cert>
<key>
-----BEGIN RSA PRIVATE KEY-----
. . .
</key>
key-direction 1
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
. . .
</key>
Preparing the configuration file
A base client configuration file that will work with the server we have just
configured will look like the file below (where ${SERVER_ADDRESS}
is the
actual server's host name or IP address):
client
dev tun
remote ${SERVER_ADDRESS}
resolv-retry infinite
nobind
persist-key
persist-tun
ca [inline]
cert [inline]
key [inline]
tls-auth [inline] 1
verb 1
keepalive 10 120
port 1194
proto udp
cipher BF-CBC
comp-lzo
remote-cert-tls server
<ca>
...
</ca>
<cert>
...
</cert>
<key>
...
</key>
<tls-auth>
...
</tls-auth>
The ellipses above will have to be substituted by the files’ contents. One easy way to do so is to use the ovpn-writer.sh script like the following:
$ cd ~/easy-rsa
$ wget https://gist.github.com/renatolfc/18e428b5a758df24455b/download -O ovpn-writer.sh
$ chmod +x ovpn-writer.sh
$ ./ovpn-writer.sh example.com ~/easy-rsa/keys/{ca.crt,example-client.crt,example-client.key,ta.key} > android-example.ovpn
This will generate the android-example.ovpn output file.
After the client files have been generated, you have to to send them to your service's user via some kind of secure channel. Selecting the channel is out of the scope of this document.
Importing the configuration file on Android
Alright! So the user has the ovpn files sitting right in his/her SD card. After OpenVPN for Android is installed, and started, one will be presented by the main OpenVPN for Android interface. Tapping the folder icon will bring a file selection dialog, which, after having selected the file, will present a screen similar to the image below.
Selecting the android-example
profile will bring a warning dialog as depicted
below. One has to accept that OpenVPN will be able to intercept all network
traffic to continue.
After a successful connection, the key icon, highlighted in the screenshot below will be present in Android's notification bar. Tapping that icon will display statistics about the connection, as shown below:
And that's it! By now you should have a working OpenVPN server and a client running on Android.
As you may be aware, this assumes the local package database is updated, and will only download and install the new packages. You might need to combine the -y and the -u options. (Which will sync the package database and update all packages.) Tweak the command line as needed.↩
Some journalists say this is a form of hacking. As @SwiftOnSecurity would say: Please stop calling DDoS attacks, “hacker attacks.” That's like calling taking a dump, “biological terrorism.”↩
Taken from OpenVPN for Android FAQ.↩