OpenBSD natively supports the following types of VPN server, which will allow your gadgets to connect to, if you configure them properly, they are:

  1. IPSec + L2TP - Very old fashioned, works with almost all devices, extremely difficult to setup and maintain. Low throughput due to complexity.
  2. Wireguard - Wireguard is a new open-source VPN / secure networking implementation which is very popular in Cloud-native environments due to its simplicity. It also is highly regarded by Linus Torvalds. As soon as Jason completed the implementation in Linux kernel, he started work on the OpenBSD kernel implementation as well, which is also well received by the OpenBSD community. However, it does not support dynamic address assignment and requires a separate App in order to work with Apple iOS or Android.
  3. IPSec IKEv2 - A newer generation of IPSec which has a higher throughput than Wireguard as long as you have a CPU that supports AES encryption instructions (basically almost all Intel / AMD CPUs in the last couple of years, and all high-end ARM CPUs, meaning all high-end gadgets). Also, it is supported natively in Mac OS, iOS and can be supported easily in Android through StrongSwan App, a popular Linux based IPSec IKEv2 implementation. We will be using this as our choice of VPN solution.

In OpenBSD, the base iked server is a native implementation of IPSec IKEv2.

Assumptions

The following assumptions, following my previous article on how to setup OpenBSD, applies.

  • Internet on em0
  • Internal network on em1, with the IP address range of 192.168.0.0/24
  • VPN address is 192.168.1.0/24

Setting up a Dynamic DNS account

You will need to setup a dynamic DNS account so that your devices can connect to your home router, which probably is using a dynamic IP address from your ISP.

I strongly recommend duckdns.org, which is free and is open. Simply register via their website and obtain a update token from them.

In the examples below, we will use example.duckdns.org as our server name.

I have a very simple script to update DDNS record for DuckDNS below:

/usr/bin/ftp -Mo - "https://www.duckdns.org/update?domains=example&token=<token provided by DuckDNS>&ip="
Script to update DuckDNS, you will need to obtain a token for your domain (example) via the DuckDNS console.

You can put this into a cron job which can run every hour, or using ifstated to update the DDNS record if it detects a reconnection (which will probably trigger your ISP to issue a new IP address).

Setting up LetsEncrypt to obtain a trusted SSL certificate

Fortunately, LetsEncrypt, a wonderful organisation which issues free SSL certificates, can work with dynamic hostname such as DuckDNS.

We will use the LetsEncrypt SSL certificate for server authentication, thus avoiding installing a third party root certificate to the devices which want to connect to the VPN server.

This can be done easily using the native OpenBSD acme-client. To make this work, you will need to configure OpenBSD httpd server, which involves a few steps:

server "example.duckdns.org" {
  listen on * port 80
  root "/htdocs/example.duckdns.org"
  location "/.well-known/acme-challenge/*" {
    root "/acme"
    request strip 2
  }
}
/etc/httpd.conf
authority letsencrypt {
  api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/ssl/private/letsencrypt.key"
}
domain home.jasworks.org {
  domain key "/etc/ssl/private/example.duckdns.org.key"
  domain certificate "/etc/ssl/example.duckdns.org.crt"
  domain full chain certificate "/etc/ssl/example.duckdns.org.pem"
  sign with letsencrypt
}
/etc/acme-client.conf

Finally, we do not want the webserver to be running all the time just responding to the LetsEncrypt challenge if you do not have a website. Hence, we will only start the httpd web server and open the firewall port only if we need to renew the certificate. I have created a handy shell script simply for this purpose:

#!/bin/sh

echo "pass in quick on egress inet proto tcp from any to (egress:0) port 80" | pfctl -a acme -f -
rcctl -f start httpd
acme-client example.duckdns.org
if [ $? -eq 0 ]; then
        cp -f /etc/ssl/example.duckdns.org.crt /etc/iked/certs/.
        cp -f /etc/ssl/private/example.duckdns.org.key /etc/iked/private/local.key
        rcctl restart iked
fi
rcctl -f stop httpd
pfctl -a acme -F rules
acme-renew shell script

In order for this to work, put a simple line in the front part of the firewall configuration, /etc/pf.conf just behind the match rules:

anchor acme
Additional line needed in /etc/pf.conf, in front of everything behind the match rules. 

You can run this shell script manually or simply setup another cron job to run it every day at midnight. It will:

  1. Open up firewall port for the httpd web server.
  2. Start httpd web server.
  3. Trigger a SSL certificate renew process using acme-client
  4. If a renewal is successful, copy the new certificates to the right location in iked configuration, and restart iked.
  5. Stop the webserver.
  6. Close the firewall port.

iked configuration

I use the following /etc/iked.conf configuration file in its default location:

set fragmentation
set passive
set mobike
set enforcesingleikesa
set cert_partial_chain
set dpd_check_interval 0

ikev2 "Home" passive esp \
        from 0.0.0.0/0 to 0.0.0.0/0 \
        peer 0.0.0.0/0 \
        ikesa enc aes-256-gcm \
                prf hmac-sha2-256 \
                group modp2048 \
        childsa enc aes-256-gcm \
                group modp2048 \
        srcid example.duckdns.org \
        ikelifetime 0 \
        lifetime 0 bytes 0 \
        config address 192.168.1.0/24 \
        config name-server 192.168.0.1

Changing firewall rules accordingly

We also need to change the firewall rules to allow traffics going in and out of VPN. The following 2 rules allow VPN traffic coming from Internet and allow the established VPN connection to access both internal and external servers.

pass in quick on em0 inet proto udp from any to (em0:0) port {500, 4500} keep state label ipsec
pass in quick on enc0 inet keep state (if-bound)
portion of /etc/pf.conf that allows VPN traffic

