Skip to content

Instantly share code, notes, and snippets.

@karlvr
Last active November 26, 2023 14:21
Show Gist options
  • Star 50 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save karlvr/34f46e1723a2118bb16190c22dbed1cc to your computer and use it in GitHub Desktop.
Save karlvr/34f46e1723a2118bb16190c22dbed1cc to your computer and use it in GitHub Desktop.
Roadwarrior configuration for macOS 10.12, iOS 10 and Windows 10 using strongSwan and user certificates

strongSwan setup for Road Warriors on macOS 10.12, iOS 10 and Windows 10

This setup is for remote users to connect into an office/home LAN using a VPN (ipsec). This is based on (but not the same as) the strongSwan documentation and this guide: https://raymii.org/s/tutorials/IPSEC_vpn_with_Ubuntu_16.04.html

I used strongSwan 5.5.1.

apt-get install -y strongswan strongswan-pki

Create server certificate and certificate authority

Replace the variables below with appropriate values for your organisation. Then copy and paste line by line.

COUNTRY_CODE=XX
ORGANISATION=XXXXX
SERVER_NAME=vpn.example.com
IP_ADDRESS=<public ip of the vpn server>

pushd /etc/ipsec.d/

Create self-signed certificate authority (CA):

ipsec pki --gen --type rsa --size 4096 --outform der > private/strongswan.der
chmod 600 private/strongswan.der

ipsec pki --self --ca --lifetime 3650 --in private/strongswan.der --type rsa --dn "C=$COUNTRY_CODE, O=$ORGANISATION, CN=$ORGANISATION Root CA" --outform der > cacerts/strongswan.der
openssl x509 -inform DER -in cacerts/strongswan.der -out cacerts/strongswan.pem -outform PEM

ipsec pki --print --in cacerts/strongswan.der

Create host key:

ipsec pki --gen --type rsa --size 4096 --outform der > private/host-vpn.der
chmod 600 private/host-vpn.der

ipsec pki --pub --in private/host-vpn.der --type rsa | ipsec pki --issue --lifetime 730 --cacert cacerts/strongswan.der --cakey private/strongswan.der --dn "C=$COUNTRY_CODE, O=$ORGANISATION, CN=$SERVER_NAME" --san=$SERVER_NAME --san $IP_ADDRESS  --san @$IP_ADDRESS --flag serverAuth --flag ikeIntermediate --outform der > certs/host-vpn.der

ipsec pki --print --in certs/host-vpn.der

mkdir -p p12
popd

Note: if you want to use "Remote ID" to let clients choose between connection profiles, then you need to add them as SANs above, as strongSwan will only let you use leftids that are named in your host cert.

Edit /etc/ipsec.secrets to add the host-vpn.der private key we generated for the host above (see the example file in this Gist).

Setup ipsec connections

Copy the example ipsec.conf attached to this Gist into /etc/ipsec.conf. Replace <VPN SERVER ID> with the value of $SERVER_NAME above. Replace <INTERNAL SUBNET> with the subnet you want to provide access to in your LAN. If you want to route all internet traffic, use 0.0.0.0/0, otherwise enter something like 192.168.1.0/24. Note that you might have trouble if LAN subnet conflicts with subnets the mobile device might otherwise be on. Replace <LAN DNS SERVERS> with the appropriate DNS servers for your LAN.

