Libreswan server and client configuration

"Perilous to us all are the devices of an art deeper than we possess ourselves." -- Gandalf

Revised 2020-07-01. Corrections/complaints contact Hugh Sparks

What's all this?

This is a mindless walk-through that shows one way of setting up an IKEv2 certificate-based VPN server on Linux that works with Windows, iOS, Android, Linux, and MacOS clients. It's not a tutorial or explanation of the technology. References are cited where you can learn more.

This procedure has been tested on Fedora 31 with Libreswan 3.29 and works with all the clients described as of 2020-07-01. (Recipes like this tend to be ephemeral, so take note of that date.)

Contents


Server Configuration

We begin on the VPN server box which is named "magoo."

Install Libreswan

dnf install libreswan

Initialize the NSS database

ipsec initnss

For some reason, the Fedora package doesn't do this. The command will create an sqlite database in /etc/ipsec.d. It does no harm if the database already exists.

Create a certificate signing authority

In this example, "MagooCorp" is the organization name. "Magoo CA" is the common name of the certificate authority.

certutil -S -x -n "Magoo CA" -s "O=MagooCorp,CN=Magoo CA" \
    -z <(head -c 1024 /dev/urandom) \
    -k rsa -g 4096 -v 120 \
    -d sql:/etc/ipsec.d \
    -t "CT,," -2

You will be prompted for answers:

Q: Is this a CA certificate?
A: y

Q: Enter the path length constraint, enter to skip...
A: <Enter> 

Q: Is this a critical extension?
A: N

Using the server IP number instead of an IP name

The following configuration uses the IP number of the VPN server instead of the IP name. My VPN server is also my internet gateway and it provides services to clients on the LAN that I don't want to make publicly available. The DNS server (bind) is configured to use "views" - My server IP name resolves to its local IP address when used by internal clients and to the public IP number for external clients. The firewall is more permissive for numbers on the internal LAN. But the VPN and associated certificates need a consistent way of refering to the server. So I used the number.

Assign a temporary shell variable for the server's public IP address

PUBLIC_IP=12.34.56.68

This is only needed when creating the server certificate.

Create a server certificate signed by the CA

certutil -S -c "Magoo CA" -n "Magoo VPN" -s "O=MagooCorp,CN=Magoo VPN" \
    -z <(head -c 1024 /dev/urandom) \
    -k rsa -g 4096 -v 120 \
    -d sql:/etc/ipsec.d \
    -t ",," \
    --keyUsage digitalSignature,keyEncipherment \
    --extKeyUsage serverAuth \
    --extSAN "ip:$PUBLIC_IP,dns:$PUBLIC_IP"

Configure a server connection

Create the file:

/etc/ipsec.d/magoo.conf

That contains:

conn magoo
    left=12.34.56.68
    leftcert="Magoo VPN"
    leftid=%fromcert
    leftsendcert=always
    leftsubnet=0.0.0.0/0
    leftrsasigkey=%cert
    leftmodecfgserver=yes
    right=%any
    rightid=%fromcert
    rightaddresspool=192.168.1.200-192.168.1.254
    rightca=%same
    rightrsasigkey=%cert
    rightmodecfgclient=yes
    modecfgdns=192.168.1.2
    narrowing=yes
    dpddelay=30
    dpdtimeout=120
    dpdaction=clear
    auto=add
    ikev2=insist
    rekey=no
    fragmentation=yes
    mobike=yes

Note: On my system, the "left" parameter can't be %defaultroute. I think this is because I have multiple IP numbers on the same adapter. It also can't be the IP name of the server because that gets resolved to a local IP inside the LAN. So the number must be used literally.

Configure the firewall:

Using iptables:

These expressions need to be "worked into" your existing firewall script:

iptables -A INPUT -p udp --dport  500 -j ACCEPT
iptables -A INPUT -p udp --dport 4500 -j ACCEPT
iptables -A INPUT -p esp -j ACCEPT
iptables -I INPUT -m policy --pol ipsec --dir in -j ACCEPT
iptables -I FORWARD -m policy --pol ipsec --dir in -j ACCEPT
iptables -t nat -I POSTROUTING  -m policy --dir out --pol ipsec -j ACCEPT
Alternative: Using firewalld

Note: I haven't tried this. I got these expressions from Derik Cameron's website.

firewall-cmd --add-service=ipsec --permanent
firewall-cmd --add-masquerade --permanent

Configure kernel parameters

