Solaris IPsec: Shared Key Transport Mode

Posted on July 19, 2008

In this entry we’ll build on our our IPsec Basics discussed last time and actually create an IPsec connection.

IPsec can be used for direct system-to-system access known as “transport mode” or to create a virtual pipeline into which everything is encrypted, known as “tunnel mode”. We’re going to look at transport mode, which is an excellent solution for encrypting otherwise unencrypted protocols, such as SNMPv1/2 or telnet.

When encrypting and decrypting data we need keys. This can be done using PKI certificates or IKE generated one-time keys, but in this examples for simplicity sake we’ll create our own “static” keys which will be used on both ends of the connection, thus said to be “pre-shared”.

Creating Keys

Using the ipsecalgs command we can see the available algorithms, including DES, 3DES, AES, Blowfish, SHA and MD5. Different alogithms require different key lengths, for instance 3DES requires a 192 bit key, whereas Blowfish can use a key anywhere from 32bits up to 448 bits.

For interoperability reasons (such as OSX or Linux), you may with to create keys that are both ASCII and hex. This is done by choosing a string and converting it to hex. To know how long a string should be, divide the number of bits required by 8, this is the number of ASCII chars you need. The hex value of that ASCII string will be double the number of ASCII chars. Using the od utility we can convert ASCII-to-hex. Here I’ll create 2 keys, one for AH which is a SHA1 160bit key (20 ASCII chars) and another for ESP which is a Blowfish 256bit key (32 ASCII chars):

benr@ultra ~$ echo "my short ah password" | od -t x1
0000000 6d 79 20 73 68 6f 72 74 20 61 68 20 70 61 73 73
0000020 77 6f 72 64 0a
0000025
benr@ultra ~$ echo "this is my long blowfish esp pas" | od -t x1
0000000 74 68 69 73 20 69 73 20 6d 79 20 6c 6f 6e 67 20
0000020 62 6c 6f 77 66 69 73 68 20 65 73 70 20 70 61 73
0000040 0a
0000041

To ensure proper length, I like using a little text-rule like you see below in vi:

         1         2    2    3 3       4         5         6   6     7  
1234567890 234567890 234567890 234567890 234567890 234567890 234567890
--------------------------------------------------------------------------------------------------------------------
my short ah password
6d792073686f72742061682070617373776f7264

this is my long blowfish esp pas
74686973206973206d79206c6f6e6720626c6f77666973682065737020706173

If you don’t require interoperability by knowing the ASCII equivilent, just grab a random set of hex chars (head /dev/random | od -t x1).

Now that we have a key, lets use it.

Configuring IPsec Policies

IPsec policies are rules that the IP stack uses to determine what action should be taken. Actions include:

  • bypass: Do nothing, skip the remaining rules if datagram matches.
  • drop: Drop if datagram matches.
  • permit: Allow if datagram matches, otherwise discard. (Only for inbound datagrams.)
  • ipsec: Use IPsec if the datagram matches.

As you can see, this sounds similar to a firewall rule, and to some extent can be used that way, but you ultimately find IPFilter much better suited to that task. When you plan your IPsec environment consider which rules are appropriate in which place.

IPsec policies are defined in the /etc/inet/ipsecinit.conf file, which can be loaded/reloaded using the ipsecconf command. Lets look at a sample configuration:

benr@ultra inet$ cat /etc/inet/ipsecinit.conf 
##
##  IPsec Policy File:
##

# Ignore SSH
{ lport 22 dir both } bypass { }

# IPsec Encrypt telnet Connections to 8.11.80.5
{ raddr 8.11.80.5 rport 23 } ipsec { encr_algs blowfish encr_auth_algs sha1 sa shared }

Our first policy explicitly bypasses connections in and out (“dir both”, as in direction) for the local port 22 (SSH). Do I need this here? No, but I include it as an example. You can see the format, the first curly block defines the filter, the second curly block defines parameters, the keyword in between is the action.

The second policy is what we’re interested in, its action is ipsec, so if the filter in the first curly block matches we’ll use IPsec. “raddr” defines a remote address and “rport” defines a remote port, therefore this policy applies only to outbound connections where we’re telnet’ing (port 23) to 8.11.80.5. The second curly block defines parameters for the action, in this case we define the encryption algorithm (Blowfish), encryption authentication algorithm (SHA1), and state that the Security Association is “shared”. This is a full ESP connection, meaning we’re encrypting and encapsulating the full packet, if we were doing AH (authentication only) we would only define “auth_algs”.

Now, on the remote side of the connection (8.11.80.5) we create a similar policy, but rather than “raddr” and “rport” we use “laddr” (local address) and “lport” (local port). We could even go so far as to specify the remote address such that only the specified host would use IPsec to the node. Here’s that configuration:

