ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Secure Your Wireless with IPsec

by Dan Langille
10/21/2004

Wireless access is all the rage. Wireless this, wireless that. Hot spots are turning up everywhere. Many are free. Many have absolutely no security. There are several in my neighborhood. I have no idea who is running them, but at least one is wide open.

This article will show you one method for locking down your wireless network so that nobody but you can use it. This approach will take you beyond WEP and MAC address filtering, both of which are a good start but have known exploits. This article expands upon the IPsec foundation and demonstrates an easy method for securing your Wireless Access Point (WAP).

I will be using FreeBSD 4.10-Stable for this excursion. Please keep your hands and legs within the vehicle at all times. In case of emergency, please follow the directions of your crew. They know what to do.

While writing this article, I have assumed the following:

Related Reading

Building Wireless Community Networks
By Rob Flickenger

Why Bother with Wireless?

Wireless is practically wide open for anyone with a laptop, a wireless card, and the appropriate set of tools. WEP is defeatable. MAC addresses are sniffable and spoofable. In short, you need the next level: IPsec.

If wireless is so risky, why use it?

Convenience.

Wireless is convenient. There are no cables to run. Anyone can pop down to the local Future Shop, buy a wireless access point, plug it in, turn it on, and start surfing the 802.11 information superhighway. Does this sound familiar? It should. I did it and I wrote about it. I did the right thing. I filtered by MAC address. I turned on WEP. Then I turned off WEP when I had trouble. Yes, I was vulnerable, but no one compromised my system ... as far as I know.

Since writing that article nearly 18 months ago, I have moved to a new house. I've gone through the same process I did last time, setting up a new rack and running some cables through the walls. This time I convinced myself that I would set up a secure wireless network. It took me a few hours, but I finally figured it out.

To answer the original question of this section and to tie it in with my recent move, I use wireless so I don't have to run cables. I want to use the laptop in the living room, the dining room, or on the front step. (I'm actually typing this into my Windows XP workstation sitting at my desk in the basement, SSH'd into the new Antec box.) As I work wirelessly, I want to keep people off my private network and keep prying eyes away from my communications. I can do that with IPsec.

Also in my mind is my neighbor, I don't know who, but somebody running a WAP nearby, totally unsecured. I know how easy it would be for me to use that internet connection. I don't want someone using mine.

What Is IPsec?

IPsec is short for IP security. It is a set of protocols for securely exchanging packets at the IP layer. VPNs frequently use it. We can use the same approach to secure our wireless network.

IPsec uses shared secrets to encrypt data. It also uses security policies to decide what types of traffic to encrypt between which hosts.

FreeBSD-specific details

This section outlines some of the details that are specific to IPsec on FreeBSD. Regardless of the operating system you wish to use, you will need an IPsec-enabled kernel. On FreeBSD, add the following directives to your kernel configuration file, and then compile a new kernel:

options IPSEC         #IP security
options IPSEC_ESP     #IP security (crypto; define w/ IPSEC)
options IPSEC_DEBUG   #debug for IP security

I haven't actually used the features of IPSEC_DEBUG, but it's there if I need it.

Note: If you are running 5.x, use FAST_IPSEC instead of the directives mentioned above. Also remove INET6, as FAST_IPSEC does not support it.

Add the following directive to /etc/rc.conf to set up your IPsec database at boot time:

ipsec_enable="YES"

That directive will load your IPsec configuration directives from /etc/ipsec.conf. (You can configure the actual filename using ipsec_file="/your/file/here". I will give you examples for that file later in this article.)

Walk First, Then Run

I'm a big believer in starting small and working one's way toward a goal. For my testing, I first tried IPsec over my wired network, then moved it over to the wireless network. You may find this strategy useful too. It allows you to concentrate on the IPsec portion of the problem, make it work, and then concern yourself with any wireless issues. After you have IPsec running properly, you can remove the wire and start using 802.11 instead.

For this testing, I created a new gateway and put two NICs into the box. This box does NAT (Network Address Translation) using ipnat and will use ipf as a firewall. It's easier to manage with the entire wireless network in a separate subnet. If necessary, I can disconnect the entire subnet by unplugging a single cable or powering off the WAP.

