IPsec

From Segfault
Jump to navigation Jump to search

There are various IPsec implementations available:

Name Supported Platforms Status
FreeS/WAN Linux ceased operations in April 2004
Openswan FreeBSD, Linux forked from FreeS/WAN in 2004, active as of 2019
Libreswan Linux forked from Openswan in 2012, active as of 2019
strongSwan Linux, Android, Maemo, FreeBSD, MacOS X, Windows forked from FreeS/WAN in 2005, active as of 2019
IPsec-Tools Linux, FreeBSD, NetBSD Ported from KAME's IPsec utilities, last release from 2014-02-27


ipsec-tools

setkey

Although ipsec-tools have been deprecated[1], let's try to use it anyway and build a simple tunnel between two nodes.

apt-get install ipsec-tools                                                   # Debian, Ubuntu
dnf install ipsec-tools                                                       # Fedora

On both hosts, create a configuration file /etc/ipsec-tools.d/local.conf:

flush;
spdflush;

# AH  - Authentication Header
add 10.0.0.10 10.0.0.20 ah 0x200 -A hmac-sha256
       0xb11e3d9bf0daa34dd091e40fd36b5e1ebcbd3ffbeff8f94703efe8db93c681fe;
add 10.0.0.20 10.0.0.10 ah 0x300 -A hmac-sha256
       0x623b2c4b1db7240dd34dafd01b0f2e4e7fc688c781208c005547db1417ac756e;

# ESP - Encapsulating Security Payload
add 10.0.0.10 10.0.0.20 esp 0x201 -E aes-ctr
        0x61537b6f4596d6d27e6a83575adc84364a20956930240c20ac9ae4d6;
add 10.0.0.20 10.0.0.10 esp 0x301 -E aes-ctr
        0x4335a97be5781e954643a9e922c43d8faa28015031b941617c5f6089;

# SPD - Security Policy Database
spdadd 10.0.0.10 10.0.0.20 any -P out ipsec
        esp/transport//require
        ah/transport//require;

spdadd 10.0.0.20 10.0.0.10 any -P in  ipsec
        esp/transport//require
        ah/transport//require;
  • They key lengths are important here, see setkey(1) for details. For example, hmac-ripemd160 only accepts 160 bit keys, so we have to generate a key of that exact length:
dd if=/dev/random bs=1 count=$(expr 160 / 8) 2>/dev/null | xxd -c 256 -ps
openssl rand -hex $(expr 160 / 8)
  • Some modes may not be available until the correct kernel module is loaded. This may be tricky to find out. E.g. xcbc is not sufficient for cbc modes, and aes-x86_64 may be needed for aes ciphers, etc.

On the other node, the same configuration is needed, but the Security Policy entries need to be reversed:

# SPD - Security Policy Database
spdadd 10.0.0.10 10.0.0.20 any -P in  ipsec
        esp/transport//require
        ah/transport//require;

spdadd 10.0.0.20 10.0.0.10 any -P out ipsec
        esp/transport//require
        ah/transport//require;

Once both files are in place (and only readable by root), we can load the configuration into the kernel:

setkey -f /etc/ipsec-tools.d/local.conf                                       # Some init script may be available to do this during startup.


We can use tcpdump to see if our encrypted tunnel is working. Before the IPsec policy was loaded, a ping request and its reply would like this:

$ tcpdump -c2 -n host 10.0.0.10
16:03:34.388305 IP 10.0.0.10 > 10.0.0.20: ICMP echo request, id 9512, seq 4, length 64
16:03:34.388383 IP 10.0.0.20 > 10.0.0.10: ICMP echo reply, id 9512, seq 4, length 64

After only one side has its policy in place, the ping requests were still noted, but not replied to:

16:03:51.936657 IP 10.0.0.10 > 10.0.0.20: ICMP echo request, id 9513, seq 1, length 64
16:03:52.943532 IP 10.0.0.10 > 10.0.0.20: ICMP echo request, id 9513, seq 2, length 64

Once both sides have their policy loaded, all we see is the encrypted traffic:

16:04:07.832305 IP 10.0.0.10 > 10.0.0.20: AH(spi=0x00000200,seq=0x4): ESP(spi=0x00000201,seq=0x4), length 84
16:04:07.832592 IP 10.0.0.20 > 10.0.0.10: AH(spi=0x00000300,seq=0x4): ESP(spi=0x00000301,seq=0x4), length 84

Racoon

Additionally, we can also use racoon for key and IP management, a common configuration.

First, let's generate our pre-shared key:

