IKEv2

Date: 2018-08-24 · Word Count: 887 · Reading Time: 5 minutes

Given how much more efficient IPSec is than OpenVPN, I decided to setup an IKEv2 endpoint using FreeBSD on my network to allow for accessing content which is restricted to the USA and doing administration on my network when traveling.

FreeBSD

The DEFAULT kernel contains everything you need to run IPSec, just add the following to /boot/loader.conf:

if_enc_load="YES"

If you’re building a custom kernel, you’ll want:

options IPSEC
device crypto
device enc

OpenIKED

As OpenIKED has a relateively simple configuration and applications written by the OpenBSD team tend to be relatively secure, I started with it. Unfortunately, while I could get transport sessions to work, tunnel mode just wasn’t happening. Even worse, enc0 doesn’t show traffic, as noted by the port developer

StrongSwan

The StrongSwan configuration is rather verbose as it provides an endless supply of options to configure, mostly due to providing more than just IKEv2. It’s worth starting with the StrongSwan security recommendations.

The first step is to generate RSA keys. If you don’t already have a CA, you can easily set one up and generate keys with the following:

#!/bin/sh

readonly KEYDIR=/usr/local/etc/ipsec.d
readonly DN="C=AU, O=OTOH.ORG, CN=VPN"

ipsec pki --gen -s 4096 --outform pem > "${KEYDIR}/private/ca.key"
ipsec pki --self --in "${KEYDIR}/private/ca.key" \
  --dn "${DN}" \
  --ca --outform pem \
  > "${KEYDIR}/cacerts/ca.pem"

ipsec pki --gen -s 4096 --outform pem > "${KEYDIR}/private/server.key"

ipsec pki --pub --in "${KEYDIR}/private/server.key" \
  | ipsec pki --issue \
    --cacert "${KEYDIR}/cacerts/ca.pem" \
    --cakey "${KEYDIR}/private/ca.key" \
    --dn "CN=${external_ip}" \
    --san="${external_ip}" \
    --flag serverAuth \
    --flag ikeIntermediate \
    --outform pem \
    > "${KEYDIR}/certs/server.pem"

You can then generate client certs with:

#!/bin/sh

if [ $# != 1 ]; then
  echo "USAGE: $0 client_name" >&2
  exit 1
fi

client=$1

readonly KEYDIR=/usr/local/etc/ipsec.d
readonly DN="C=AU, O=OTOH, CN=${client}"

if [ -f "${KEYDIR}/private/client.pem" ]; then
  echo "Client ${client} already exists" >&2
  exit 1
fi

ipsec pki --gen -s 4096 --outform pem > "${KEYDIR}/private/${client}.pem"

ipsec pki --pub --in "${KEYDIR}/private/${client}.pem" \
  | ipsec pki --issue \
    --cacert "${KEYDIR}/cacerts/ca.pem" \
    --cakey "${KEYDIR}/private/ca.key" \
    --dn "${DN}" \
    --san="${client}" \
    --outform pem > "${KEYDIR}/certs/${client}.pem"

You’ll then need to make the CA cert available to all clients and distribute client keys as well.

Next is to setup ipsec.conf. StrongSwan confusingly refers to the local side of the connection as left and the remote side as right, which took a bit of getting used to. Next is working out which ciphers to use for the connection. As with all other aspects, there are a lot of options and it took a while to work out whether things like MODP or ECP were better. Fortunately, RFC5114 and CSNA provide some insights into selecting strong ciphers (turns out ECP is generally what you want).

This resulted in an initial configuration of:

# NB: The ! at the end is important, or weak ciphers will be appended
ike=aes256gcm16-prfsha512-ecp521!
esp=aes256gcm16-ecp521!

Unfortunately, this didn’t connect and, after running StrongSwan in debug mode, I was able to see that the strongest I was going to get was:

ike=aes256-sha256-ecp256!
esp=aes256-sha256-ecp256!

Later, I found out about the configuration profile reference and that stronger configurations were available, which is what I implemented below.

Here’s my resulting ipsec.conf:

config setup

    # Increase debug level
    # charondebug = ike 3, cfg 3

conn %default

    # NB: MacOS only does the weaker ciphers
    ike=aes256gcm16-prfsha512-ecp521,aes256gcm16-prfsha384-ecp384,aes256gcm16-prfsha256-ecp521,aes256-sha256-ecp256!
    esp=aes256gcm16-ecp521,aes256gcm16-ecp384,aes256-sha256-ecp384,aes256-sha256-ecp256!

    # Dead peer detection will ping clients and terminate sessions after
    # timeout
    dpdaction=clear
    dpddelay=35s
    dpdtimeout=2000s

    keyexchange=ikev2
    auto=add
    rekey=yes
    reauth=yes
    fragmentation=no
    compress=yes
    mobike=yes
    type=tunnel

    # Default is 3h, rekey more often
    lifetime=1h

    # Rekey when this many bytes have been transferred
    lifebytes=536870912

    leftauth=pubkey
    left=${external_ip}
    leftid=${external_ip}
    leftcert=${path_to_server_cert}
    leftsendcert=always
    # Routes pushed to clients. If you don't have ipv6 then add ::/0
    # This route sends all traffic via the VPN
    leftsubnet=0.0.0.0/0

    # right - remote (client) side
    right=%any
    rightid=%any
    # ipv4 and ipv6 subnets that assigns to clients. If you don't have ipv6
    # then remove it
    rightsourceip=${internal_netblock}/24
    rightdns=${dns_server}
    rightauth=secret

# MacOS will not connect without this
conn ikev2-mschapv2-apple
    rightauth=eap-mschapv2

If you have windows clients, you’ll want to add

conn ikev2-mschapv2
    rightauth=eap-mschapv2

You’ll also need an ipsec.secrets:

: RSA ${path_to_server_cert}

user1-laptop : EAP "${user_laptop_passphrase}"
user1-phone : EAP "${user_phone_passphrase}"

If you want to use pre-shared-keys (not recommended), then replace : RSA ... with : PSK "${pre_shared_key}". As you can see, I use separate keys for my laptop and my phone, to allow both to connect at once. An alternative is to turn off uniqueids, which will allow any given user to connect more than once simultaneously, but this is not recommended.

MacOS

After getting a tunnel going, the next surprise was that my laptop disconnected every 8 minutes. Turns out MacOS doesn’t allow for perfect forward secrecy if you use the GUI to setup your VPN. My first thought was that it may be an expiry rekey issue, but no amount of fiddling with these settings made a difference.

For those also trying to debug IKEv2 issues in MacOS, you can increase the log level via:

sudo defaults write \
  /Library/Preferences/com.apple.networkextension.control.plist LogLevel 6

After some poking around, I came across Apple Configurator, which will allow for generating configuration profiles which you can share between clients. Using configurator, you can generate a configuration with the following important properties:

  • PFS: Enabled
  • DiffieHellmanGroup: 21
  • EncryptionAlgorithm: AES-256-GCM
  • IntegrityAlgorithm: SHA2-256
  • DisableMOBIKE: 0

After saving, you can then distribute this file to all your Mac clients to automatically setup a properly configured VPN.