Virtual private networking

Authors: Mikhail Zolotukhin and Timo Hämäläinen

1. Introduction

Suspecting her HTTP sessions being hijacked, Alice may decide to configure a VPN tunnel with the web server to protect her data. This tutorial explains how to configure simple VPN server and client, set up your own Certificate Authority (CA), generate keys and sign certificates. For this tutorial, you are supposed to configure the virtual network described in our previous tutorials. The remainder of this tutorial is organized as follows. Preliminary questions are listed in Section 2. Section 3 describes how to configure a public key infrastructure. The configuration of VPN server and client is presented in Section 4. Assignments are listed in Section 5. Section 6 concludes the tutorial.

This tutorial (including assignments) takes on average 8.13 hours to complete.

2. Preliminary questions

  • Explain what is X.509 and its relation to Public Key Infrastructure (PKI).
  • Read article "The X.509 trust model needs a technical and legal expert". Why is X.509 trust model complex and how does this paper try to make it easier?
  • Compare cryptography algorithms RSA and DSA. Which one is better? What are their pros and cons?
  • What is Diffie-Hellman key exchange, what is its purpose in PKI?
  • What is HMAC signature, what is its purpose in PKI?

3. Public key infrastructure with EasyRSA

The first step in building an OpenVPN configuration is to establish a public key infrastructure (PKI). Usually PKI consists of a separate certificate (also known as a public key), private key for the server and each client, a master Certificate Authority (CA) certificate and key which is used to sign each of the server and client certificates. OpenVPN supports bidirectional authentication based on certificates, meaning that the client must authenticate the server certificate and the server must authenticate the client certificate before mutual trust is established. Both the server and the client will authenticate the other by first verifying that the presented certificate was signed by the master certificate authority (CA), and then by testing information in the now-authenticated certificate header, such as the certificate common name or certificate type (client or server). This security model has a number of desirable features from the VPN perspective. First, the server only needs its own certificate/key - it doesn't need to know the individual certificates of every client which might possibly connect to it. Second, the server will only accept clients whose certificates were signed by the master CA certificate. Since the server can perform this signature verification without needing access to the CA private key itself, it is possible for the CA key to reside on a completely different machine, even one without a network connection. If a private key is compromised, it can be disabled by adding its certificate to the certificate revocation list (CRL). The CRL allows compromised certificates to be selectively rejected without requiring that the entire PKI be rebuilt. The server can enforce client-specific access rights based on embedded certificate fields, such as common name. Note that the server's and client's clocks need to be roughly in sync or certificates might not work properly.

3.1 CA

First, we create our CA on the webserv-VM.

  1. Run following virtual machines (VMs): gateway, dnsserv, alice-VM and webserv.
  1. On webserv-VM, use the following command to install OpenVPN:

    $ sudo apt install openvpn
  1. Download the latest version of EasyRSA (by the time you complete the tutorial, the latest version may change, you can use either version 3.1.0 or the real latest version, but there is no guaranty that it will work exactly as described in the tutorial, e.g. some directory names may change):

    $ wget https://github.com/OpenVPN/easy-rsa/releases/download/v3.1.0/EasyRSA-3.1.0.tgz

    extract the archive:

    $ tar xvf EasyRSA-3.1.0.tgz

    and change directory to EasyRSA-3.1.0:

    $ cd EasyRSA-3.1.0

    Further in the tutorial, we assume that you work from this directory, otherwise, you should modify paths in the commands below.

  1. Copy file vars.example:

    $ cp vars.example vars
  1. Open file vars:

    $ nano vars

    find section:

    ...
    #set_var EASYRSA_REQ_COUNTRY    "US"
    #set_var EASYRSA_REQ_PROVINCE   "California" 
    #set_var EASYRSA_REQ_CITY       "San Francisco"
    #set_var EASYRSA_REQ_ORG        "Copyleft Certificate Co"
    #set_var EASYRSA_REQ_EMAIL      "me@example.net"
    #set_var EASYRSA_REQ_OU         "My Organizational Unit"
    ...

    uncomment those lines and modify them as you like, e.g.

    ...
    set_var EASYRSA_REQ_COUNTRY    "FI"
    set_var EASYRSA_REQ_PROVINCE   "Central Finland" 
    set_var EASYRSA_REQ_CITY       "Jyväskylä"
    set_var EASYRSA_REQ_ORG        "University of Jyväskylä"
    set_var EASYRSA_REQ_EMAIL      "ties327@tim.jyu.fi"
    set_var EASYRSA_REQ_OU         "Ties327"
    ...
  1. Initialize PKI, the command will create all necessary directories:

    $ ./easyrsa init-pki
  1. Now, we are ready to create CA certificates. However, in the current version of EasyRSA (tested on 29.9.2022), there is a bug with vars file. We have to rename the new vars in pki directory to something else, otherwise EasyRSA will throw an error:

    $ mv pki/vars pki/vars_new

    After that, execute:

    $ ./easyrsa build-ca nopass

    When asked about common name, just press Enter to use the default name Easy-RSA CA. We are using "nopass" (no password) directive for the sake of demonstration to avoid entering the password every time we interact with the CA. When configuring a real-world CA, we would like to add some password for better security.

    After completing the last command, you should have two following files: "ca.key" (CA private key) in directory "pki/private/" and "ca.crt" (CA public certificate) in directory "pki/". Check that you have them both.

    $ ls pki/private
    $ ls pki

