VR on IPsec VPN Series:

  1. VR on IPsec VPN Part 1 - First Peek Into The Rabbit Hole
  2. VR on IPsec VPN Part 2 - Experimental Setup

1. Introduction

In Part 1, we went through why IKEv2 is such a juicy (and painful) target. Before we can start crafting packets or poking at the state machine, we need a lab that we fully control.

We will be building strongSwan from source instead of pulling it from the package manager. There are two good reasons for this:

  1. Debug symbols – Compiling with -g -O0 makes life a lot easier when we start stepping through the daemon with gdb or cross-referencing functions in Ghidra later on.

  2. Session key extraction – The save-keys plugin is not enabled in most distro builds. With it, strongSwan dumps the IKE and ESP session keys in a format Wireshark understands, so we can decrypt every captured message.

The lab consists of two Linux hosts on the same isolated network:

Role IP Address IKE Identity Notes
Client 192.168.122.3 road@lab Requests a virtual IP from the server
Server 192.168.122.4 192.168.122.4 Protects 192.168.100.4/32 and hands out virtual IPs from 10.8.0.0/24
Road Warrior Setup Plan

roadwarrior-setup-diagram

Road Warrior Setup Diagram

We will configure two flavours of the same road warrior connection: one authenticated with a Pre-Shared Key (PSK), and the other with digital signatures (certificates). This gives us captures of both IKE_AUTH variants to compare against in the later parts.

2. Building strongSwan from Source

The steps in this section apply to both the client and the server.

2.1 Installing Build Dependencies

First, get rid of any package-managed strongSwan so that it does not clash with our own build, then install the build dependencies.

# Remove any legacy / package-managed strongSwan
$ sudo apt remove --purge strongswan strongswan-starter strongswan-charon 2>/dev/null
$ sudo apt autoremove

# Build dependencies
$ sudo apt update
$ sudo apt install -y build-essential pkg-config libssl-dev libsystemd-dev
Removing Existing strongSwan and Installing Build Dependencies

2.2 Downloading the Source

At the time of writing, the latest release is strongSwan 6.1.0.

$ cd /usr/local/src
$ sudo wget https://download.strongswan.org/strongswan-6.1.0.tar.gz
$ sudo tar -zxvf strongswan-6.1.0.tar.gz
$ cd strongswan-6.1.0
Downloading and Extracting strongSwan 6.1.0

2.3 Compilation & Installation

Now, let’s configure the build. The important options here are:

  • --enable-systemd builds charon-systemd, a daemon that integrates with systemd and logs straight into the journal.
  • --enable-save-keys builds the plugin that dumps session keys for Wireshark.
  • CFLAGS="-g -O0" keeps debug symbols and disables optimizations, so that what we see in the debugger matches the source code.
$ sudo ./configure \
    --prefix=/usr \
    --sysconfdir=/etc \
    --enable-systemd \
    --with-systemdsystemunitdir=/etc/systemd/system \
    --enable-save-keys \
    CFLAGS="-g -O0"

$ sudo make -j$(nproc)
$ sudo make install
Configuring and Compiling strongSwan

3. Configuring the Server

All connection configurations live in /etc/swanctl/conf.d/, and are loaded by swanctl --load-all when the service starts. To make our lives easier when testing, we shall configure the server to accept both PSK and certificate-based authentication methods.

3.1 Pre-Shared Key (PSK)

The server listens on 192.168.122.4 and accepts any initiator (id = %any) that knows the PSK. A few things worth pointing out:

  • proposals restricts the IKE SA to AES-CBC-128, SHA2-256 integrity, HMAC-SHA2-256 PRF and DH group 14 (modp2048). This is the exact proposal we will be dissecting in Part 3.
  • dpd_delay makes the server send an empty INFORMATIONAL message (heartbeat) every 30 seconds when the tunnel is idle.
  • pools hands out virtual IPs to clients from rw-pool.
connections {
  rw-psk {
    version = 2
    local_addrs  = 192.168.122.4
    proposals    = aes128-sha2_256-prfsha256-modp2048
    rekey_time   = 4h
    dpd_delay    = 30s
    dpd_timeout  = 120s
    pools        = rw-pool

    local {
      auth = psk
      id   = 192.168.122.4
    }
    remote {
      auth = psk
      id   = %any
    }

    children {
      net {
        mode          = tunnel
        local_ts      = 192.168.100.4/32
        remote_ts     = dynamic
        esp_proposals = aes128-sha2_256
        start_action  = trap
        dpd_action    = clear
      }
    }
  }
}

pools {
  rw-pool {
    addrs = 10.8.0.0/24
  }
}

secrets {
  ike-rw {
    id     = %any
    secret = "lab-psk-change-me"
  }
}
Server PSK Configuration - /etc/swanctl/conf.d/roadwarrior-psk.conf