The libreswan package includes a kernel parameter file:

/etc/sysctl.d/50-libreswan.conf

It contains:

net.ipv6.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0

Create this file:

/etc/sysctl.d/51-libreswan.conf

Containing:

net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2

These expressions set the reverse path forwarding filter policy to "loose=2". The default setting in Fedora 31 is "strict=1". The VPN described here won't work with that setting.

Assign the new kernel parameters without rebooting using these expressions:

sysctl -p /etc/sysctl.d/50-libreswan.conf
sysctl -p /etc/sysctl.d/51-libreswan.conf

Start the VPN server daemon

systemctl enable --now ipsec

The server is now ready to accept connections.


iOS client configuration

In this example, the phone belongs to "Hugh."

Create a client certificate:

certutil -S -c "Magoo CA" -n "Hugh iPhone to MagooVPN" -s "O=MagooCorp,CN=Hugh iPhone to MagooVPN" \
    -z <(head -c 1024 /dev/urandom) \
    -k rsa -g 4096 -v 120 \
    -d sql:/etc/ipsec.d \
    -t ",," \
    --keyUsage digitalSignature,keyEncipherment \
    --extKeyUsage serverAuth,clientAuth -8 "Hugh iPhone to MagooVPN"

Export the client certificate:

pk12util -d sql:/etc/ipsec.d -n "Hugh iPhone to MagooVPN" -o hugh_iphone.p12

You will be prompted for a password. The iPhone won't import a
certificate with a null password.

Export the CA certificate as a .cer file:

certutil -L -d sql:/etc/ipsec.d -n "Magoo CA" -a -o magoo_ca.cer

Note: Only iOS needs a separate CA file. The .p12 file contains this information but Apple "thinks different."

These files will appear in the current directory:

magoo_ca.cer
hugh_iphone.p12

Transfer certificates to the phone:

Enclose them in an email and send it to the iphone owner. There are other ways using the iOS Files application.

Import the certificates on the client:

On the phone, open the mail and click on "magoo_ca.cer" It will transfer the certificate to the certificate store on the phone. The certificate is in a pending state - not yet imported.

Go the the certificate store here:

Settings -> General -> Profiles

Click on the new certificate and click a series of more-or-less obvious buttons to get it "really imported."

Go back to the email and do the same thing with the "hugh_iphone.p12" client certificate. This time while it's in the pending state, it will be displayed as an anonymous "Identity Certificate" - The phone can't read the actual CA name inside until the encryption is removed. You'll be prompted to supply the password when the import step occurs. When the process is finished it will appear in the store as "Hugh iPhone".

It is best to delete the exported certificate files.

Configure a VPN connection

Go to:

Settings -> General -> VPN

Click "Add VPN connection" and fill in the form:

You are ready to make a connection.


Windows client configuration

In this example, the Windows box is named "Asus."

Create a client certificate:

certutil -S -c "Magoo CA" -n "Asus to MagooVPN" -s "O=MagooCorp,CN=Asus to MagooVPN" \
    -z <(head -c 1024 /dev/urandom) \
    -k rsa -g 4096 -v 120 \
    -d sql:/etc/ipsec.d \
    -t ",," \
    --keyUsage digitalSignature,keyEncipherment \
    --extKeyUsage serverAuth,clientAuth -8 "Asus to MagooVPN"

Export .p12 certificate:

pk12util -d sql:/etc/ipsec.d -n "Asus to MagooVPN" -o asus_magoo.p12

Transfer certificate to Windows client

If you're on a LAN, just copy it to the client's desktop. Otherwise mail it as an enclosure and save it on the client desktop.

Import the client certificate:

It's best to delete the exported certificate files.

Create a VPN connection

Enable high security encryption

This is not an option: Modern version of Librswan won't work with Microsoft's default 1024 bit encryption.

Create a plain text file:

High Security VPN - Install.reg

Containing:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RasMan\Parameters]
"NegotiateDH2048_AES256"=dword:00000001

Right-click on the file and select "Merge"

You are ready to connect.


Android client configuration

In this example, the Android device is named "Hughpad."

Create a client certificate:

certutil -S -c "Magoo CA" -n "Hughpad to MagooVPN" -s "O=MagooCorp,CN=Hughpad to MagooVPN" \
    -z <(head -c 1024 /dev/urandom) \
    -k rsa -g 4096 -v 120 \
    -d sql:/etc/ipsec.d \
    -t ",," \
    --keyUsage digitalSignature,keyEncipherment \
    --extKeyUsage serverAuth,clientAuth -8 "Hughpad to MagooVPN"