3.2 Server

Next, we will create a certificate and private key for our web server. Since, in our case, CA and the server are on the same webserv-VM, we do not need to initialize a new PKI, just use the existing one.

  1. Assuming that you are still in directory EasyRSA-3.1.0 on webserv-VM, create signing request for the server using common name "webserv" (you can use any other name):

    $ ./easyrsa gen-req webserv nopass

    and press Enter to confirm the common name. As a result, you should have "webserv.req" in directory "pki/reqs/". Check that it is there.

  1. Since we use the same PKI for both CA and server, we do not have to copy the resulting request anywhere, just sign it with CA's private key:

    $ ./easyrsa sign-req server webserv 

    where "server" is request type (it can be either server or client), and "webserv" is the common name of our request. Confirm the signing by entering "yes". As a result, you should have file "webserv.crt" in directory "pki/issued/".

  1. To create a OpenVPN server, we also need to initialize a Diffie-Hellman key and an HMAC signature:

    $ ./easyrsa gen-dh
    $ openvpn --genkey secret ta.key

    The former will take some time.

  1. Now, we have all files to start an OpenVPN server, copy them to "/etc/openvpn" directory:

    $ sudo cp pki/ca.crt /etc/openvpn/
    $ sudo cp pki/private/webserv.key /etc/openvpn/
    $ sudo cp pki/issued/webserv.crt /etc/openvpn/
    $ sudo cp pki/dh.pem /etc/openvpn/
    $ sudo cp ta.key /etc/openvpn/

3.3 Client

To create PKI for a client, we need to do almost the same steps as we did on the server. The only difference is that we will configure PKI on a VM different than CA and therefore we need to copy signing request and signed certificate between client's and server's VMs using scp.

  1. On alice-VM, use the following command to install OpenVPN:

    $ sudo apt install openvpn
  1. Download EasyRSA:

    $ wget https://github.com/OpenVPN/easy-rsa/releases/download/v3.1.0/EasyRSA-3.1.0.tgz

    extract the archive:

    $ tar xvf EasyRSA-3.1.0.tgz

    and change directory to EasyRSA-3.1.0:

    $ cd EasyRSA-3.1.0

    Further in the tutorial, we assume that you work from this directory, otherwise, you should modify paths in the commands below.

  1. Copy file vars.example:

    $ cp vars.example vars
  1. Open file vars:

    $ nano vars

    find section:

    ...
    #set_var EASYRSA_REQ_COUNTRY    "US"
    #set_var EASYRSA_REQ_PROVINCE   "California" 
    #set_var EASYRSA_REQ_CITY       "San Francisco"
    #set_var EASYRSA_REQ_ORG        "Copyleft Certificate Co"
    #set_var EASYRSA_REQ_EMAIL      "me@example.net"
    #set_var EASYRSA_REQ_OU         "My Organizational Unit"
    ...

    uncomment those lines and modify them as you like, e.g.

    ...
    set_var EASYRSA_REQ_COUNTRY    "FI"
    set_var EASYRSA_REQ_PROVINCE   "Central Finland" 
    set_var EASYRSA_REQ_CITY       "Jyväskylä"
    set_var EASYRSA_REQ_ORG        "University of Jyväskylä"
    set_var EASYRSA_REQ_EMAIL      "alice@wonderland.com"
    set_var EASYRSA_REQ_OU         "Alice"
    ...
  1. Initialize PKI:

    $ ./easyrsa init-pki

    and rename the vars file:

    $ mv pki/vars pki/vars_new
  1. Create signing request for the client using common name "alice" (you can use any other name):

    $ ./easyrsa gen-req alice nopass

    and press Enter to confirm the common name. As a result, you should have "alice.req" in directory "pki/reqs/".

  1. Copy this request from alice-VM to webserv-VM:

    $ scp pki/reqs/alice.req webserv@192.168.11.2:/home/webserv/EasyRSA-3.1.0/pki/reqs/
  1. Now, we need to sign this request with CA private key. On webserv-VM, run:

    $ ./easyrsa sign-req client alice

    Confirm the signing by entering "yes". As a result, you should have file "alice.crt" in directory "pki/issued/" on webserv-VM.

  1. Go back to alice-VM and copy the signed certificate from webserv-VM:

    $ scp webserv@192.168.11.2:/home/webserv/EasyRSA-3.1.0/pki/issued/alice.crt pki/
  1. We also need to copy CA public certificate:

    $ scp webserv@192.168.11.2:/home/webserv/EasyRSA-3.1.0/pki/ca.crt pki/

    and ta.key:

    $ scp webserv@192.168.11.2:/home/webserv/EasyRSA-3.1.0/ta.key ./
  1. Now, we have all files to start an OpenVPN client, copy them to "/etc/openvpn" directory of alice-VM:

    $ sudo cp pki/ca.crt /etc/openvpn/
    $ sudo cp pki/private/alice.key /etc/openvpn/
    $ sudo cp pki/alice.crt /etc/openvpn/
    $ sudo cp ta.key /etc/openvpn/