Warning
The lab-psk-change-me secret is only meant for an isolated lab. Never reuse a weak, hardcoded PSK on a real deployment.

3.2 Digital Signatures

For certificate-based authentication, we first need a Certificate Authority (CA). We can use strongSwan’s own pki tool to generate the CA, then issue a server certificate signed by it.

# Generate CA private key (ECDSA P-256)
$ pki --gen --type ecdsa --size 256 --outform pem > caKey.pem

# Self-sign the CA certificate (valid 10 years)
$ pki --self --ca --lifetime 3650 \
    --in caKey.pem --type ecdsa \
    --dn "CN=Lab CA" \
    --outform pem > caCert.pem

# Generate server private key
$ pki --gen --type ecdsa --size 256 --outform pem > serverKey.pem

# Create a CSR for the server
$ pki --req --type ecdsa \
    --in serverKey.pem \
    --dn "CN=192.168.122.4" \
    --outform pem > serverReq.pem

# Issue the server certificate (valid 2 years)
$ pki --issue --lifetime 730 \
    --cacert caCert.pem --cakey caKey.pem \
    --in serverReq.pem --type pkcs10 \
    --san 192.168.122.4 \
    --flag serverAuth \
    --outform pem > serverCert.pem
Generating CA and Server Certificate

Notice that the server certificate’s Subject Alternative Name (SAN) is set to 192.168.122.4. This must match the server’s IKE identity, otherwise the client will reject it during IKE_AUTH.

Next, install the certificates and private key into their respective swanctl directories:

$ sudo cp caCert.pem     /etc/swanctl/x509ca/
$ sudo cp serverCert.pem /etc/swanctl/x509/
$ sudo cp serverKey.pem  /etc/swanctl/private/
$ sudo chmod 600         /etc/swanctl/private/serverKey.pem
Installing Server Certificates and Private Key

The connection configuration is nearly identical to the PSK one. The only differences are that auth is now set to pubkey, the server presents its own certificate, and clients must present a certificate signed by our CA.

connections {
  rw-cert {
    version = 2
    local_addrs  = 192.168.122.4
    proposals    = aes128-sha2_256-prfsha256-modp2048
    rekey_time   = 4h
    dpd_delay    = 30s
    dpd_timeout  = 120s
    pools        = rw-pool

    local {
      auth  = pubkey
      certs = serverCert.pem
      id    = 192.168.122.4
    }
    remote {
      auth    = pubkey
      cacerts = caCert.pem
      id      = %any
    }

    children {
      net {
        mode          = tunnel
        local_ts      = 192.168.100.4/32
        remote_ts     = dynamic
        esp_proposals = aes128-sha2_256
        start_action  = trap
        dpd_action    = clear
      }
    }
  }
}

pools {
  rw-pool {
    addrs = 10.8.0.0/24
  }
}
Server Certificate Configuration - /etc/swanctl/conf.d/roadwarrior-cert.conf

3.3 Starting the Service

Enable and start the service:

$ sudo systemctl enable --now strongswan
$ sudo systemctl restart strongswan
Starting strongSwan Service

Then, verify that both connections and the address pool are loaded:

$ sudo swanctl --list-conns
$ sudo swanctl --list-pools
Verifying Loaded Connections and Pools

4. Configuring the Client

4.1 Pre-Shared Key (PSK)

The client configuration mirrors the server’s. This time, we specify remote_addrs so that the client knows who to initiate to, and vips = 0.0.0.0 to request a virtual IP from the server’s pool. Setting start_action = start makes the client bring up the tunnel as soon as the configuration is loaded.

connections {
  rw-psk {
    version = 2
    local_addrs  = 192.168.122.3
    remote_addrs = 192.168.122.4
    proposals    = aes128-sha2_256-prfsha256-modp2048
    rekey_time   = 4h
    dpd_delay    = 30s
    dpd_timeout  = 120s
    vips         = 0.0.0.0

    local {
      auth = psk
      id   = road@lab
    }
    remote {
      auth = psk
      id   = 192.168.122.4
    }

    children {
      net {
        mode          = tunnel
        local_ts      = dynamic
        remote_ts     = 192.168.100.4/32
        esp_proposals = aes128-sha2_256
        start_action  = none
        dpd_action    = clear
      }
    }
  }
}

secrets {
  ike-rw {
    id-1   = 192.168.122.4
    secret = "lab-psk-change-me"
  }
}
Client PSK Configuration - /etc/swanctl/conf.d/client-psk.conf

4.2 Digital Signatures

Generate the client certificate on the CA host (or wherever caKey.pem and caCert.pem reside), then copy the relevant files over to the client.

# Generate client private key (ECDSA P-256)
$ pki --gen --type ecdsa --size 256 --outform pem > clientKey.pem