##  IPsec Policy File:
##

# Ignore SSH
{ lport 22 dir both } bypass { }

# IPsec Encrypt telnet Connections to 8.11.80.5
{ laddr 8.11.80.5 lport 23 } ipsec { encr_algs blowfish encr_auth_algs sha1 sa shared }

To load the new policy file you can refresh the ipsec/policy SMF service like so: svcadm refresh ipsec/policy. I recommend avoiding the ipsecconf command except to (without arguments) display the active policy configuration.

So we’ve defined policies that will encrypt traffic from one node to another, but we’re not done yet! We need to define a Security Association that will association keys with our policy.

Creating Security Associations

Security Associations (SAs) can be manually created by either using the ipseckeys command or directly editing the /etc/inet/secret/ipseckeys file, I recommend the latter, I personally find the ipseckeys shell very intimidating.

Lets look at a sample file and then discuss it:

add esp spi 1000 src 8.15.11.17 dst 8.11.80.5 auth_alg sha1 authkey 6d792073686f72742061682070617373776f7264 encr_alg blowfish encrkey 6d792073686f72742061682070617373
add esp spi 1001 src 8.11.80.5 dst 8.15.11.17 auth_alg sha1 authkey 6d792073686f72742061682070617373776f7264 encr_alg blowfish encrkey 6d792073686f72742061682070617373

It looks more intimidating that it is. Each line is “add”ing a new static Security Association, both are for ESP. The SPI is the “Security Parameters Index”, is a simple numeric value that represents the SA, nothing more, pick any value you like. The src and dst define the addresses to which this SA applies, note that you have two SA’s here, one for each direction. Finally, we define the encryption and authentication algorithms and full keys.

I hope that looking at this makes it more clear how policies and SA’s fit together. If the IP stack matches a datagram against a policy who’s action is “ipsec”, it takes the packet and looks for an SA who’s address pair matches, and then uses those keys for the action encryption.

Note that if someone obtains your keys your hosed. If you pre-shared keys in this way, change the keys from time-to-time or consider using IKE which can negotiate keys (and thus SAs) on your behalf.

To apply your new SA’s, flush and then load using the ipseckeys command:

$ ipseckey flush
$ ipseckey -f /etc/inet/secret/ipseckeys

Is it working? How to Test

All this is for nothing if you don’t verify that the packets are actually encrypted. Using snoop, you should see packets like this:

$ snoop -d e1000g0
Using device e1000g0 (promiscuous mode)
ETHER:  ----- Ether Header -----
ETHER:  
ETHER:  Packet 1 arrived at 9:52:4.58883
ETHER:  Packet size = 90 bytes
ETHER:  Destination = xxxxxxxxxxx, 
ETHER:  Source      = xxxxxxxxxx, 
ETHER:  Ethertype = 0800 (IP)
ETHER:  
IP:   ----- IP Header -----
IP:   
IP:   Version = 4
IP:   Header length = 20 bytes
IP:   Type of service = 0x00
IP:         xxx. .... = 0 (precedence)
IP:         ...0 .... = normal delay
IP:         .... 0... = normal throughput
IP:         .... .0.. = normal reliability
IP:         .... ..0. = not ECN capable transport
IP:         .... ...0 = no ECN congestion experienced
IP:   Total length = 72 bytes
IP:   Identification = 36989
IP:   Flags = 0x4
IP:         .1.. .... = do not fragment
IP:         ..0. .... = last fragment
IP:   Fragment offset = 0 bytes
IP:   Time to live = 61 seconds/hops
IP:   Protocol = 50 (ESP)
IP:   Header checksum = ab9c
IP:   Source address = XXXXXXXXX
IP:   Destination address = XXXXXXXXXXXX
IP:   No options
IP:   
ESP:  ----- Encapsulating Security Payload -----
ESP:  
ESP:  SPI = 0x3e8
ESP:  Replay = 55
ESP:     ....ENCRYPTED DATA....

And there you go. You can no encrypt communication transparently in the IP stack. Its a little effort to get going, but once its running your done… just remember to rotate those keys every so often!

Why do I care about this again?

In this modern era where SSH is the standard for communication its easy to get jaded. Either you can communicate via SSH or easily create a tunnel to get the job done. But lets face it, SSH is massively overused, and in many cases SSH tunneling is just downright ghetto. With IPsec we can as easily encrypt 100 ports as 1, whereas with SSH thats very ugly. Furthermore, there are many instances in which you want a secure communications channel thats as transparent as possible, such as a network database connection that doesn’t offer native encryption or perhaps an SCM or even SNMPv1/2.

While most applications today provide some type of encryption capability it surprising how few people leverage them unless they are the default. In situations where its difficult or impractical to use encryption in the application, IPsec can be a really sweet solution.