Your NAT box will want to do some forwarding of packets. I recommend the use of ipf and ipnat. I also use ipmon. I have these entries in /etc/rc.conf:

gateway_enable="YES"
ipfilter_enable="YES"
ipnat_enable="YES
ipmon_enable="YES"

Firewall and NAT rules are beyond the scope of this article, but those two links should give you a running start.

A Short Introduction to IPsec

IPsec can create a point-to-point tunnel between two hosts. Being encrypted, the data will be safe from prying eyes, and the gateway won't accept modified packets, since they lack an authentic signature. IPsec can also secure traffic between two networks or a network and a gateway. Other configuration options are available, but I will concentrate on just network, not point to point.

The key point to realize is that IPsec cannot exist on its own. You need to have IPsec at both ends of the communication. You cannot just slap IPsec onto your laptop and expect it to work wherever you go. This is why I have decided to create a wireless gateway through which all my wireless traffic will flow.

Network diagram

The following diagram (created with Xfig) illustrates my wireless network. My laptop sits at 10.0.0.10 and communicates over wireless (802.11) to my WAP. The WAP connects to a dedicated gateway box (via a hub) which sits between the WAP and my LAN.

wireless
network
Figure 1. Wireless network

Any traffic coming in over the wireless network must pass through the WAP and then the wireless gateway. This gateway has two NICs (one at 10.0.0.1, the other at 192.168.0.55). These are conventional, wired NICS. There is no Wifi in this gateway, but there certainly could be. I have chosen to use a WAP instead. The WAP plugs into a hub, and 10.0.0.1 on the gateway plugs into the same HUB. The other NIC plugs into the main LAN.

The IPsec database

IPsec uses a database to decide how to treat traffic. The database contains the rules on what traffic to encrypt and how to encrypt it. The two main types of rules are policy and association. The Security Policy Database (SPD) determines what traffic IPsec should handle. The Security Association Database (SAD) specifies how to encrypt that traffic.

The main tool for manipulating the database is setkey(8). I will show you one way to use that tool later. Usually, you place these rules in /etc/ipsec.conf.

Creating the network tunnel

These rules cause the encryption of all traffic between the network (10.0.0.0/24) and the gateway (10.0.0.1). We will use ESP (Encapsulating Security Payload) as found in RFC 2406. This ensures that nobody can read your data.

Laptop rules
add 10.0.0.1 10.0.0.10 esp 691 -E rijndael-cbc "1234567890123456";
add 10.0.0.10 10.0.0.1 esp 693 -E rijndael-cbc "1234567890123456";

spdadd 10.0.0.0/24 0.0.0.0/0 any -P out ipsec esp/tunnel/10.0.0.10-10.0.0.1/require;
spdadd 0.0.0.0/0 10.0.0.0/24 any -P in ipsec esp/tunnel/10.0.0.1-10.0.0.10/require;

The first two rules (add) are SAD entries. The next two rules (spdadd) are SPD entries.

The add items set up the encryption keys for communication between the two computers. Be sure to use different keys; however, if you use IKE you won't need keys. The values shown are just to keep things easy. The spdadd items set up the actual tunnel between the two computers. In brief, the above directives mean:

  1. Between 10.0.0.1 and 10.0.0.10, use the index 691, the encryption algorithm known as rijndael-cbc, and use a shared secret of "1234567890123456".
  2. Similarly, in the other direction between 10.0.0.10 and 10.0.0.1, use the index 693, the same encryption algorithm, and the same shared secret.
  3. All outgoing communication between the network (10.0.0.0/24) and everywhere else (0.0.0.0) must go (require) through a tunnel.
  4. In the other direction, incoming communication between everywhere else (0.0.0.0) and the network (10.0.0.0/24) must come (require) from a tunnel.
Gateway rules

The rules for the gateway are very similar to the laptop rules and also are slightly symmetric.

add 10.0.0.1 10.0.0.10 esp 691 -E rijndael-cbc "1234567890123456";
add 10.0.0.10 10.0.0.1 esp 693 -E rijndael-cbc "1234567890123456";