4. VPN service

We can finally setup simple VPN server and client.

4.1 Server

  1. On webserv-VM, copy example VPN server configuration to "/etc/openvpn":

    $ sudo cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf /etc/openvpn/
  1. Edit the OpenVPN server configuration file "/etc/openvpn/server.conf":

    $ sudo nano /etc/openvpn/server.conf 

    Find "ca", "cert", "key", "dh" and "tls-auth" directives :

    ...
    ca ca.crt
    cert server.crt
    key server.key
    ...
    dh dh2048.pem
    ...
    tls-auth ta.key 0
    ...

    and edit them in such a way that they point to the files you generated with EasyRSA:

    ...
    ca /etc/openvpn/ca.crt
    cert /etc/openvpn/webserv.crt
    key /etc/openvpn/webserv.key
    ...
    dh /etc/openvpn/dh.pem
    ...
    tls-auth /etc/openvpn/ta.key 0
    ...

    At this point, the server configuration file can be used, however you still might want to customize it further. If you want your OpenVPN server to listen on a TCP port instead of a UDP port, use "proto tcp" instead of "proto udp", but remember, that the server's and the client's protocol used must be the same. Further in this tutorial, we assume that UDP is used. If you want OpenVPN to listen on both a UDP and TCP port, you must run two separate OpenVPN instances. If you want to use a virtual IP address range other than 10.8.0.0/24, you should modify the server directive. Remember that this virtual IP address range should be a private range which is currently unused on your network. If you would like connecting clients to be able to reach each other over the VPN, uncomment out the client-to-client directive. By default, clients will only be able to reach the server.

  1. Start the OpenVPN server:

    $ sudo openvpn /etc/openvpn/server.conf

    In case of success, you will see "Initialization Sequence Completed" message. Correct typos (e.g. wrong file paths in server.conf) if needed.

    If you encounter "the address already in use" error, this simply means that OpenVPN is already (somehow) running, you can try to stop it as follows:

    $ sudo systemctl stop openvpn.service

    After that, try to run the previous command again.

