Thomas Ward's website
Thoughts on AI, surgery, programming, and more.


Securely tunnel smart phone traffic with WireGuard and OpenBSD

Posted on

Table of Contents

Purpose

Learn how to securely tunnel smart phone traffic over a WireGuard VPN with an OpenBSD 6.8 endpoint using the newly released in-kernel wg(4) driver with only base utilities.

Introduction

Why tunnel internet traffic?

When you browse the internet, check emails, use social media, etc. on your laptop or smart phone, the traffic must go from your device, through the local network, then through the wider internet until it ultimately reaches the destination server. Whoever runs the local Wi-Fi or cell network can see what websites you visit, and if the connection to the website is not encrypted, exactly what you are sending and receiving from the site. Further, if the Wi-Fi network is not encrypted (an open insecure network with no password), then any user on the network can also read your traffic!

One way to keep your browsing and activity private and secured from the people on the local network is to tunnel it through an encrypted Virtual Private Network (VPN). I commonly do this when using public Wi-Fi at airports, libraries, restaurants, etc. to secure my internet traffic from potential eavesdroppers. Ways to obtain a VPN include buying one from a commercial VPN provider, but this shifts the issue of who can look at your traffic from people on the Wi-Fi to this provider. Instead, I setup my own VPN (using WireGuard) on a virtual private server (VPS) that I own on Vultr that runs on OpenBSD, my favorite operating system.

What is WireGuard?

WireGuard is becoming the go-to software to create a VPN. Compared to common alternatives (OpenVPN, IPsec) it is simpler, faster, and uses modern cryptography (making it arguably more secure). OpenBSD has had user-land support for WireGuard using WireGuard-go. OpenBSD 6.8 was released today (2020-10-18) and now includes an in-kernel WireGuard implementation wg(4). Being in-kernel, this implementation is faster. It also means we can skip using extra software and use base-only utilities for simple, easy configuration.

What will things look like at the end?

Your smart phone will enable the VPN through one-click on the WireGuard app. All traffic on your phone will then go over an encrypted tunnel to the OpenBSD endpoint server, after which, it will route to the rest of the internet. This will protect your traffic from anyone snooping on the Wi-Fi or cell network.

OpenBSD Configuration.

Below shows configuration for an OpenBSD server to be a WireGuard endpoint. All instructions below pertain to OpenBSD 6.8 released on 2020-10-18.

Network setup

Below will set-up some background configuration to allow tunneling of packets and configure the firewall:

  1. Enable internet protocol (IP) forwarding

    This will allow packets to move between the WireGuard interface and the egress interface.

    First enable it on the running system:

    # sysctl net.inet.ip.forwarding=1
    

    Then make it be enabled after system reboot:

    # echo  'net.inet.ip.forwarding=1' >> /etc/sysctl.conf
    
  2. Configure pf

    Configure OpenBSD's firewall/packet filter, pf(4) to open the port for WireGuard, allow traffic between WireGuard peers, and forward tunneled traffic from the client to the internet. To do so, add the following lines to your pf.conf(5):

    # wireguard
    # open wireguard port
    pass in on egress proto udp from any to any port 51820
    # allow communication between wireguard peers
    pass on wg0
    # allow clients connected to wg0 to tunnel their outside world traffic
    pass out on egress inet from (wg0:network) nat-to (egress:0)
    

WireGuard Configuration

Previous to OpenBSD 6.8, configuration required the wireguard-tools packages. Now that WireGuard is in base, we can do all configuration with base utils. This is all nicely documented in the manual pages ( wg(4), ifconfig(4)) and summarised below:

Set-up wg0

  1. Generate a end point private key

    WireGuard uses a Curve25519 key that is 32 bytes in length and base64 encoded. A Curve25519 key needs 5 particular bits to be configured in a certain way to valid, but wg(4) can correct this from any randomly generated 32 bytes. Therefore, we just need a 32 bytes random base 64 encoded string and can use openssl(1) for that:

    $ openssl rand -base64 32 > ep_private.key
    

    This will save the private key to ep_private.key. Keep a hold of this for now. We can delete it after the set-up is complete.

  2. Bring up wg0 interface

    # ifconfig wg0 create wgkey "$(cat ep_private.key)" wgport 51820
    
  3. Save end point public key

    Bringing up the wg0 interface generates a public key from the private key. You will need to give this public key to the client (your laptop, smart phone etc), so save it for now:

    # ifconfig wg0 | grep wgpubkey | cut -d ' ' -f 2 > ep_public.key
    
  4. Assign wg0 an IP address

    This IP address will be the IP address your endpoint will have on the VPN. The OpenBSD endpoint will be reachable at 10.0.0.1 by clients (smartphone, laptop, etc) which will also be on the 10.0.0.0/24 subnet. I recommend picking an ip address range randomly to prevent any collision between a network you may join and your WireGuard network (eg 10.12.24.0/24). To keep things simple for this tutorial I'll just use 10.0.0.0/24 subnet and give the end point the first IP address:

    # ifconfig wg0 10.0.0.1/24
    

wg0 is now set up and functional.

Generate public/private key pair for client

Below is a bit of a hack so we can use base utilities only. It brings up a temporary wg interface with a generated private key just so we can extract the generated public key.

  1. Generate a client private key

    $ openssl rand -base64 32 > client1_private.key
    

    This will save the private key to client1_private.key. Keep a hold of this for now. We can delete it after the set-up is complete.

  2. Bring up wg1 interface

    Note, I'm picking a different port (this can be whatever you want) since wg0 is already up on 51820.

    # ifconfig wg1 create wgkey "$(cat client1_private.key)" wgport 51822
    
  3. Save client public key

    You will need to use this public key on the OpenBSD endpoint in order for the client to connect.

    # ifconfig wg1 | grep wgpubkey | cut -d ' ' -f 2 > client1_public.key
    
  4. Remove the temporary wg1 interface

    This will remove the temporary interface we created to extract keys:

    # ifconfig wg1 destroy
    