Documentation of what the configuration in the conn section means: https://wiki.strongswan.org/projects/strongswan/wiki/ConnSection

  • keyexchange=ikev2 We use the modern key exchange protocol. This works on all of the platforms we want to support.
  • ike=... A list of cipher suites as recommended by the guide linked above
  • esp=... ditto
  • dpdaction=clear When the Dead Peer Detection detects a dead peer it closes the ipsec connection
  • dpddelay=60s How often to send the Dead Peer Detection check if the connection is idle. Don't make this too long as some clients close the connection themselves if it is idle for much longer than this.
  • left=%any We don't need to know what our IP address. Just use one of our IP addresses at runtime, depending upon how the client connected.
  • leftid=... This is the "Remote ID" string that macOS and iOS clients use. I suggest using your server's fully-qualified domain name, as above.
  • leftsubnet=... As above.
  • leftcert=host-vpn.der The certificate representing the server that we created above.
  • leftsendcert=always See https://wiki.strongswan.org/projects/strongswan/wiki/MacOSX which notes that we should agressively send our certificate so the user doesn't need to install it.
  • right=%any Our mobile users could have any IP address
  • rightauth=eap-tls This activates user authentication on the client. More below.
  • rightsourceip=%dhcp Use our network's DHCP server to issue an IP address. You can also just give a subnet here, or an IP address range, e.g. 10.1.1.50-10.1.1.70
  • rightdns=... DNS servers to give to the mobile user. Probably the same DNS servers you use on your LAN.
  • eap_identity=%identity For Windows 10 to work.
  • auto=add When strongSwan starts up it should add this connection to its list of connections available to use when a mobile user connects.

Configure the DHCP plugin

If we issue remote clients IPs using our LAN's DHCP server, and the DHCP server is on the same server as the VPN, then we need a little extra configuration. Edit the /etc/strongswan.d/charon/dhcp.conf file, consulting the example attached to this Gist. Replace <LAN BROADCAST ADDRESS> with the broadcast address of your LAN.

Documentation: https://wiki.strongswan.org/projects/strongswan/wiki/Dhcpplugin

System configuration

Install the /etc/sysctl.d/99-strongswan.conf file listed below, and make sure it's loaded:

sysctl -p

Network configuration

If your VPN server is not public on the internet, you'll need to setup port forwarding on your internet-facing router.

The following ports must be forwarded to your VPN server:

  • UDP 500
  • UDP 4500 (for nat traversal)

Firewall configuration

I use Shorewall to control the iptables firewall on my VPN server. Basically you just need to open UDP 500 and 4500. Here's how to do it using Shorewall.

Add the following line to /etc/shorewall/tunnels:

ipsecnat        net     0.0.0.0/0

Add the following line to /etc/shorewall/zones, assuming that your network zone is named net (if not, substitute):

vpn:net ipsec   mode=tunnel mss=1024

Add the following line to /etc/shorewall/hosts, assuming that the interface your VPN is connected to is eth0 (if not, substitute):

vpn     eth0:0.0.0.0/0

Then either configure the vpn zone using rules or an entry in /etc/shorewall/policy.

Then restart Shorewall:

shorewall safe-restart

Control strongSwan

To startup strongSwan:

ipsec start

To stop it:

ipsec stop

To reload the configuration from /etc/ipsec.conf when you've made changes, but without interfering with any existing connected users:

ipsec reload

To restart strongSwan when you've made configuration changes, or want to bump connected users:

ipsec restart

To get the status of established strongSwan connections:

ipsec status

To get more details of strongSwan's status:

ipsec statusall

Create user certificates

We use certificates to authenticate users. This works on macOS 10.12, iOS 10 and Windows 10. So next you need to create user certificates so that you can connect to the VPN.

Set the variables first. The username should be a string that is okay as a filename, and doesn't contain any spaces. Like a username. The user id is usually an email address.

Note that we store the user files in the same directories as the CA and host ones. So don't use strongswan as a username, or host-vpn, or you'll overwrite your previous files!

NAME=John Doe
USERNAME=jdoe
USERID=jdoe@example.com
COUNTRY_CODE=XX
ORGANISATION=XXXXX

Then run these commands. You will be prompted for a password on the final openssl command. This password protects the .p12 file that contains the private key and certificate for the user. I suggest making up a random password for each .p12 file.