spdadd 10.0.0.0/24 0.0.0.0/0 any -P in ipsec esp/tunnel/10.0.0.10-10.0.0.1/require;
spdadd 0.0.0.0/0 10.0.0.0/24 any -P out ipsec esp/tunnel/10.0.0.1-10.0.0.10/require;

You can add these rules manually using setkey -c and then copy and paste the rules from above (after making adjustments so they refer to your IP addresses, not mine). To exit, press Ctrl-D. When testing, I actually keep it running and copy and paste the commands directly. I use these commands to clear out existing database entries before adding new ones.

flush;
spdflush;

The flush command clears out the SAD entries, while spdflush clears out the SPD entries.

In the policy statements (spdadd), the second line above states that all traffic from 10.0.0.0/24 to anywhere requires ESP. The fourth line states that all traffic from anywhere to 10.0.0.0/24 also requires ESP. Combined, these two directives ensure that all traffic from anywhere to anywhere on this network must use ESP.

While I tested these rules, I kept them on a local web site. That made it easier to copy and paste the rules from the browser. I'm not suggesting that you publicly publish your rules. This is just a debugging tool. Mind you, the only parts you need to keep secret are the keys (in my example, 1234567890123456).

If you place your rules in /etc/ipsec.conf and have ipsec_enable="YES" in your /etc/rc.conf, the system will load them at boot-up.

When loading the rules, you'll need to coordinate them. I sat at the gateway console with my laptop beside me. That way, if I messed up the rules, I could reset them without moving. The provided rules worked for me. They should work for you too. If they don't, go back to square one and verify your rules. Ensure that the subnet and the IP addresses are what they should be.

After you implement these rules, the gateway will reject all non-ESP traffic. Furthermore, anyone attempting to communicate with the gateway must have the shared secrets. If you change the secret, nothing will pass the gateway.

Confirming traffic

By this point, you have IPsec on both machines and you have set the IPsec database rules. Traffic is flowing. Now you want to confirm the encapsulation, so nothing should appear in plain text. Here is how I did that.

On my wireless gateway, the dc0 device has an IP address of 10.0.0.1. All traffic from the laptop will come in on that NIC. I issued this command to view that traffic:

# tcpdump -ni dc0 not esp
tcpdump: listening on dc0
13:40:25.651640 0.0.0.0.68 > 255.255.255.255.67: xid:0xebd53d39 [|bootp] [tos 0x10]
13:40:25.656090 10.0.0.1.67 > 10.0.0.10.68: xid:0xebd53d39 Y:10.0.0.10 S:10.0.0.1 
     [|bootp] [tos 0x10]

The above shows dhclient starting on the laptop. A bit of ARP traffic follows. If all you see via tcpdump is stuff like this, then you're good to go.

13:42:18.225304 arp who-has 10.0.0.1 tell 10.0.0.10
13:42:18.225450 arp reply 10.0.0.1 is-at 0:32:91:32:91:32

If all you see is arp, then you're good to go.

You should see what the IPsec traffic looks like. Have a look. Shorten the above command to this:

# tcpdump -ni dc0
tcpdump: listening on dc0
13:44:34.371866 10.0.0.10 > 10.0.0.1: ESP(spi=0x000002b5,seq=0xfe)
13:44:34.385237 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0xec) 
     (frag 368:1480@0+)
13:44:34.385339 10.0.0.1 > 10.0.0.10: esp (frag 368:48@1480)
13:44:34.387672 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0xed) 
     (frag 369:1480@0+)
13:44:34.387775 10.0.0.1 > 10.0.0.10: esp (frag 369:48@1480)
13:44:34.390066 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0xee) 
     (frag 370:1480@0+)
13:44:34.390165 10.0.0.1 > 10.0.0.10: esp (frag 370:48@1480)
13:44:34.390996 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0xef)
13:44:34.393155 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0xf0) 
     (frag 372:1480@0+)