dd if=/dev/random bs=1 count=$(expr 192 / 8) 2>/dev/null | xxd -c 256 -ps     # Adjust key length as needed
openssl rand -hex $(expr 192 / 8)

Add the key to /etc/racoon/psk.txt on both nodes, each pointing to the other remote. So, on 10.0.0.10 we would add:

10.0.0.20   acca3d446a1c0e9e28469f936bb2c3817df4bb3c69233753

And on 10.0.0.20 we would add:

10.0.0.10   acca3d446a1c0e9e28469f936bb2c3817df4bb3c69233753

With that in place, we can configure /etc/racoon/racoon.conf on both nodes, again each referencing the other remote node. Note that this time we will create a seprate network for our VPN. So, on 10.0.0.10 we have:

log notify;
path pre_shared_key "/etc/racoon/psk.txt";

# Remotes
remote 10.0.0.20 {
       exchange_mode main,aggressive;
       proposal {
               encryption_algorithm blowfish;
               hash_algorithm sha256;
               authentication_method pre_shared_key;
               dh_group modp2048;
       }
}

# SA, with internal networks (local=>remote)
sainfo address 172.16.1.0/24 any address 172.16.2.0/24 any {
       pfs_group modp2048;
       encryption_algorithm blowfish;
       authentication_algorithm hmac_sha256;
       compression_algorithm deflate;
}

And on the other node, 10.0.0.20:

log notify;
path pre_shared_key "/etc/racoon/psk.txt";

# Remotes
remote 10.0.0.10 {
       exchange_mode main,aggressive;
       proposal {
               encryption_algorithm blowfish;
               hash_algorithm sha256;
               authentication_method pre_shared_key;
               dh_group modp2048;
       }
}

# SA, with internal networks (local=>remote)
sainfo address 172.16.2.0/24 any address 172.16.1.0/24 any {
       pfs_group modp2048;
       encryption_algorithm blowfish;
       authentication_algorithm hmac_sha256;
       compression_algorithm deflate;
}

Because the PSK has been configured with racoon, our ipsec-tools configuration can be simplified, but we have to pay attention to the new SA entries, this time in tunnel mode:

spdadd 172.16.1.0/24 172.16.2.0/24 any -P out ipsec
          esp/tunnel/10.0.0.10-10.0.0.20/require;

spdadd 172.16.2.0/24 172.16.1.0/24 any -P in  ipsec
          esp/tunnel/10.0.0.20-10.0.0.10/require;

And on the remote node:

spdadd 172.16.1.0/24 172.16.2.0/24 any -P in  ipsec
          esp/tunnel/10.0.0.10-10.0.0.20/require;

spdadd 172.16.2.0/24 172.16.1.0/24 any -P out ipsec
          esp/tunnel/10.0.0.20-10.0.0.10/require;

After starting both setkey and racoon, we still have to adjust our routing:

ip addr add 172.16.1.1/24 dev eth0 && ip route add to 172.16.2.0/24 via 172.16.1.1 src 172.16.1.1

And on the remote node:

ip addr add 172.16.2.1/24 dev eth0 && ip route add to 172.16.1.0/24 via 172.16.2.1 src 172.16.2.1

This all needs to go into network scripts, but here's how to tear it all down again:

ip route del 172.16.2.0/24 dev eth0 && ip addr del 172.16.1.1/24 dev eth0
ip route del 172.16.1.0/24 dev eth0 && ip addr del 172.16.2.1/24 dev eth0     # For the remote node

iproute2

We can also build a VPN with the standard iproute2 toolset.[2] Once configured, we should arrive at something like this:

$ ip xfrm state && ip xfrm policy
src 192.168.56.144 dst 192.168.56.130
       proto esp spi 0x865f38ae reqid 2254387374 mode tunnel
       replay-window 0 
       auth-trunc hmac(sha256) 0x182b3d4674e8387ddcd3df82185568d87ab33db6b07cbe53d97e6e13f60c376b 96
       enc cbc(blowfish) 0x3ac0d5c10202087bbe8ff043a7922914f7c44fd6e694b0023e0996745baf98ae
       anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
       sel src 0.0.0.0/0 dst 0.0.0.0/0 
src 192.168.56.130 dst 192.168.56.144
       proto esp spi 0x865f38ae reqid 2254387374 mode tunnel
       replay-window 0 
       auth-trunc hmac(sha256) 0x182b3d4674e8387ddcd3df82185568d87ab33db6b07cbe53d97e6e13f60c376b 96
       enc cbc(blowfish) 0x3ac0d5c10202087bbe8ff043a7922914f7c44fd6e694b0023e0996745baf98ae
       anti-replay context: seq 0x0, oseq 0x7f3e5, bitmap 0x00000000
       sel src 0.0.0.0/0 dst 0.0.0.0/0 

src 172.16.2.1/32 dst 172.16.1.1/32 
       dir in priority 0 ptype main 
       tmpl src 192.168.56.144 dst 192.168.56.130
               proto esp reqid 2254387374 mode tunnel
src 172.16.1.1/32 dst 172.16.2.1/32 
       dir out priority 0 ptype main 
       tmpl src 192.168.56.130 dst 192.168.56.144
               proto esp reqid 2254387374 mode tunnel

And on the remote side:

$ ip xfrm state && ip xfrm policy
ip xfrm state
src 192.168.56.144 dst 192.168.56.130
       proto esp spi 0x865f38ae reqid 2254387374 mode tunnel
       replay-window 0 
       auth-trunc hmac(sha256) 0x182b3d4674e8387ddcd3df82185568d87ab33db6b07cbe53d97e6e13f60c376b 96
       enc cbc(blowfish) 0x3ac0d5c10202087bbe8ff043a7922914f7c44fd6e694b0023e0996745baf98ae
       anti-replay context: seq 0x0, oseq 0x1cc8b, bitmap 0x00000000
       sel src 0.0.0.0/0 dst 0.0.0.0/0 
src 192.168.56.130 dst 192.168.56.144
       proto esp spi 0x865f38ae reqid 2254387374 mode tunnel
       replay-window 0 
       auth-trunc hmac(sha256) 0x182b3d4674e8387ddcd3df82185568d87ab33db6b07cbe53d97e6e13f60c376b 96
       enc cbc(blowfish) 0x3ac0d5c10202087bbe8ff043a7922914f7c44fd6e694b0023e0996745baf98ae
       anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
       sel src 0.0.0.0/0 dst 0.0.0.0/0 
src 172.16.1.1/32 dst 172.16.2.1/32 
       dir in priority 0 
       tmpl src 192.168.56.130 dst 192.168.56.144
               proto esp reqid 2254387374 mode tunnel
src 172.16.2.1/32 dst 172.16.1.1/32 
       dir out priority 0 
       tmpl src 192.168.56.144 dst 192.168.56.130
               proto esp reqid 2254387374 mode tunnel

Libreswan

Install the libreswan package and generate a secret on each side:

$ ipsec initnss --nssdir /var/lib/ipsec/nss
Initializing NSS database

$ ipsec newhostkey --bits 3072 --output /etc/ipsec.d/test.secrets
Generated RSA key pair with CKAID f25fa16922b06aaa2cd8f828049cb8098472a084 was stored in the NSS database

On the remote side:

$ ipsec initnss --nssdir /var/lib/ipsec/nss
Initializing NSS database

$ ipsec newhostkey --bits 3072 --output /etc/ipsec.d/test.secrets
Generated RSA key pair with CKAID bcdfa3e6210e83d59c54dac35989018aa7b96c08 was stored in the NSS database

The CKAID can be used to lookup and show the actual secrets:

$ ipsec showhostkey --list
< 1> RSA keyid: AwEAAcN28 ckaid: f25fa16922b06aaa2cd8f828049cb8098472a084

$ ipsec showhostkey --left --ckaid f25fa16922b06aaa2cd8f828049cb8098472a084
       # rsakey AwEAAcN28
       leftrsasigkey=0sAwEAAcN28aOnHIhmUaZPrhZRmeD04PUuXQ/lql/Ac2EffrVfLnijc93duYdfGfgFEtk[...]

Example configuration for a simple host-to-host VPN:

config setup
       protostack=netkey

conn test
       leftid=@left
       left=192.168.56.130
       leftrsasigkey=0sAwEAAcN28aOnHIhmUaZPrhZRmeD04PUuXQ/lql/Ac2EffrVfLnijc93duYdfGfgFEtkGA[...]

       rightid=@right
       right=192.168.56.144
       rightrsasigkey=0sAwEAAcHr+05UAVJwnFOA12jhpM2z5hQ5kfklJ4084UXjSAaTG+cwYJXPGd640Ng1oxQr[...]

       authby=rsasig
       ikev2=insist
       ike=aes_gcm256-sha2
       auto=add

The same configuration can be used on the remote side too.

Start the IPsec framework, on both nodes:

$ ipsec setup --start
Redirecting to: systemctl start ipsec.service

Add and start the connection:

$ ipsec auto --add test
002 "test": deleting non-instance connection
002 "test" #2: deleting state (STATE_V2_IPSEC_I) and sending notification
005 "test" #2: ESP traffic information: in=0B out=0B
002 "test" #1: deleting state (STATE_PARENT_I3) and sending notification
002 added connection description "test"
$ ipsec auto --up test
002 "test" #3: initiating v2 parent SA
133 "test" #3: initiate
002 "test" #3: constructed local IKE proposals for test (IKE SA initiator selecting KE): 1:IKE:ENCR=AES_GCM_C_256;PRF=HMAC_SHA2_256;INTEG=NONE;DH=MODP2048
133 "test" #3: STATE_PARENT_I1: sent v2I1, expected v2R1
002 "test" #3: constructed local ESP/AH proposals for test (IKE SA initiator emitting ESP/AH proposals): 1:ESP:ENCR=AES_GCM_C_256;INTEG=NONE;ESN=DISABLED 2:ESP:ENCR=AES_GCM_C_128;INTEG=NONE;ESN=DISABLED 3:ESP:ENCR=AES_CBC_256;INTEG=HMAC_SHA2_512_256,HMAC_SHA2_256_128;ESN=DISABLED 4:ESP:ENCR=AES_CBC_128;INTEG=HMAC_SHA2_512_256,HMAC_SHA2_256_128;ESN=DISABLED 5:ESP:ENCR=AES_CBC_128;INTEG=HMAC_SHA1_96;ESN=DISABLED (default)
134 "test" #4: STATE_PARENT_I2: sent v2I2, expected v2R2 {auth=IKEv2 cipher=AES_GCM_16_256 integ=n/a prf=HMAC_SHA2_256 group=MODP2048}
002 "test" #4: IKEv2 mode peer ID is ID_FQDN: '@right'
003 "test" #4: Authenticated using RSA
002 "test" #4: negotiated connection [192.168.56.130-192.168.56.130:0-65535 0] -> [192.168.56.144-192.168.56.144:0-65535 0]
004 "test" #4: STATE_V2_IPSEC_I: IPsec SA established tunnel mode {ESP=>0x91463e52 <0x68ec030f xfrm=AES_GCM_16_256-NONE NATOA=none NATD=none DPD=passive}

Stop the connection with:

$ ipsec auto --down test
002 "test": terminating SAs using this connection
002 "test" #4: deleting state (STATE_V2_IPSEC_I) and sending notification
005 "test" #4: ESP traffic information: in=0B out=0B
002 "test" #3: deleting state (STATE_PARENT_I3) and sending notification

$ ipsec auto --delete test
002 "test": deleting non-instance connection

Stop the whole IPsec framework:

$ ipsec setup --stop
Redirecting to: systemctl stop ipsec.service

strongSwan

strongSwan can be quite difficult to configure and appears to focus on bigger setups, where authentication keys are managed by a PKI. But let's skip all that and just create a simple host-to-host configuration.

sudo apt-get install strongswan libstrongswan-standard-plugins strongswan-pki
  • libstrongswan-standard-plugins is needed to handle ECDSA or ed25519 keys.[3]. And since we're messing with ike and esp settings[4], we need to install this.
  • strongswan-pki would be needed to provide the ipsec pki command.

Example configuration:

$ cat /etc/ipsec.conf
conn test
       authby=psk
       keyexchange=ike
       left=192.168.56.130
       right=192.168.56.144
       type=transport
       auto=route
       ike=aes128gcm16-prfsha256-ecp256!
       esp=aes128gcm16!

The same configuration can be used on the remote side too.

Generate a PSK:

$ tail -2 /etc/ipsec.secrets
# openssl rand -hex $(expr 192 / 8)
192.168.56.130 192.168.56.144 : PSK "69b9b50297bdfd9cbb87a1c990dc811f203c3046265ed638"

Once both the configuration and ipsec.secrets have been copied to the remote side, the ipsec framework (namely the charon daemon) need to be started, on both hosts.

$ ipsec restart
Stopping strongSwan IPsec...
Starting strongSwan 5.7.2 IPsec [starter]...

IPsec is now active, but we don't have a running SA yet:

$ ipsec status
Routed Connections:
       test{1}:  ROUTED, TRANSPORT, reqid 1
       test{1}:   192.168.56.130/32 === 192.168.56.144/32
Security Associations (0 up, 0 connecting):
 none

The first packet will be used to initiate the SA:

$ ping -c2 192.168.56.144
PING 192.168.56.144 (192.168.56.144) 56(84) bytes of data.
64 bytes from 192.168.56.144: icmp_seq=2 ttl=64 time=1.58 ms
--- 192.168.56.144 ping statistics ---
2 packets transmitted, 1 received, 50% packet loss, time 28ms
rtt min/avg/max/mdev = 1.584/1.584/1.584/0.000 ms

And now we have something like this:

$ ipsec status
Routed Connections:
       test{1}:  ROUTED, TRANSPORT, reqid 1
       test{1}:   192.168.56.130/32 === 192.168.56.144/32
Security Associations (1 up, 0 connecting):
       test[1]: ESTABLISHED 13 seconds ago, 192.168.56.130[192.168.56.130]...192.168.56.144[192.168.56.144]
       test[1]: IKE proposal: AES_GCM_16_128/PRF_HMAC_SHA2_256/ECP_256
       test{2}:  INSTALLED, TRANSPORT, reqid 1, ESP SPIs: ce9cb6bc_i c7aa0fe0_o
       test{2}:   192.168.56.130/32 === 192.168.56.144/32

WireGuard

WireGuard is all the rage these days, and its setup is quite straightforward too. Let's build a host-to-host VPN for testing purposes. Install as needed:

apt-get install linux-headers-$(uname -r)                          # Debian
dnf install wireguard                                              # Fedora

For this Ubuntu system, the following was needed:[5]

$ cat /etc/apt/sources.list.d/wireguard.list 
deb http://ppa.launchpad.net/wireguard/wireguard/ubuntu bionic main 

$ gpg --keyserver hkp://keys.gnupg.net --recv-keys AE33835F504A1A25
$ gpg --armour --export AE33835F504A1A25 | apt-key add -

Generate a key pair on each side:

cd /etc/wireguard/
umask 0266
wg genkey | tee privatekey | wg pubkey > publickey

Create a configuration on each side:

[Interface]
Address = 172.16.1.1/16
ListenPort = 51820
PrivateKey = <privatekey1>

[Peer]
PublicKey  = <publickey2>
AllowedIPs = 172.16.2.1/32
Endpoint   = 192.168.56.144:51820

And on the remote side:

[Interface]
Address = 172.16.2.1/16
ListenPort = 51820
PrivateKey = <privatekey2>

[Peer]
PublicKey = <publickey1>
AllowedIPs = 172.16.1.1/32
Endpoint = 192.168.56.130:51820

Activate the new interface on both nodes:

wg-quick up wg0

And we should be good to go:

# wg show wg0 
interface: wg0
 public key: U65vBZ01dvtHI4vX1RZ7wJ9tEI2UvC9ztBhZJ8h5QyE=
 private key: (hidden)
 listening port: 51820

peer: Y61kB3RUVsaAeREwwk0/pBXUOLb9ZtRfvw3ZXMkgXC0=
 endpoint: 192.168.56.144:51820
 allowed ips: 172.16.2.1/32
 latest handshake: 3 minutes, 11 seconds ago
 transfer: 604 B received, 724 B sent

Tinc

TBD
How to Set up tinc, a Peer-to-Peer VPN

SSH

We can also use SSH to create a tunnel between two hosts.[6] For this to work, PermitTunnel must be enabled on the remote side's sshd. First we have to configure our tun device on both sides:

sudo ip tuntap add dev tun1 mode tun user $(id -n -u) group $(id -n -g)
sudo ip addr add 10.0.0.10/32 peer 10.0.0.20 dev tun1

And on the remote side:

sudo ip tuntap add dev tun0 mode tun user $(id -n -u) group $(id -n -g)
sudo ip addr add 10.0.0.20/32 peer 10.0.0.10 dev tun0

Note: with ifconfig, the same would look like this for the remote side:

sudo ifconfig tun0 up 
sudo ifconfig tun0 10.0.0.20 pointopoint 10.0.0.10 netmask 255.255.255.255                             # pointopoint can be omitted in NetBSD

With that in place, we can use SSH to establish a tunnel:

ssh -NTf -w 1:0 192.168.56.144                                                                         # Maybe add -C for compression?

The command should go into background and now we should have a working connection. performance may not be great though, in part because TCP Over TCP Is A Bad Idea

sshuttle

sshuttle states in its documentation that it should prevent the TCP-over-TCP problem:

Unlike most VPNs, sshuttle forwards sessions, not packets. That is, it uses kernel transparent
proxying (iptables REDIRECT rules on Linux) to capture outgoing TCP sessions, then creates
entirely separate TCP sessions out to the original destination at the other end of the tunnel.

Usage:

sshuttle --verbose --dns --remote dummy@10.0.0.20 0.0.0.0/0

This will now send all traffic through our remote host, 10.0.0.20.

VTUN

TBD
vtun

fastd

TBD
Fast and Secure Tunnelling Daemon (fastd)
fastd documentation

QuickTun

TBD
QuickTun

Links


References