Encrypted Tunnels with FreeS/WAN's x509 Patch
In countries where a private or semi-public WAN is something not every company can afford, the Internet is the only option available to connect all of those remote offices. The obvious problems are this is a public network and, in most cases, we don't get a static IP address. We know that sending data through the Internet is like talking to somebody in an elevator--everybody just can't help hearing what you're saying. So, not encrypting the data is not an option.
Many types of tunnels can be used and VPNs can be put together in many ways, but the IPsec implementation of Linux (FreeS/WAN) is by far the most secure and compatible way to do it. In this article, I explain how to establish LAN-to-LAN tunnels using the x509 patch and only one static IP address. I also tell you two ways to get around the four tunnel inconvenience.
FreeS/WAN has two main parts: the IPsec patch for the kernel (KLIPS) that implements AH (authentication header), ESP (encapsulating security payload) and packet handling within the kernel; and the userland tools (pluto). Pluto implements IKE (internet key exchange), which negotiates the many connections with other systems.
The first thing to do is make the kernel understand IPsec, which can be done by applying the IPsec patch to the kernel's source tree and compiling it. People at the FreeS/WAN project have done a great job documenting the whole process, but for the impatient users (like myself), shortcuts are lifesavers. So, the most recommended way of doing this is not doing it all; instead, install the packages ready to be downloaded from the FreeS/WAN web site. Because I'm going to talk about the x509 FreeS/WAN patch here, download the x509 "already patched" packages.
The installation of these RPM packages is no different from any other .rpm package:
# rpm -ivh freeswan-version-module.rpm # rpm -ivh freeswan-version.rpm
A word of advice: if you install the packages this way, do not try to compile the sources in the .tgz packages. Doing so will overwrite the FreeS/WAN binaries (pluto's tools) before you know it, and get you into trouble.
Once the installation is done, you're ready to link your offices with encrypted tunnels.
Two files play a major role in configuring FreeS/WAN, the /etc/ipsec.conf file and the /etc/ipsec.secrets file. Things always can get more difficult, but because we want to keep this simple, let's play with only these two guys. The ipsec.conf file contains all the information about how the tunnels are established, and the ipsec.secrets file contains all the passwords and things we want to keep, well, secret.
The ipsec.conf file consists of two sections, the config and the conn section. Right now the only config section recognized by FreeS/WAN is "setup config". The config setup section contains all the information the software needs to know at the time it starts up. Here's an example from this section:
config setup interfaces=%defaultroute klipsdebug=none plutodebug=none plutoload=%search plutostart=%search uniqueids=yes
The most important value here is the interfaces parameter; the %defaultroute special value means the interface that pluto uses to establish an encrypted tunnel is the one that has the system's default route. The debug parameters, klipsdebug and plutodebug, need changing only when you have strange problems. The default messages receive get in the logs (messages and auth/secure) are, in most cases, enough to understand where the problem is.
The conn sections tell what type of tunnels pluto can accept or establish. Let's start with an example of what I'm going to call "the concentrator", which is the secure gateway (sgw) in front of the LAN to which everybody wants to connect. This concentrator is the only gateway (gw) that needs a static IP address in my example, and it also is the center of the star network topology used in this case.
conn %default keyingtries=0 authby=rsasig leftrsasig=%cert leftcert=hostBobcert.pem leftsubnet=192.168.1.0/24 leftnexthop=the Concentrator's default gateway rightrsasig=%cert
As this is the default conn section, the parameters set here apply to all the conn sections. The keyingtries line states how many times pluto tries to establish a specific tunnel; a zero value means keep trying. The authby parameter can have one of two values, secret, for pre-shared secret keys authentication and rsasig, which is used to authenticate the sgw using a RSA digital signature. leftrsasig tells pluto how to get the RSA signature from the left side of the tunnel. %cert is an x509-specific value that says the public key should be extracted from an X.509 certificate sent by the peer. leftcert is an x509-specific parameter that informs pluto which file has the x509 certificate on the left side of the tunnel. In this example, the concentrator's certificate is Bob.
The FreeS/WAN people did a great job of naming things. Instead of using the local or remote names, they chose left and right; thus, the same names can be used in any peer's ipsec.conf file. The left name is always used to point to the same sgw; that is, left is always left and right is always right, no matter which sgw you're configuring. In this example, left is the concentrator.
The x509 patch is great because of two main functionalities. First, it's easy to administrate once you get the certificate authority (CA) in place, and second, the ipsec.conf file can be extremely small if you use host-to-LAN tunnels. Using CRLs (certificate revocation lists), the VPN administrator can invoke easily a certain certificate, and the certificate then will no longer be accepted as valid by pluto. The whole idea of using x509 certificates is that the concentrator can create tunnels only with peers that have a valid certificate (that'd be a certificate signed by our CA and not found in the CRL).
The difficult part of using this patch is creating the CA, the certificates themselves and signing them. In order to do this, we use OpenSSL. Begin by creating a CA certificate:
# openssl req -x509 -newkey rsa:2048 -keyout cakey.pem -out cacert.pem
This command asks a few simple questions; make sure you write down what you enter. The cakey.pem file must be stored in the /etc/ipsec.d/private directory, and the cacert.pem file in the /etc/ipsec.d/cacerts directory. Now we need to create all the hosts certificates. These files can be created on any system, but the private and signed public keys of the hosts should be stored in these hosts.
This next command creates the hosts (request of) certificates:
# openssl req -newkey rsa:1024 -keyout hostAlicekey.pem -out hostAlicereq.pem
This command also asks a few questions. At the Organization Name question you should write the same name used on the CA certificate. We do this in order to sign this certificate with less configuration adjustments than are strictly necessary.
Before sending the certificates to the different hosts, they need to be signed with the CA we created above. Here's the tricky part--we have to make some adjustments before we sign the hosts certificates.
Copy the openssl.cnf file to the /etc/ipsec.d directory. Edit the openssl.cnf file and change the dir variable under the CA_default section. Replace the ./demoCA value with something like ./. Also, change the certificate variable for something like $dir/cacerts/cacert.pem. In order to finish these adjustments, create an empty file called index.txt in the /etc/ipsec.d directory and another file called serial with the number 01 inside it. Now we're ready to sign certificates with our brand new CA:
# openssl ca -in hostAlicereq.pem -out hostAlicecert.pem -notext -config ./openssl.cnf
Once the certificates are signed, they are ready to be sent to the peers involved. As I said before, we need to send the private and the signed certificate of the peer. You should do this in the most secure way you can think of as the peer's private key is highly sensitive (the signed certificate is public, so we don't really care). For this example I used hostAlice*.pem for the peer and hostBob*.pem for the concentrator (I ran the last two commands twice). Remember to put all the *cert.pem files in the /etc/ipsec.d directory, and make sure you're in /etc/ipsec.d when you sign the hosts' certificates.
We need to add a connection in the concentrator ipsec.conf file, because we're creating a LAN-to-LAN tunnel. Pluto, at the concentrator's side, needs to know what LAN is on the peer's side. If the tunnel is a host-to-LAN type, we need to configure only one conn with the right=%any and auto=add parameters. Yes, those two lines are enough.
conn Alice-to-Bob right=%any rightid="/C=AR/ST=Buenos Aires/O=Xtreme Technologies S.A./CN=alice.coletti.com.ar" rightsubnet=192.168.0.0/24 auto=add
On the peer's side, the ipsec.conf file should look like:
conn %default keyingtries=1 authby=rsasig leftrsasigkey=%cert rightrsasigkey=%cert conn lan-lan right=%defaultroute rightsubnet=192.168.0.0/24 rightcert=hostAlicecert.pem left=<Concentrator's public IP Address> leftnexthop=<Concentrator's default gateway> leftsubnet=192.168.1.0/24 leftid="/C=AR/ST=Buenos Aires/O=Xtreme Technologies S.A./CN=roma.xtech.com.ar" auto=auto
So, we've got a lot of new variables. The leftid is the subject field of the concentrator's x509 certificate we created above. Each certificate has its own leftid. With the command
openssl x509 -in hostAlicecert.pem -subject -notext -text | grep Subject:
you can see this id, which is the subject field of the x509 certificate.(You can replace the commas for the slash; pluto understands it both ways.) The rightcert= parameter points to the file containing the signed certificate. If the signed certificates are in the /etc/ipsec.d directory, there's no need to write the absolute PATH to it. Because the concentrator's certificate is not on the peer side, we specify only its id. In order to establish a LAN-to-LAN tunnel, the leftsubnet/rightsubnet parameters must be in this conn section. If one or both parameters are missing, pluto thinks you're willing to do some type of host-to-LAN tunnel or host-to-host tunnel.
The last parameter, auto=start, orders pluto to start the tunnel as soon as the IPsec service is started. If you don't want this (and I don't recommend it when you're setting things up), then the recommended value is add, and the tunnel must be started manually with the userland tools.
The only configuration left to do is setting up the ipsec.secrets file, which should be in the /etc directory. The content of this file is simple:
: RSA <privkey_file> "<password>"
where <privkey_file> is the private key of this host certificate. If this file--say, hostAlicekey.pem--is in the /etc/ipsec.d/private directory, absolute PATH won't be needed. <password> is the password you entered when you created this certificate, and it must be closed with double quotes.
So, quick review: all the certificates (the signed ones) have to be in the /etc/ipsec.d directory; every peer needs only one signed certificate (which is its own); the private keys should be in the /etc/ipsec.d/private directory (you only need the private key of this peer's certificate); and the CA certificate should be in the /etc/ipsec.d/cacerts directory.
One last piece of advice, if you see an invalid certificate" message when you add your conns, check the date against the Valid time line of the certificate. The date should be between the Not before and Not after values of the certificate.
Once everything is in place, start the pluto dæmon with the command service ipsec start on both sides. Bring up the tunnel on the peer's side, not the concentrator's, by running
ipsec auto --show --verbose --up lan-lan
Because the concentrator doesn't know the peer's dynamic IP address, the tunnels always must be started on the peer's side.
With a LAN-to-LAN tunnel, FreeS/WAN encrypts only the packets going from LAN A to LAN B. If you try to send packets from the sgw A to the LAN B, FreeS/WAN drops the packets on the other side and they don't get through. This is done on purpose for security reasons. In FreeS/WAN documentation, you can read that one of the ways to establish a secure connection to all the different points of the VPN is to establish four different tunnels (LAN-to-LAN, host-to-LAN, LAN-to-host and host-to-host). In order to avoid this complication, you can play with iproute2, also known as advanced routing.
With advanced routing you can do many amazing things, including setting routes depending on the source IP address, which is exactly what we need to do in this case. The following commands do the trick:
# ip rule add table 2 # ip rule add iif lo table 2 priority 500 # ip route add <LEFT-LAN> table 2 dev ipsec0 src LOCAL-SGW-IP
The first IP command tells the kernel to add a new rule to the routing table. The second sets the trigger for this rule and tells the kernel that all the traffic originated locally (iif lo) should go trough this rule. The last command adds a new route for all the packets with the source address LOCAL-SGW-IP (for example, 192.168.0.1) that go to the LAN on the other side, or LEFT-LAN, (for example, 192.168.1.0/24) and that must go through the ipsec0 interface. You can run this set of commands on both sgw (changing the LEFT-LAN and LOCAL-SGW-IP values, of course). You should be able to ping everywhere from everywhere.