Export .p12 certificate:

pk12util -d sql:/etc/ipsec.d -n "Hughpad to MagooVPN" -o hughpad_magoo.p12

Transfer certificate to client

Email it as an attachement and save it on the client. The default location will be something like:

/storage/emulated/0/Download/hughpad_magoo.p12

Install VPN software (Android 4.x to 9.x)

Run the google play store app, search for and install "strongSwan VPN Client"

Create a VPN connection

When you start the connection, a number of nags, warnings and prompts will appear. One will rant about "Unknown beings are intercepting your internet traffic" or something like that. Just say yes to everything.

You are ready to connect.


Linux client configuration

In this example, the Linux box is named "Robor."

Create a client certificate:

certutil -S -c "Magoo CA" -n "Robor2MagooVPN" -s "O=MagooCorp,CN=Robor2MagooVPN" \
    -z <(head -c 1024 /dev/urandom) \
    -k rsa -g 4096 -v 120 \
    -d sql:/etc/ipsec.d \
    -t ",," \
    --keyUsage digitalSignature,keyEncipherment \
    --extKeyUsage serverAuth,clientAuth -8 "Robor2MagooVPN"

Note: When using the NetworkManager GUI, the certificate nickname cannot contain spaces or underscore characters.

Export .p12 certificate:

pk12util -d sql:/etc/ipsec.d -n "Robor2MagooVPN" -o robor_magoo.p12

Transfer the .p12 file to the client machine:

You'll figure something out.

Initialize the NSS database: (if not already present on the client)

ipsec initnss

Import the client certificate:

pk12util -d sql:/etc/ipsec.d -i robor_magoo.p12

You'll be prompted for the password. The certificate contains the nickname specified when the certificate was created.

Create a VPN connection with NetworkManager

From the GUI run:

Network Connections

OR from a console window run:

nm-connection-editor

Connect or disconnect using the network icon on the task bar.

Alternative: Create a VPN connection "by hand"

Create the file:

/etc/ipsec.d/magoo.conf

Containing:

conn magoo
    left=%defaultroute
    leftcert=Robor2MagooVPN
    leftmodecfgclient=yes
    right=12.34.56.68
    rightsubnet=0.0.0.0/0
    rightmodecfgserver=yes
    narrowing=yes
    ikev2=insist
    rekey=yes
    fragmentation=yes
    auto=add    
    mobike=yes

Start the service

systemctl enable --now ipsec

Connect using:

ipsec auto --up magoo

Disconnect using:

ipsec auto --down magoo

MacOS client configuration

In this example, the MacOS box is named "MyMac"

Create a client certificate:

certutil -S -c "Magoo CA" -n "MyMac2MagooVPN" -s "O=MagooCorp,CN=MyMac2MagooVPN" \
    -z <(head -c 1024 /dev/urandom) \
    -k rsa -g 4096 -v 120 \
    -d sql:/etc/ipsec.d \
    -t ",," \
    --keyUsage digitalSignature,keyEncipherment \
    --extKeyUsage serverAuth,clientAuth -8 "MyMac2MagooVPN"

Export .p12 certificate:

pk12util -d sql:/etc/ipsec.d -n "MyMac2MagooVPN" -o mymac_magoo.p12

Transfer the .p12 file to the client machine:

You'll figure something out.

Import the client certificate:

Double-click on the mymac_magoo.p12 file. You'll be prompted for the certificate password. The Keychain Access utility will launch and display two new certificatesin the System keychain area:

CSparksCA
MyMac2CSparksVPN

The CSparksCA certificate will have a red X on the icon because it's untrusted. To fix that, right-click on "CSparksCA"

Get Info -> Trust -> When using this certificate -> Always trust

The red X may still appear. This is a bug in the GUI only. You will be prompted again for you MacOS credentials.

Back out.

Create a VPN connection

Run:

System Preferences -> Network -> "+"

The dialog will refresh and display two radio buttons.

The first time I did this, I got prompted for yet another Admin username and password.

In the future, you can connect or disconnect using the VPN icon on the task bar.


Appendix - Diagnostics

Configuration check:

/usr/libexec/ipsec/addconn --config /etc/ipsec.conf --checkconfig

Runtime configuration check:

ipsec verify

Check individual connection configuration:

ipsec auto --add magoo