13:44:34.393260 10.0.0.1 > 10.0.0.10: esp (frag 372:48@1480)
13:44:34.394641 10.0.0.10 > 10.0.0.1: ESP(spi=0x000002b5,seq=0xff)
13:44:34.396986 10.0.0.10 > 10.0.0.1: ESP(spi=0x000002b5,seq=0x100)
13:44:34.398044 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0xf1) 
     (frag 373:1480@0+)
13:44:34.398142 10.0.0.1 > 10.0.0.10: esp (frag 373:48@1480)

The above tcpdump is of HTTP traffic as my laptop accessed my development copy of FreshPorts.

Here is how a ping looks:

13:45:39.886113 10.0.0.10 > 10.0.0.1: ESP(spi=0x000002b5,seq=0x118)
13:45:39.887436 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0x10a)
13:45:40.898972 10.0.0.10 > 10.0.0.1: ESP(spi=0x000002b5,seq=0x119)
13:45:40.900134 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0x10b)
13:45:41.908735 10.0.0.10 > 10.0.0.1: ESP(spi=0x000002b5,seq=0x11a)
13:45:41.909912 10.0.0.1 > 10.0.0.10: ESP(spi=0x000002b3,seq=0x10c)

Note: this is all ESP. The following is an example of traffic that does not use IPsec:

$ sudo tcpdump -ni fxp1
tcpdump: listening on fxp1
13:47:39.130953 192.168.0.21.22 > 192.168.0.99.3077: P 903783889:903783933(44) 
     ack 4184487194 win 58400 (DF) [tos 0x10]
13:47:39.151794 192.168.0.99.1763 > 66.197.0.145.6665: . ack 305831024 win 64160 (DF)
13:47:39.252127 192.168.0.99.3077 > 192.168.0.21.22: . ack 44 win 64028 (DF)
13:47:39.621526 192.168.0.18 > 203.118.144.45: icmp: echo request

There you go. All good. Nothing passes through the gateway unless it matches the rules. The shared secret is the key to this security. This would be more secure if the secret changed occasionally, though.

Racoon Likes to Keep Secrets

Instead of manually changing the shared secrets in your /etc/ipsec.conf file, you can keep one shared secret and use the IKE protocol to negotiate a key. Racoon speaks IKE (ISAKMP/Oakley), which is a key management protocol.

I installed Racoon from the ports tree. Here are the configuration files from both my laptop and the gateway. The only difference between the two files is that I instruct the gateway to listen on only one address (it has two NICs). Here is the diff, if you are interested:

--- racoon.conf.laptop        Wed Sep 15 19:26:03 2004
+++ racoon.conf.gateway Wed Sep 15 19:31:46 2004
@@ -33,6 +33,8 @@
        #isakmp 202.249.11.124 [500];
        #admin [7002];          # administrative's port by kmpstat.
        #strict_address;        # required all addresses must be bound.
+
+       isakmp 10.0.0.1;
 }
 
 # Specification of default various timer.

The configuration file tells Racoon the main things it needs to know. One of the items is the preshared key file. Look for this directive:

# search this file for pre_shared_key with various ID key.
path pre_shared_key "/usr/local/etc/racoon/psk.txt" ;

From man racoon.conf:

Pre-shared key File
  Pre-shared key file defines a pair of the identifier and the shared
  secret key which are used at Pre-shared key authentication method in
  phase 1.  The pair in each lines are separated by some number of blanks
  and/or tab characters like hosts(5).  Key can be included any blanks
  because all of the words after 2nd column are interpreted as a secret
  key.  Lines start with #' are ignored.  Keys which start with ' are
  hexa-decimal strings.  Note that the file must be owned by the user ID
  running racoon(8) (usually the privileged user), and must not be accessi-
  ble by others.

Here is the /usr/local/etc/racoon/psk.txt file on my laptop:

10.0.0.1 MySecretValue

Here is the file from the wireless gateway:

10.0.0.10 MySecretValue

With these values, Racoon on my laptop knows that when it talks to 10.0.0.1 (the gateway) it should use the shared key MySecretValue. Similarly, the Racoon running on the gateway knows to use the same shared key when speaking to 10.0.0.10 (my laptop).

To start Racoon, issue this command:

/usr/local/etc/rc.d/racoon.sh start