4.2 Client

  1. On alice-VM, copy file with example client configuration to "/etc/open/vpn":

    $ sudo cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf /etc/openvpn/
  1. Edit the OpenVPN client configuration file "/etc/openvpn/client.conf":

    $ sudo nano /etc/openvpn/client.conf

    Find "remote", "ca", "cert", and "key" directives :

    ...
    remote my-server-1 1194
    ...
    ca ca.crt
    cert client.crt
    key client.key
    ...
    tls-auth ta.key 1
    ...

    and edit them as follows:

    ...	
    remote 192.168.11.2 1194	
    ...
    ca /etc/openvpn/ca.crt
    cert /etc/openvpn/alice.crt
    key /etc/openvpn/alice.key
    ...
    tls-auth /etc/openvpn/ta.key 1
    ...

    Finally, ensure that the client configuration file is consistent with the directives used in the server configuration. The major thing to check for is that the "dev" (tun or tap) and "proto" (udp or tcp) directives are consistent. Save changes and close the file.

  1. Now you can run the OpenVPN client on alice-VM:

    $ sudo openvpn /etc/openvpn/client.conf

    In case of success, you will see "Initialization Sequence Completed" message. Correct typos (e.g. wrong file file paths in client.conf) if needed.

  1. Open new terminal on the alice-VM and type

    $ ifconfig

    You will see that now there is new network interface "tun0" (in your case, the name can be different).

  1. To check the VPN connection established, we can capture traffic on our pfSense gateway. In alice-VM's browser, open pfSense configuration web page

    https://pfsense.home.arpa

    login using default credentials and go to Diagnostics -> Packet Capture, select interface "LAN", enter "0" to "Count" for limitless capture, and click "Start".

  1. Use the second terminal of alice-VM (not the terminal with OpenVPN client running!) to request index web page from the web server by using IP address 192.168.11.2 and plain HTTP:

    $ wget http://192.168.11.2/accounts/index.php
  1. Go to pfSense web page opened in the browser of alice-VM and click "Stop" to stop capturing packets. Click "Download Capture" to download captured traffic. After that, file "packetcapture.cap" will be saved probably into directory "/home/alice/Downloads" of alice-VM. You can open and look inside this file using Wireshark that you can install by executing

    $ sudo apt install wireshark
  1. Now, we repeat this routine, but this time we will use our VPN tunnel. First, start capturing packets again. For this purpose, in "Packet Capture" section the pfSense configuration web page opened on alice-VM, make sure that interface "LAN" is still selected and click "Start"
  1. In the second terminal of alice-VM (not the terminal with OpenVPN client running!), request the same page by using IP address provided by OpenVPN tunnel "10.8.0.1" (in your case the IP address can be different):

    $ wget http://10.8.0.1/accounts/index.php
  1. Go to pfSense web page opened in the browser of alice-VM and click "Stop" to stop capturing packets. Click "Download Capture" to download the capture. Notice, that the capture will be saved by the same name "packetcapture.cap". For this reason, the file most likely will be renamed automatically to "packetcapture(1).cap" and saved into directory "/home/alice/Downloads". As previously, you can open and look inside this file using Wireshark.

5. Assignment (5p.)

5.1 Preliminary

Complete the test based on the preliminary questions (1 point).

# vpn_basic1

The article doesn't mention creators of web browsers, but rather "editors". I presumed this to mean a different thing. Could the last question be rephrased to be clearer?

01 Oct 23

changed to "developers", would it be ok?

02 Oct 23
# vpn_basic2

5.2 Basic

Look through packet capture files obtained during the tutorial. Find packets that in your opinion roughly correspond to Alice's HTTP requests in both those files: you can use some display filters to show only the packets related to those requests. What are the main differences between the packet captures?

Complete the test below based on the capture comparison results (1 point).

# vpn_basic3

Is the question about seeing the packets, or only seeing the packets, related to file transfer? As I tried all the filters and saw packets in my answers, but it's not correct and I don't understand why.

03 Oct 23

You have one filter missing for each capture

03 Oct 23

I didn't understand this question at all. Should I have been looking for all http packets, or specific ones? I have chosen filters for captions, which showed me any http packets.

05 Oct 23 (edited 05 Oct 23)

Filters (192.168.10.101 is assumed to be Alice's IP address):

  1. (ip.src == 192.168.11.2 || ip.dst == 192.168.11.2)
  2. (ip.src == 192.168.11.2 && ip.dst == 192.168.11.2) && tcp
  3. (ip.src == 192.168.11.2 || ip.dst == 192.168.11.2) && tcp
  4. (ip.src == 192.168.10.101 && ip.dst == 192.168.11.2) || (ip.src == 192.168.11.2 && ip.dst == 192.168.10.101)
  5. (ip.src == 192.168.10.1 || ip.dst == 192.168.10.1) && tcp
  6. (ip.src == 192.168.10.1 || ip.dst == 192.168.10.1) && openvpn
  7. (ip.src == 10.8.0.1 && ip.dst == 10.8.0.1) && openvpn
  8. (ip.src == 10.8.0.1 || ip.dst == 10.8.0.1) && openvpn
  9. (ip.src == 192.168.11.2 || ip.dst == 192.168.11.2) && openvpn
  10. (ip.src == 192.168.10.101 && ip.dst == 192.168.10.1) || (ip.src == 192.168.10.1 && ip.dst == 192.168.10.101)
# vpn_basic4
# vpn_basic5

This question is impossible to answer, if u answered incorrect for the first assignment of basic part :). And I don't know which filters are correct, bacause I have used my two tries :(

05 Oct 23

yeah sure, because looking through captures in wireshark is indeed impossible...

06 Oct 23
# vpn_basic6
# vpn_basic7