Monitor ipsec startup and connection progress:

journalctl -f -t pluto

Check I/O counters

ipsec whack --trafficstatus

The trafficstatus command can be used to see if you're actually using the VPN:. Execute the command several times and confirm that the I/O counts are stationary. Then ping an internet site and run the command again. This time, the numbers should go up.

For a continuous view, create a new console window and run the command:

watch -n 1 ipsec whack --trafficstatus

That command will run every second so you can see the I/O counts changing while you browse the web or execute pings in another console window.


Appendix - Certificate operations

Using certutil to manage keys

Certutil is a high-level way of creating and managing SSL certificates.

You will be surprised to see nothing in the current directory as you create various certificates with certutil. It keeps all keys in a database. This reduces clutter at the expense of learning another abstraction.

IPsec expects the certificate database to be in the default directory:

/etc/ipsec.d/

Certutil, on the other hand, has no concept of a default directory. Hence we must must constantly specify:

-d sql:/etc/ipsec.d

The sqlite database is in a single file:

/etc/ipsec.d/cert9.db

List all the certificates in the database

certutil -L -d sql:/etc/ipsec.d

List details for a specific certificate

certutil -L -d sql:/etc/ipsec.d -n "Nickname"

Change a certificate nickname

certutnil --rename -n OldNickname --new-n NewNickname -d /etc/ipsec.d

Delete a certificate

certutil -D -d sql:/etc/ipsec.d -n "Nickname"

Export a client certificate in .p12 format:

pk12util -d sql:/etc/ipsec.d -n "Hugh iPhone to MagooVPN" -o hugh_iphone.p12

The utility will prompt for a password.

Import a client certificate from a .p12 file:

pk12until -d sql:/etc/ipsec.d -i robor_magoo.p12

The utitlity will prompt for the password specified when the certificate was exported.

Export a CA certificate in .cer format: (Just the public part)

Some clients want the certificate authority certificate as well as the client certificate. (iPhone is an example.)

certutil -L -d sql:/etc/ipsec.d -n "Magoo CA" -a -o magoo_ca.cer

Revoke a client certificate

If someone steals your phone or laptop that has a client certificate, they can access your office LAN if they guess or circumvent the user account login credentials.

The quick and easy thing to do is simply delete the client certificate from the store on the server. But if the phone or laptop is found, a new certificate will have to be installed at both ends.

A more sophisticated approach is to revoke the client certificate. A revoked certificate can be reinstated if the phone or laptop was simply misplaced.

Certificates are revoked by adding them to a certificate revocation list "CRL" - This is an optional feature that must be added to the certificate authority. You only need to do this once: the same CRL can be used to revoke any client certificate signed by the CA.

Create a CRL

For the "Magoo CA":

crlutil -G -d sql:/etc/ipsec.d -n "Magoo CA" -c /dev/null

The output will show some information about the new CRL.

Don't accidentally create another CRL for the same CA: You can check for a CLR using the expression:

crlutil -G -d sql:/etc/ipsec.d -n "Magoo CA" -c /dev/null

If there's no output, the CRL doesn't exist.

Get the certificate serial number

List the store and note the nickname of the client certificate you want to revoke:

certutil -L -d sql:/etc/ipsec.d

Show the certifcate details and note the serial number:

certutil -L -d sql:/etc/ipsec.d -n "Hugh iPhone"

The serial number appears in the first few lines of the dump. It will look like this:

...
Serial Number:
    00:b5:1a:f2:61

Convert the serial number to a decimal value:

echo $((b51af261))

Result: (example)

3038442081
Revoke the certificate

Specify the time you want the revocation to occur in generalized time format YYYYMMDDhhmmssZ - The time is in UTC and the Z at the end is required. Example:

20200624053200Z

Add the client certificate to the CRL:

crlutil -M -d sql:/etc/ipsec.d -n "Magoo CA" <<EOF
addcert 3446275956 20200606220100Z
EOF

Tell ipsec to reload the CRL:

ipsec crls

The deed is done.

After a reasonble time, revoked certificates can be removed from the CRL (so it doesn't get too big) and simply deleted from the server certificate store. At that point there is no way to reinstate them.

Un-revoke a certificate

If you want to reinstate a certificate, simply remove it from the CRL. You need the serial number and the current time:

crlutil -M -d sql:/etc/ipsec.d -n "Magoo CA" <<EOF
rmcert 3446275956 20200606220100Z
EOF

References