If you're running a recent version of FreeBSD (for example, 4.10-RELEASE), add this entry in /etc/rc.conf:

racoon_enable="YES"

Ensure that Racoon is running on both the laptop and the gateway. Then remove the SAD entries from both machines, and Racoon should negotiate a new set of keys.

# setkey -c
flush;
^D

If that doesn't work, try running Racoon in the foreground (after first stopping the one running in the background):

/usr/local/sbin/racoon -F

It should just work.

Fun with Keys: Understanding What Happens

I thought it might be interesting to clear out the SAD entries and see what happens when it comes time to negotiate new keys. I started a ping running and ran tcpdump while I issued this command:

# setkey -F

As you can see, the ping missed a few steps. That is understandable.

[dan@laptop:~] $ ping -A 192.168.0.18
PING xeon.unixathome.org (192.168.0.18): 56 data bytes
64 bytes from 192.168.0.18: icmp_seq=0 ttl=63 time=4.880 ms
64 bytes from 192.168.0.18: icmp_seq=1 ttl=63 time=4.847 ms
64 bytes from 192.168.0.18: icmp_seq=2 ttl=63 time=5.126 ms
64 bytes from 192.168.0.18: icmp_seq=3 ttl=63 time=5.209 ms
64 bytes from 192.168.0.18: icmp_seq=6 ttl=63 time=5.468 ms
64 bytes from 192.168.0.18: icmp_seq=7 ttl=63 time=4.838 ms
64 bytes from 192.168.0.18: icmp_seq=8 ttl=63 time=5.270 ms
^C
--- xeon.unixathome.org ping statistics ---
9 packets transmitted, 7 packets received, 22% packet loss
round-trip min/avg/max/stddev = 4.838/5.091/5.468/0.226 ms
[dan@laptop:~] $

Two pings went missing and never reached the machine on the other side of the gateway. Here is some of the tcpdump traffic:

16:36:34.517794 10.0.0.10 > 10.0.0.1: ESP(spi=0x02dc8063,seq=0x1d)
16:36:34.867830 10.0.0.10 > 10.0.0.1: ESP(spi=0x02dc8063,seq=0x1e)
16:36:34.873592 10.0.0.1 > 10.0.0.10: ESP(spi=0x0751ce23,seq=0x1d)
16:36:35.737921 10.0.0.10.500 > 10.0.0.1.500: isakmp: phase 2/others 
     ? inf[E]: [encrypted hash]
16:36:35.904270 10.0.0.10.500 > 10.0.0.1.500: isakmp: phase 2/others 
     ? oakley-quick[E]: [encrypted hash]
16:36:36.604941 10.0.0.1.500 > 10.0.0.10.500: isakmp: phase 2/others 
     ? oakley-quick[E]: [encrypted hash]
16:36:36.605565 10.0.0.10.500 > 10.0.0.1.500: isakmp: phase 2/others 
     ? oakley-quick[E]: [encrypted hash]
16:36:36.887859 10.0.0.10 > 10.0.0.1: ESP(spi=0x01f4fcea,seq=0x1)
16:36:37.897889 10.0.0.10 > 10.0.0.1: ESP(spi=0x01f4fcea,seq=0x2)

The lines that contain isakmp represent the two Racoon daemons negotiating a new key.

As an experiment, I turned off IPsec on my laptop by commenting out the ipsec_enable line in /etc/rc.conf. Then I rebooted. Interestingly, I still received an IP address from my DHCP server on the other side of the wireless gateway. However, I could not get through the gateway. Even simple pings to the gateway went unanswered. At this time, the firewall on the wireless gateway allowed all traffic to pass. Therefore, the gateway rejected the traffic because it did not use IPsec.

To make IPsec work again, I did this while the ping was still running:

[root@laptop:/home/dan] # setkey -f /etc/ipsec.conf
[root@laptop:/home/dan] # tcpdump -ni wi0 tcpdump: listening on wi0
17:21:20.168434 10.0.0.10.500 > 10.0.0.1.500: isakmp: phase 2/others 
     ? oakley-quick[E]: [encrypted hash]