pushd /etc/ipsec.d
ipsec pki --gen --type rsa --size 2048 --outform der > private/$USERNAME.der
chmod 600 private/$USERNAME.der
ipsec pki --pub --in private/$USERNAME.der --type rsa | ipsec pki --issue --lifetime 730 --cacert cacerts/strongswan.der --cakey private/strongswan.der --dn "C=$COUNTRY_CODE, O=$ORGANISATION, CN=$USERID" --san "$USERID" --outform der > certs/$USERNAME.der
openssl rsa -inform DER -in private/$USERNAME.der -out private/$USERNAME.pem -outform PEM
openssl x509 -inform DER -in certs/$USERNAME.der -out certs/$USERNAME.pem -outform PEM
openssl pkcs12 -export -inkey private/$USERNAME.pem -in certs/$USERNAME.pem -name "$NAME's VPN Certificate" -certfile cacerts/strongswan.pem -caname "$ORGANISATION Root CA" -out p12/$USERNAME.p12
popd

The /etc/ipsec.d/p12/$USERNAME.p12 file contains the user's private key and certificate. It is a binary file. You need to send this file to the user, and send the password (entered above) separately.

Client setup

macOS 10.12

Send the .p12 file for the user and the /etc/ipsec.d/cacerts/strongswan.pem file.

On macOS, double-click the .p12 file, and enter the password, to add the user's certificate and private key to the keychain using Keychain Access.app. In Keychain Access.app you should see the certificate in the My Certificates list of the login keychain, with the user id from above as its name.

Double-click the .pem file to add our CA certificate to the keychain using Keychain Access.app. Find the CA certificate in the Certificates list of the login keychain, named with your organisation name followed by "Root CA". Double-click to open it. Expand the Trust section, and choose "Always Trust".

  • Open the Network system preferences pane.
  • Click the + button to add a new connection.
    • Interface: VPN
    • VPN Type: IKEv2
    • Service Name: A name of your choice
    • Click "Create", then fill in the details for the connection:
  • Server Address: the fully-qualified domain name (or IP) of your VPN server
  • Remote ID: the same as the Server Address, unless you've done something different with your leftid
  • Local ID: the user id, and name on your user certificate, probably the email address
  • Click "Authentication Settings…"
    • Choose "Certificate" from the dropdown for authentication settings
    • Click "Select…" and choose the certificate matching the Local ID you entered above.
    • Click "OK" to close the Authentication Settings sheet.
  • Click "Connect" to connect your VPN.

iOS 10

One way to get the .p12 file and CA cert (/etc/ipsec.d/cacerts/strongswan.pem) onto an iOS device is to put them on a web server and browse to them. Another is to use the Apple Configurator application.

The web server approach is pretty easy. Browse to each file and follow the prompts to add them to your profiles.

  • Open the Settings app
  • Open the VPN settings
  • Tap "Add VPN Configuration…"
    • Type: IKEv2
    • Description: Anything here
    • Server: the fully-qualified domain name (or IP) of your VPN server
    • Remote ID: the same as the Server Address, unless you've done something different with your leftid
    • Local ID: the user id, and name on your user certificate, probably the email address
    • User Authentication: Certificate
    • Certificate: choose the certificate matching the Local ID you entered above.
    • Done

You can now connect to the VPN from your iOS device.

Windows 10