Finalize setting up OpenBSD Endpoint

Now that we have the client's public key, we can establish a WireGuard peer on the endpoint to allow the client to connect:

# ifconfig wg0 wgpeer "$(cat client1_public.key)" wgaip 10.0.0.2/32

This will limit the IP address of the peer to 10.0.0.2. Changed the IP address to the IP address you would like on the subnet you picked.

Enable configuration to persist on reboot

With the above instructions, your endpoint works. However, it will not work once you reboot the operating system. Save this configuration with the below text in /etc/hostname.wg0:

wgkey REPLACE_WITH_EP_PRIVATE_KEY wgport 51820
inet 10.0.0.1 255.255.255.0
wgpeer REPLACE_WITH_CLIENT1_PUBLIC_KEY \
        wgaip 10.0.0.2/32

Making sure to replace REPLACE_WITH_EP_PRIVATE_KEY with the text stored in ep_private.key and REPLACE_WITH_CLIENT1_PUBLIC_KEY with the text stored in client1_public.key.

Make sure the permissions for /etc/hostname.wg0 are correct so users outside of root and those in the group wheel cannot read the keys:

# chmod 640 /etc/hostname.wg0
# chown root:wheel /etc/hostname.wg0

Now, after a reboot OpenBSD will bring up the WireGuard endpoint for you automatically.

Smart phone configuration

With the endpoint set-up, now you just need to configure your smart phone to use the tunnel. The authors of WireGuard provide apps for both Android and iOS. These applications make configuration easy. You just need to take a photo of a QR code made from a client.conf that you can write on your computer. The client.conf should have the following contents:

[Interface]
PrivateKey = REPLACE_WITH_CLIENT1_PRIVATE_KEY
Address = 10.0.0.2/24

[Peer]
PublicKey = REPLACE_WITH_EP_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = OPENBSD_EP_IP_ADDRESS:51820

Make sure to replace REPLACE_WITH_CLIENT1_PRIVATE_KEY with the contents from client1_private.key, REPLACE_WITH_EP_PUBLIC_KEY with the contents from ep_public.key, and OPENBSD_EP_IP_ADDRESS with the IPv4 address of your OpenBSD end point.

Next, generate a qr code with this information using the libqrencode package. If you do not want to install a package outside of base, you can enter the above info by hand into your phone instead. Then do the following to create the code:

$ qrencode -t ansiutf8 < client.conf

If you have issues taking a photo from the QR code generated in your terminal, you can also save it as an image and take a photo of the image.

Open your WireGuard application and take a photo of the QR code, and the app is now configured! Toggle the switch in the app to turn it on. To verify it is working, go to a website like icanhazip.com to see your IP address. It should change when the WireGuard tunnel is up to the same address as your OpenBSD end point.

Once everything works, if you want, you can delete the key files and client.conf:

$ rm {client1,ep}_{public,private}.key
$ rm client.conf

Enjoy securely tunneling your traffic with WireGuard!

Optional DNS Resolver

You can additionally setup unbound(8), to serve as a DNS cache and resolver. This will allow you to guarantee that your DNS queries are tunneled to your Wireguard server then sent to your preferred upstream DNS resolver. By setting up unbound, you also cache your DNS requests for some added speed compared to specifying a dedicated public resolver.

Credit goes to David Robert Newman for this part who suggested adding a DNS line to the client configuration.

To do so, you need to:

  1. Setup unbound
  2. Add a DNS resolver to your wireguard client configurations

Setup unbound

  1. Configure unbound to always run on system start
# rcctl enable unbound
  1. Create a /var/unbound/etc/unbound.conf, you can use the below to get started:
server:
        # Only listen locally
        interface: 127.0.0.1
        interface: 10.0.0.1
        interface: ::1
        access-control: 0.0.0.0/0 refuse
        access-control: 127.0.0.0/8 allow
        access-control: 10.0.0.0/24 allow
        access-control: ::0/0 refuse
        access-control: ::1 allow
        hide-identity: yes
        hide-version: yes

        # Perform DNSSEC validation.
        #
        auto-trust-anchor-file: "/var/unbound/db/root.key"
        val-log-level: 2

        # Synthesize NXDOMAINs from DNSSEC NSEC chains.
        # https://tools.ietf.org/html/rfc8198
        #
        aggressive-nsec: yes

        # Perform DNSSEC validation.
        #
        auto-trust-anchor-file: "/var/unbound/db/root.key"
forward-zone:
        name: "."                               # use for ALL queries
        forward-addr: 8.8.4.4                   # Google DNS
        forward-addr: 208.67.222.222            # UltraDNS
  1. Start unbound
# /etc/rc.d/unbound start

Add DNS cache to wireguard config

This is easy, you just need to add a DNS line to your client configuration:

[Interface]
PrivateKey = REPLACE_WITH_CLIENT1_PRIVATE_KEY
Address = 10.0.0.2/24
DNS = 10.0.0.1

[Peer]
PublicKey = REPLACE_WITH_EP_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = OPENBSD_EP_IP_ADDRESS:51820

Comments, questions, input, concerns?

Please contact me with any questions or input on the article using any of the methods on my contact page.