17:21:46.426485 10.0.0.10.500 > 10.0.0.1.500: isakmp: phase 2/others 
     ? oakley-quick[E]: [encrypted hash]
17:21:47.112010 10.0.0.1.500 > 10.0.0.10.500: isakmp: phase 2/others 
     ? oakley-quick[E]: [encrypted hash]
17:21:47.113115 10.0.0.10.500 > 10.0.0.1.500: isakmp: phase 2/others 
     ? oakley-quick[E]: [encrypted hash]
17:21:47.375549 10.0.0.10 > 10.0.0.1: ESP(spi=0x02c19c7a,seq=0x1)
17:21:48.385549 10.0.0.10 > 10.0.0.1: ESP(spi=0x02c19c7a,seq=0x2)
17:21:48.390088 10.0.0.1 > 10.0.0.10: ESP(spi=0x041e48fb,seq=0x1)
17:21:49.395564 10.0.0.10 > 10.0.0.1: ESP(spi=0x02c19c7a,seq=0x3)
17:21:49.399754 10.0.0.1 > 10.0.0.10: ESP(spi=0x041e48fb,seq=0x2)

The first line populates the SPD1 database, based upon the data within the file /etc/ipsec.conf. From there, Racoon must negotiate a new key. It took some time (about 26 seconds), but Racoon eventually succeeded. Immediately thereafter, the pings resumed.

1Actually, the command populates only the SAD because the file in question contains only add commands. I commented out the spdadd commands.

DHCP Server

I mentioned above that I could still receive an IP address from my DHCP server that was running on my gateway. I had not previously mentioned it, but I think it might be useful to you. Here are the basics; the rest you should be able to piece together yourself.

Installing dhcpd

To install the dhcp server, I did this:

# cd /usr/ports/net/isc-dhcp3-server
# make install clean

Starting at boot time

This will install /usr/local/etc/rc.d/isc-dhcpd.sh. Remember to add dhcpd_enable="YES" to /etc/rc.conf, or the script will not start the server. I also added dhcpd_ifaces="dc0" so that dhcpd would listen only on the one NIC--the one attached to the same hub as the WAP.

The configuration file

The port installs /usr/local/etc/dhcpd.conf. It is full of examples, but here is what I'm using, slightly altered to protect the obvious:

default-lease-time 600;
max-lease-time 7200;

authoritative;
ddns-update-style none;

option domain-name "example.org";

#
# this points to my local DNS server on the other
# side of the wireless gateway
#
option domain-name-servers 192.168.0.101;

default-lease-time 86400;
max-lease-time 86400;


# This is a very basic subnet declaration.

subnet 10.0.0.0 netmask 255.255.255.0 {
        option routers 10.0.0.1;
        range 10.0.0.192 10.0.0.207; # this is 10.0.0.200/27 => (28)

        host laptop.example.org {
                option dhcp-client-identifier "laptop.example.org";
                fixed-address laptop.example.org;
        }
}

That fixed-address relates to the following entry in /etc/dhclient.conf:

send dhcp-client-identifier "laptop.example.org";

This allows the laptop to tell the DHCP server who it is, which I use to assign a specific IP address. Note: this method is convenient, but it is not necessarily secure. If you're like me, sometimes using wireless with your laptop and sometimes connecting via wire, then you might want to give it a different IP address depending on where it is. I do that by having two DHCP servers. I'm sure someone will suggest another method.

Is That Enough?

Now you have your wireless laptop connected to your LAN, with encrypted and secured traffic. Nobody else can use your gateway unless they guess the secret key. That's not easy. The key will change from time to time, so even if someone guesses a key, there's a new one coming along soon. The only thing you have to secure is the preshared secret. Don't use what I've supplied. Come up with something odd, even some random values. Pick some text from IRC. That should work.

Are you being paranoid enough? I think for one of my next tasks I will look for any unusual traffic coming on the gateway, from any IP other than my laptop. There are whole books written on intrusion detection, and that topic is well beyond what I can cover here.

Enjoy.

Dan Langille runs a consulting group in Ottawa, Canada, and lives in a house ruled by felines.


Return to the BSD DevCenter.

Copyright © 2009 O'Reilly Media, Inc.