Does the "initially" part in the statements mean that the traffic goes "initially" through gateway, or that the "Destination" in Wireshark is the gateway, or something else? I'm wondering is this part repetition to the earlier question, does file transfer go through gateway, or is there something else asked here?

03 Oct 23
# vpn_basic8

Configure our OpenVPN server in such a way that it forces all the traffic of its clients to be routed through the VPN. For this purpose, complete the following three actions.

  1. Enable IP forwarding by executing in the terminal:

    $ sudo sysctl ...
  2. Open the VPN server configuration file with nano:

    $ sudo nano /etc/openvpn/server.conf

    and add one line that pushes all IP traffic of its clients such as web browsing to go through the VPN. Save and exit.

  3. Use Iptables to masquerade all the traffic from subnet 10.8.0.0/24 to the webserv-VM's main network interface enp0s3:

    $ sudo iptables -t ... -A ... -s ... -o ... -j ...
    

You can test that the network traffic to/from alice-VM goes through the VPN by capturing packets with pfSense as explained in the tutorial. In the textboxes below, write the complete commands and configuration settings used.

The command to enable IP forwarding (0.2 points):

# vpn_basic12

The configuration setting (one line) to push traffic through VPN (0.4 points):

# vpn_basic13

The command to masquerade the traffic (0.4 points):

# vpn_basic14

When you complete this assignment it makes sense to stop and disable OpenVPN service so that it does not affect our future experiments. For this purpose, run on both the webserv- and alice-VM:

$ sudo systemctl stop openvpn.service
$ sudo systemctl disable openvpn.service

5.3 Advanced

If you have no possibility to configure a VPN, you still can communicate with someone without exchanging a secret password. One tool suited well for this purpose is called GNU privacy guard (GPG), which is GNU project's complete and free implementation of the OpenPGP standard defined by RFC4880. GPG allows to encrypt and sign your data, it features a versatile key management system as well as access modules for all kinds of public key directories. It is a command line tool with features for easy integration with other applications. You can install GPG on alice-VM (if it is not installed yet) as follows:

$ sudo apt install gnupg

Find out how to generate your own private key, list keys with their signatures, import someone's public key from a keyserver, check key's fingerprint, sign the public key imported with your private key, decrypt files and verify signatures.

The assignment is following:

  • Create a private key in alice-VM's GPG, e.g. use name "Alice" and email "alice@wonderland.com".

  • On alice-VM, import the key with ID "B0B9D9E4" from keyserver "keyserver.ubuntu.com" (the key's name should be "Ties327" and email - "ties327@tim.jyu.fi").

  • Check this key's fingerprint, make sure it ends with "B0B9D9E4".

  • Sign the imported key with Alice's private key.

  • Download zip-archive with three files: "leipajuusto.txt", "soccer.jpg" and "backdoor.exe" (this is not a malware, just a file generated with /dev/urandom) that are presumably signed with Ties327's key:

    $ wget http://student:Ties327_2023@users.jyu.fi/%7Emizolotu/teaching/files/gpgfiles.zip

    and extract the archive's content (the archive also contains the files' signatures).

  • Verify signature of each of these three files, and find out which one actually was not signed by Ties327.

Post results of the experiments just conducted to the textboxes below. First, enter the whole fingerprint of the key downloaded from the key server.

Fingerprint of the key (40 hex digits) downloaded from the keyserver.ubuntu.com (0.5 points):

# vpn_advanced1

Then, copy-paste the signature verification commands and the results of their execution for the three files from the assignment into the next three answer boxes. The first line should be the command and the next lines should start with "gpg:", e.g.:

gpg --do_something ...

gpg: assuming signed data in 'file.ext'
gpg: Signature made to 12. lokakuuta 2017 15.52.51 EEST
gpg:                using RSA key AAAABBBBCCCCDDDD
gpg: ...

The signature verification command and its results for leipajuusto.txt (0.5 points):

# vpn_advanced2

The signature verification command and its results for soccer.jpg (0.5 points):

# vpn_advanced3

The signature verification command and its results for backdoor.exe (0.5 points):

# vpn_advanced4

is the script ok here? I tried to paste the output in different ways but wasn't able to get more than 0.3pts from this and leipajuusto...

27 Sep 23 (edited 27 Sep 23)

Yep, it is ok; it looks like you didn't sign the imported key with Alice's key.

28 Sep 23

5.4 General comments and feedback

Let us know how many hours in total have you spent on this assignment:

# vpn_time

On a scale from 1 to 10, estimate how interesting and difficult was the tutorial:

# vpn_interest
# vpn_difficulty

You can also give us some general feedback:

# vpn_feedback

6. Conclusion

7. Comments

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.