# Create a CSR for the client
$ pki --req --type ecdsa \
    --in clientKey.pem \
    --dn "CN=road@lab" \
    --outform pem > clientReq.pem

# Issue the client certificate (valid 2 years)
$ pki --issue --lifetime 730 \
    --cacert caCert.pem --cakey caKey.pem \
    --in clientReq.pem --type pkcs10 \
    --san road@lab \
    --flag clientAuth \
    --outform pem > clientCert.pem
Generating Client Certificate

Install the certificates and private key on the client:

$ sudo cp caCert.pem     /etc/swanctl/x509ca/
$ sudo cp clientCert.pem /etc/swanctl/x509/
$ sudo cp clientKey.pem  /etc/swanctl/private/
$ sudo chmod 600         /etc/swanctl/private/clientKey.pem
Installing Client Certificates and Private Key

connections {
  rw-cert {
    version = 2
    local_addrs  = 192.168.122.3
    remote_addrs = 192.168.122.4
    proposals    = aes128-sha2_256-prfsha256-modp2048
    rekey_time   = 4h
    dpd_delay    = 30s
    dpd_timeout  = 120s
    vips         = 0.0.0.0

    local {
      auth  = pubkey
      certs = clientCert.pem
      id    = road@lab
    }
    remote {
      auth    = pubkey
      cacerts = caCert.pem
      id      = 192.168.122.4
    }

    children {
      net {
        mode          = tunnel
        local_ts      = dynamic
        remote_ts     = 192.168.100.4/32
        esp_proposals = aes128-sha2_256
        start_action  = none
        dpd_action    = clear
      }
    }
  }
}
Client Certificate Configuration - /etc/swanctl/conf.d/client-cert.conf

4.3 Session Key Extraction

This is the part that makes the lab actually useful for research. Edit /etc/strongswan.conf on both hosts:

  • ike = 4 under charon-systemd.journal raises the IKE subsystem’s log level to the most verbose setting, which includes the raw session keys in the journal.
  • The save-keys plugin writes the IKE SA keys to ikev2_decryption_table and the ESP SA keys to esp_sa inside the Wireshark configuration directory, so Wireshark can decrypt the captured traffic on the fly.
charon-systemd {
  journal {
    default = 1
    ike     = 4
  }
}

charon {
  load_modular = yes

  plugins {
    include strongswan.d/charon/*.conf

    save-keys {
      load           = yes
      esp            = yes
      ike            = yes
      wireshark_keys = /home/kali/.config/wireshark
    }
  }
}

include strongswan.d/*.conf
Debug Logging and save-keys Configuration - /etc/strongswan.conf

Tip
Change wireshark_keys to the Wireshark configuration directory of whichever user runs Wireshark on your machine.

5. Managing VPN Sessions

Ensure that the charon-systemd is enabled on startup and is running before we use swanctl to manage our VPN session.

$ sudo systemctl enable strongswan
$ sudo systemctl restart strongswan
Enabling & Restarting strongSwan Service

To manage your VPN session, run the following commands:

# Start VPN session (Create IKE SA + Child SA)
## ------------- PSK ------------- ##
$ sudo swanctl --initiate --ike rw-psk --child net
## ------ Digital Signature ------ ##
$ sudo swanctl --initiate --ike rw-cert --child net

# Stop VPN session (Delete IKE SA)
## ------------- PSK ------------- ##
$ sudo swanctl --terminate --ike rw-psk
## ------ Digital Signature ------ ##
$ sudo swanctl --terminate --ike rw-cert

# Stop VPN session but preserve IKE SA (Delete Child SA only)
## ------------- PSK ------------- ##
$ sudo swanctl --terminate --ike rw-psk --child net
## ------ Digital Signature ------ ##
$ sudo swanctl --terminate --ike rw-cert --child net
Managing VPN sessions

7. Conclusion

We now have a strongSwan server and client built with debug symbols, two road warrior connections using PSK and certificate authentication, and session keys being dumped straight into Wireshark. With this setup, every IKEv2 message going across the wire can be decrypted and inspected.

In the next part, we will use this lab to break down the different IKEv2 message types, and compute the session keys ourselves to understand exactly what goes into each message.

8. References

  1. strongSwan Downloads
  2. strongSwan swanctl.conf documentation
  3. strongSwan pki documentation
  4. strongSwan save-keys plugin
  5. strongSwan Logging documentation
  6. RFC 7296 – Internet Key Exchange Protocol Version 2 (IKEv2)

9. Resources

  1. DrawIO Diagram for Setup

VR on IPsec VPN Series:

  1. VR on IPsec VPN Part 1 - First Peek Into The Rabbit Hole
  2. VR on IPsec VPN Part 2 - Experimental Setup