Also, a match and scrub rule will adjust TCP-MSS to a smaller value, this is just an example and you can fine tune accordingly.

match in on enc0 inet scrub (random-id max-mss 1328 no-df)
scrub and match rule in /etc/pf.conf that change max-mss to a smaller value.

This above is recommended because PMTU (Path MTU discovery) does not work well in IPSec VPN. I welcome any suggestions on this.

The resulting /etc/pf.conf looks like this:

wan = em0
lan = em1
vpn = enc0

table <reserved> const { 
        240.0.0.0/4, \
        224.0.0.22, \
        127.0.0.0/8, \
        192.168.0.0/16, \
        172.16.0.0/12, \
        10.0.0.0/8, \
        169.254.0.0/16, \
        192.0.2.0/24, \
        198.51.100.0/24, \
        203.0.113.0/24, \
        169.254.0.0/16, \
        0.0.0.0/8
}

set block-policy return
set state-policy floating
set loginterface egress
set skip on lo0

# Scrub IPv4
match on $wan inet scrub (random-id max-mss 1460 no-df)
match in on enc0 inet scrub (random-id max-mss 1328 no-df)

# IPv4 NAT
match out on $wan inet nat-to ($wan:0)

# Security rules
block in log quick on egress from { <reserved> no-route urpf-failed } to any label noroute

# VPN - Allow IPSec traffic from Internet
pass in quick on $wan inet proto udp from any to ($wan:0) port {500, 4500} keep state label ipsec
pass in quick on $vpn inet keep state (if-bound)

# Default rules to allow internal network
# devices to access Internet
pass quick on $lan inet
pass out quick inet keep state

# Allow internet to ping your WAN IP address
pass in quick on $wan inet proto icmp from any to ($wan:0) icmp-type {echoreq unreach} keep state

# Block everything else, last rule in pf.conf
block in log all
/etc/pf.conf

Setting up PKI certificates for iked

IKEv2 support multiple different kinds of authentications (ways to identify yourelf and allow you in). We will be using PKI certificates. OpenBSD provides ikectl command which allows you to easily create a certificate.

You will need the following certificates in order for iked to work:

  1. A server certificate, which is already created, and
  2. 1 client certificate per device using the following instructions.

The server certificate can be easily created by the following command:

# Create the certificate 
# authority, the word 
# "vpn" below can be 
# replaced by whatever you
# want to use as the name
# of the CA.

ikectl ca vpn create
Create the certificate authority
# Follow the instructions
# and answer the questions
# make sure you answer
# the "common name" question
# with example.duckdns.org
# in this example (replace
# with your own host if
# needed

ikectl ca vpn certificate example.duckdns.org create
Create the server certificate, make sure you use answer the common name question with the correct domain name.

Then install the server certificate:

ikectl ca vpn certificate example.duckdns.org install

Then, for each client, create a certificate using email address (doesn't matter if the email address is working or not), such as user1@example.com

ikectl ca vpn certificate user1@example.com create
Create user certificate. Make sure the common name entered is the same as the certificate name, in this example, user1@example.com

Then, copy the user certificate from the SSL certificate directory (/etc/ssl/vpn/) to the IKED certificate directory of /etc/iked/pubkeys/ufqdn/ with an example like below:

# Run this as shell, or use
# doas

cp /etc/ssl/vpn/user1@example.com.crt /etc/iked/pubkeys/ufqdn/user1@example.com
Copy the device certificate to iked certificate directory.

Do note that the filename is changed from <email address>.crt to <email address>. iked will identify the certificate that is used for authentication using the filename that is the same as the common name. Since the common name is in a email address format, it should be put into the ufqdn folder under /etc/iked/pubkeys/.

Installing certificates - Mac OS and iOS

After generating the device certificates, we will need to export the certificate into .pfx format so that Mac OS and iOS can use them.

Since we have adjusted the authentication and encryption protocols in /etc/iked.conf, we will use a customized mobile configuration which can work with both Mac OS and iOS. I have created a template mobile configuration as well as some shell scripts to generate Mac and iOS compatible mobileconfig files which can be imported into those devices effortlessly.

The mobileconfig profiles, once applied (by double clicking them on Macs, and downloading them through iCloud in iOS), will:

  1. Install a client certificate into your device, and
  2. Setup VPN connection accordingly with the right encryptions. I am using AES-256-GCM with DH group 14.

Generating .pfx certificate bundle using ikectl

You can generate a .pfx certificate bundle using the following command from ikectl:

ikectl ca vpn certificate user1@example.com export
Export a certificate bundle

It will ask you for a export passphrase and repeat it. Enter a secret that you will only share with the device that you intend to have the VPN profile installed.

A compressed archive with the file extension of .tgz will be created. Extract the user1@example.com.pfx from the archive.

Generating mobileconfig

Normally, you can use the Apple Configurator to generate a mobileconfig file. However, I have written a quick script with some template files to make this easier. Simply copy the .pfx file into the same location of the script and the template file and execute the following command to generate the mobileconfig file:

./generate-mobileconfig user1@example.com.pfx Home user1@example.com normal example.duckdns.org lan 192.168.0.1
Example of mobile configuration generator

Simply open the mobileconfig using a Mac or iOS device and the profile will be installed after following the instructions. You will also need to enter the export passphrase that you have defined previously.

The shell script and the template files can be found in my Github.

Wrap it up

Once everything is setup, you will have:

  1. A working IPSec IKEv2 VPN server at home
  2. Your devices can use the certificates generated to connect to your home VPN. If you use the examples above to generate mobile configuration for Mac OS and iOS, they will direct all network traffic through your home network before going out. In the next article, I will share a very useful case in this scenario, which I will direct all Internet traffic to a VPN provider instead of using my ISP.

Readout