Send the .p12 file for the user and the /etc/ipsec.d/cacerts/strongswan.der file, but rename strongswan.der to strongswan.cer. That will make it easier to use on Windows.

  • On Windows 10, double-click the .p12 file to open the Certificate Import Wizard.

  • Choose "Current User" and click "Next".

  • Check the file path, and click "Next" again.

  • Enter the password for the .p12 file that you used above. The default import options are fine (just "Include all extended properties" is ticked)

  • Choose "Automatically select the certificate store..." and click Next (it will choose the Personal certificate store, which is good).

  • Click Finish.

  • Open the "Manage computer certificates" control panel (use the search from the Windows menu)

  • Right-click on "Trusted Root Certification Authorities" in the tree, and choose All Tasks > Import…

  • The Store Location will be "Local Machine". If not you need to exit out of the app and make sure you are in the computer certificates control panel, not the user certificates. Click Next.

  • Browse for strongswan.cer. Click Next. (If the file is named strongswan.der, then you'll need to change the file type dropdown to All Files)

  • The certificate store option will be the specific store, "Trusted Root Certification Authoritities". Click Next.

  • Click Finish.

  • Open the "Change virtual private networks (VPN)" control panel.

  • Click the "Add a VPN connection" button

  • VPN provider: Windows (built-in)

  • Connection name: A name of your choice

  • Server name or address: the fully-qualified domain name (or IP) of your VPN server

  • VPN type: IKEv2

  • Type of sign-in info: Certificate

  • User name and Password can stay blank

  • Click "Save"

Then click on the VPN connection you just created and connect.

If you want to route your entire internet connection over the VPN, you need to get to the advanced settings on the IPv4 and enable "Use default gateway on remote network".

  • Open the "Network and Sharing Center"
  • Click "Change adapter settings" (left)
  • Right-click on your VPN and choose Properties
  • Click the "Networking" tab.
  • Click "Internet Protocol Version 4 (TCP/IPv4)", then click "Properties"
  • Tick on "Use default gateway on remote network"
  • OK out of all of those screens.
config setup
#charondebug="all"
conn roadwarrior
keyexchange=ikev2
ike=aes128-sha1-modp1024,aes128-sha1-modp1536,aes128-sha1-modp2048,aes128-sha256-ecp256,aes128-sha256-modp1024,aes128-sha256-modp1536,aes128-sha256-modp2048,aes256-aes128-sha256-sha1-modp2048-modp4096-modp1024,aes256-sha1-modp1024,aes256-sha256-modp1024,aes256-sha256-modp1536,aes256-sha256-modp2048,aes256-sha256-modp4096,aes256-sha384-ecp384,aes256-sha384-modp1024,aes256-sha384-modp1536,aes256-sha384-modp2048,aes256-sha384-modp4096,aes256gcm16-aes256gcm12-aes128gcm16-aes128gcm12-sha256-sha1-modp2048-modp4096-modp1024,3des-sha1-modp1024!
esp=aes128-aes256-sha1-sha256-modp2048-modp4096-modp1024,aes128-sha1,aes128-sha1-modp1024,aes128-sha1-modp1536,aes128-sha1-modp2048,aes128-sha256,aes128-sha256-ecp256,aes128-sha256-modp1024,aes128-sha256-modp1536,aes128-sha256-modp2048,aes128gcm12-aes128gcm16-aes256gcm12-aes256gcm16-modp2048-modp4096-modp1024,aes128gcm16,aes128gcm16-ecp256,aes256-sha1,aes256-sha256,aes256-sha256-modp1024,aes256-sha256-modp1536,aes256-sha256-modp2048,aes256-sha256-modp4096,aes256-sha384,aes256-sha384-ecp384,aes256-sha384-modp1024,aes256-sha384-modp1536,aes256-sha384-modp2048,aes256-sha384-modp4096,aes256gcm16,aes256gcm16-ecp384,3des-sha1!
dpdaction=clear
dpddelay=60s
left=%any
leftid=<VPN SERVER ID>
#leftsubnet=0.0.0.0/0
leftsubnet=<INTERNAL SUBNET>
leftcert=host-vpn.der
leftsendcert=always
right=%any
rightauth=eap-tls
rightsourceip=%dhcp
rightdns=<LAN DNS SERVERS>
eap_identity=%identity
auto=add
# This file holds shared secrets or RSA private keys for authentication.
# RSA private key for this host, authenticating it to any other host
# which knows the public part.
: RSA host-vpn.der
dhcp {
# Always use the configured server address.
force_server_address = yes
# Derive user-defined MAC address from hash of IKE identity.
#identity_lease = no
# Interface name the plugin uses for address allocation.
# interface =
# Whether to load the plugin. Can also be an integer to increase the
# priority of this plugin.
load = yes
# DHCP server unicast or broadcast IP address.
# server = 255.255.255.255
server = <LAN BROADCAST ADDRESS>
}
# IP forwarding so VPN can forward to and from the VPN to the local lan, and internet
net.ipv4.ip_forward=1
@Paulmulonzia
Copy link

When I try to run Ipsec up roadwarrior, I keep getting this error: unable to resolve %any, initiate aborted
tried to check-in and delete nonexisting IKE_SA
establishing connection 'roadwarrior' failed.
What could be the problem

@drwhitehouse
Copy link

This works well for me with High Sierra and not so well with iOS 11.2.2 still troubleshooting atm.

@below
Copy link

below commented Jul 20, 2018

Thanks a lot for posting this, I can't get it to work :(

These are the (probably) relevant lines from my log:

Jul 20 14:33:15 below charon: 12[CFG] selecting traffic selectors for us:
Jul 20 14:33:15 below charon: 12[CFG]  config: 0.0.0.0/0, received: 0.0.0.0/0 => match: 0.0.0.0/0
Jul 20 14:33:15 below charon: 12[CFG]  config: 0.0.0.0/0, received: ::/0 => no match
Jul 20 14:33:15 below charon: 12[CFG] selecting traffic selectors for other:
Jul 20 14:33:15 below charon: 12[CFG]  config: 10.0.0.102/32, received: 0.0.0.0/0 => match: 10.0.0.102/32
Jul 20 14:33:15 below charon: 12[CFG]  config: 10.0.0.102/32, received: ::/0 => no match
Jul 20 14:33:15 below charon: 12[KNL] received netlink error: Protocol not supported (93)
Jul 20 14:33:15 below charon: 12[KNL] unable to add SAD entry with SPI cdd06e1d
Jul 20 14:33:15 below charon: 12[KNL] received netlink error: Protocol not supported (93)
Jul 20 14:33:15 below charon: 12[KNL] unable to add SAD entry with SPI 0d3c02aa
Jul 20 14:33:15 below charon: 12[IKE] unable to install inbound and outbound IPsec SA (SAD) in kernel
Jul 20 14:33:15 below charon: 12[IKE] failed to establish CHILD_SA, keeping IKE_SA
Jul 20 14:33:15 below charon: 12[ENC] generating IKE_AUTH response 10 [ AUTH CPRP(ADDR DNS DNS) N(AUTH_LFT) N(MOBIKE_SUP) N(NO_ADD_ADDR) N(NO_PROP) ]```

@malaiwah
Copy link

malaiwah commented Aug 2, 2018

I was on debian and strongswan/charon are quite modularized. I was getting:

Aug 2 20:12:52 vpnmaster charon: 11[IKE] EAP-Identity request configured, but not supported
Aug 2 20:12:52 vpnmaster charon: 11[IKE] loading EAP_TLS method failed

Turns out I had to install libcharon-extra-plugins (https://packages.debian.org/sid/libcharon-extra-plugins) to make it work.

@gitbls
Copy link

gitbls commented Aug 6, 2018

Thanks for posting this! I've been using iOS with a mobileconfig, but wanted to switch to using cert-based auth. Unfortunately, I'm having an issue and hoping you might have some insight or ideas as to how to pursue. When I connect from my iDevice, Strongswan thinks that the connection has been made, but iOS does not, and closes the VPN connection. My take from this is that the certs are correct, but some validation check is failing on the iOS side. For completeness, here's the script I'm using to create the certs. Thanks for any and all ideas, hints, etc!

#!/bin/bash

$1 = client PC name (without domain)

$2 = client PC user (user@domain.xxx)

$3 = VPN host (FQDN) [optional. default is current host]

if $3 is specified, new CA and VPN Host key/cert will be created

$4 = VPN host IP [optional. default is current host IP]

cd /etc/swanctl
if [ "$1" != "" ]; then
vpnclientpc=$1
else
echo "% No client PC specified"
echo "% $0 [] []"
exit
fi
if [ "$2" != "" ]; then
vpnclientuser=$2
else
echo "% No client user specified"
echo "% $0 [] []"
exit
fi
if [ "$3" != "" ]; then
vpnhost=$3
else
vpnhost=$(hostname -f)
fi

shorthost=$(echo $vpnhost | (IFS="."; read a b c d e; echo $a))
vpnsankey="ipsec.server.$shorthost"
if [ "$4" != "" ]; then
vpnip=$4
else
vpnip=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)
fi
rootca="$shorthost strongSwan Root CA"
dnou="$shorthost-StrongSwan"
fSwanCert="x509/$shorthost-strongswanCert.pem"
fSwanKey="private/$shorthost-StrongswanKey.pem"
fVPNHostCert="x509/$shorthost-vpnHostCert.pem"
fVPNHostKey="private/$shorthost-vpnHostKey.pem"
fClientCert="x509/$shorthost-${vpnclientpc}Cert.pem"
fClientKey="private/$shorthost-${vpnclientpc}Key.pem"

if [ "$3" != "" ]; then
echo ">> Create CA for host $vpnhost IP address $vpnip ($fSwanKey)"
# Create CA
# This is all from https://www.zeitgeist.se/2013/11/22/strongswan-howto-create-your-own-vpn/
#
pki --gen --type rsa --size 4096 --outform pem > $fSwanKey
echo ">> Create CA Cert ($fSwanCert)"
chmod 600 $fSwanKey
# Create self-signed root CA Cert
pki --self --ca --lifetime 3650 --in $fSwanKey --type rsa
--dn "C=US, O=$dnou, CN=$rootca" --outform pem > $fSwanCert
pki --print --in $fSwanCert
echo ">> Create VPN Host Cert ($fVPNHostCert) and Key ($fVPNHostKey)"
# Create VPN Host Key
pki --gen --type rsa --size 4096 --outform pem > $fVPNHostKey
chmod 600 $fVPNHostKey
# Create VPN Host Cert
pki --pub --in $fVPNHostKey --type rsa |
pki --issue --lifetime 3650 --cacert $fSwanCert --cakey $fSwanKey
--dn "C=US, O=$dnou, CN=$vpnhost" --san $vpnhost --san $vpnip --san $vpnsankey
--flag serverAuth --flag ikeIntermediate --outform pem > $fVPNHostCert
pki --print --in $fVPNHostCert
fi
echo ">> Create Client Cert ($fClientCert) and Key ($fClientKey)"

Creat Client Key

pki --gen --type rsa --size 2048 --outform pem > $fClientKey
chmod 600 $fClientKey

Create Client Cert

pki --pub --in $fClientKey --type rsa |
pki --issue --lifetime 730 --cacert $fSwanCert --cakey $fSwanKey
--dn "C=US, O=$dnou, CN=$vpnclientuser" --san $vpnclientuser --outform pem >$fClientCert

Export Client Cert as a PKCS#12 file

echo ">> Export Client Cert as PKCS#12 to p12/$vpnclientpc.p12"
openssl pkcs12 -export -inkey $fClientKey
-in $fClientCert -name "$vpnclientpc VPN Certificate"
-certfile $fSwanCert
-caname "$rootca"
-out p12/$vpnclientpc.p12

@gitbls
Copy link

gitbls commented Aug 8, 2018

I missed your point that the CA cert needed to be loaded onto the iDevice as well as the user cert. Once I loaded the CA cert, everything works fine. This is SO much better than using mobileconfig!

@shawxu
Copy link

shawxu commented Sep 11, 2019

thanks
a lot

@gtrevg
Copy link

gtrevg commented Jan 8, 2020

NOTE: https://support.apple.com/en-us/HT210176

For certificates in iOS 13 and macOS 10.15, any certificate issued after July 1, 2019 must have a validity period of 825 days or fewer for the --lifetime value.

@realindiahotel
Copy link

NOTE: https://support.apple.com/en-us/HT210176

For certificates in iOS 13 and macOS 10.15, any certificate issued after July 1, 2019 must have a validity period of 825 days or fewer for the --lifetime value.

Nah, not when used for IPSec. At least not macOS 11.2.3, I am successfully using certs that expire in the year 4759

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment