IPsec Setup

I wanted to configure transport-mode IPsec between some of the hosts on my local network. It was a bit of a struggle.

The platforms here are Debian jessie and unstable (as of April 2016) and Windows 10.

Goals

My network has increasingly many devices on it which I have limited control over. No GPL-violating lightbulbs (yet) but modern consumer technology is increasingly made up of network-connected devices running software I have no real control over or even sight of, and putting it in the same layer 2 network as a wide variety of complex protocol servers makes me nervous.

I’m worried about failures of either confidentiality or integrity. It’s true that network equipment provide limited mitigations in both cases, and that there are application-level mitigations (I use SSH and actually check key fingerprints, and many other protocols now use opportunistic encryption). Nevertheless:

Therefore I want an application-neutral layer of defence, so I can spend less time worrying about just how resistant to attack any given bit of network software is.

For these reasons, and also as a learning exercise, I decided to try point-to-point IPsec transport mode associations between individual hosts.

Prerequisites

I configured all participating hosts to have static IP addresses, eliminating the need to special-case DHCP and avoiding any need to keep up with changing addresses.

Linux-to-Windows: Windows Setup

Here I set up IPsec between 172.17.207.66 (Windows) and 172.17.207.12 (Linux), and also the corresponding IPv6 addresses.

Global Settings

Some global defaults compatible with the Linux implementation must be set. Use the Windows Firewall with Advanced Security snapin, which should look something like this:

Select Properties and then the IPsec settings tab; from there select Customize from the IPsec defaults.

(I’ve actually disabled IPsec for ICMP, despite how it looks in the screenshot. See below for discussion.)

Leave the authentication method alone. In the Key exchange and Data protection settings you need to choose algorithms which are (i) good enough for your security requirements and (ii) supported by the Linux implementation. For instance:

Both sides can do better than SHA-1 and 2048-bit groups but not, as far as I can see, in a compatible way.

Tunnel Settings

Back in Windows Firewall with Advanced Security snapin, right click Connection Security Rules and create a new server-to-server rule. I stepped through the wizard and then tinkered until I got it working; the screenshots below show the end state visible in the Properties dialog for the new rule.

I named the rule after the Linux system. Endpoint 1 is the local endpoint (Windows); endpoint 2 the remote one (Linux). I had to configure the Windows system to use a fixed IPv6 address, as I couldn’t figure out how to make everything keep up to date with temporary addresses.

I wanted authentication (and encryption) both ways. I’d have preferred to use pre-shared RSA keys (which is how I authenticated Linux systems to one another), on the grounds that it minimizes the spread of secrets but doesn’t put an X.509 parser on the attack surface or require messing about with CAs. However this option wasn’t available in the Windows implementation so I went with a pre-shared key.

Most IPsec setup guides don’t tell you much about choosing the key, other than vague advice like “use /dev/random” - but not how long it should be or how it will be used. It’s used to key the IPsec PRF which as far as I can tell is always HMAC. I used:

dd if=/dev/urandom bs=1 count=15|base64

Linux-to-Windows: Linux Setup

Kernel Policy

The kernel-level policy is set as follows:

#!/usr/sbin/setkey -f
flush;
spdflush;

# Don't require encryption for ICMP
spdadd ::/0 ::/0 icmp6 -P out priority 10 none;
spdadd ::/0 ::/0 icmp6 -P in priority 10 none;
spdadd 0.0.0.0/0 0.0.0.0/0 icmp -P out priority 10 none;
spdadd 0.0.0.0/0 0.0.0.0/0 icmp -P in priority 10 none;
   
spdadd 172.17.207.12 172.17.207.66 any -P out ipsec esp/transport//require ah/transport//require;
spdadd 172.17.207.66 172.17.207.12 any -P in ipsec esp/transport//require ah/transport//require;

spdadd 2001:470:1f09:11ed::c 2001:470:1f09:11ed::42 any -P out ipsec esp/transport//require ah/transport//require;
spdadd 2001:470:1f09:11ed::42 2001:470:1f09:11ed::c any -P in ipsec esp/transport//require ah/transport//require;

I’ve disabled encryption for ICMP. The IPv6 neighbor-discovery parts need to be unencrypted, since key exchange can’t take place without resolving IP addresses to link layer addresses; and Windows can’t selectively disable a subset of ICMP.

This file needs to be executed during system boot (and each time is modified). I did this by making it executable and adding a pre-up declaration in /etc/network/interfaces:

auto eth0
iface eth0 inet static
 address 172.17.207.12
 gateway 172.17.207.1
 netmask 255.255.255.0
 pre-up /etc/racoon/setkey.conf

IKE Configuration

I used racoon to do the key exchange. Global configuration is as follows:

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

I tried to use privsep but it crashed.

The IPv4 configuration is as follows:

remote 172.17.207.66 {
       proposal {
		encryption_algorithm aes;
		hash_algorithm sha256;
		authentication_method pre_shared_key;
		dh_group modp2048;
		lifetime time 480 min;
       }
       exchange_mode main;
       my_identifier address;
       peers_identifier address;
       verify_identifier on;
}

sainfo anonymous address 172.17.207.66 any {
       #pfs_group modp2048;
       encryption_algorithm aes;
       authentication_algorithm hmac_sha1;
       compression_algorithm deflate;
}

pfs_group seems to be toxic in this configuration, despite the Windows-side setting requesting DH key exchange. I’m not sure what’s going on here.

The IPv6 configuration is identical except for the addresses.

Keys

Keys are stored in /etc/racoon/psk.txt:

172.17.207.66   XXXXXXXXXXXXXXXXXXXX
2001:470:1f09:11ed::42   XXXXXXXXXXXXXXXXXXXX

This file should not be world-readable:

-rw------- 1 root root 83 Apr 17 17:05 /etc/racoon/psk.txt

Linux-to-Linux

Here I set up IPsec between 172.17.207.1 (sfere) and 172.17.207.12 (deodand). The configuration reflects the files on the latter; the configuration on the former is the same but with the obvious changes. In this case I was able to use RSA authentication and choose stronger groups and algorithms.

At the time of writing I’ve not done IPv6 yet.

Kernel Policy

#!/usr/sbin/setkey -f
flush;
spdflush;

# Don't require encryption for ICMP
spdadd ::/0 ::/0 icmp6 -P out priority 10 none;
spdadd ::/0 ::/0 icmp6 -P in priority 10 none;
spdadd 0.0.0.0/0 0.0.0.0/0 icmp -P out priority 10 none;
spdadd 0.0.0.0/0 0.0.0.0/0 icmp -P in priority 10 none;
   
spdadd 172.17.207.12 172.17.207.1 any -P out ipsec esp/transport//require ah/transport//require;
spdadd 172.17.207.1 172.17.207.12 any -P in ipsec esp/transport//require ah/transport//require;

See above for how I applied this policy at startup.

As in the Linux-Windows case I’ve disabled IPsec for ICMP. Linux's more flexible configuration means that a more limited disablement would be sufficient.

Keys

plainrsa-gen -b 3072 -f /etc/racoon/deodand.key

The public key can be extracted from the first line and distributed to the peers.

IKE Configuration

I have lots of Linux hosts so expect to make the configuration failure common:

remote 0.0.0.0 {
       proposal {
                encryption_algorithm aes;
                hash_algorithm sha256;
                authentication_method rsasig;
                dh_group modp3072;
       }
       exchange_mode main;
       my_identifier address;
       peers_identifier address;
       verify_identifier on;
       certificate_type plain_rsa "/etc/racoon/deodand.key";
}

remote 172.17.207.1 inherit 0.0.0.0 {
       peers_certfile plain_rsa "/etc/racoon/sfere.pub";
}

sainfo anonymous {
       pfs_group modp3072;
       encryption_algorithm aes;
       authentication_algorithm hmac_sha256;
       compression_algorithm deflate;
}

Troubleshooting

Make sure you aren’t disrupting the connectivity you’re actually using! My approach was to get IPv4 working while using an IPv6 session into the Linux system, then bounce off a second Linux device to maintain access to it while sorting out IPv6.

Keep an eye on daemon.log.

Leave a something running across the connection to be secured. (Don’t use ping if you disabled encryption for ICMP). When it’s working it should look something like this in tcpdump output:

17:26:10.489791 IP6 2001:470:1f09:11ed::c > 2001:470:1f09:11ed::42: AH(spi=0x303bfcdc,seq=0x29e6): ESP(spi=0xadb71052,seq=0x29e6), length 116
17:26:10.490619 IP6 2001:470:1f09:11ed::42 > 2001:470:1f09:11ed::c: AH(spi=0x0779c4d7,seq=0x124c): ESP(spi=0x0efbebc2,seq=0x124c), length 116

RJK | Contents