Practical: Configuring IPsec, and Reading the Policy Back
Chapter Ninety-Seven
Syllabus topic Practical 5, "IP Security (IPsec) Configuration"
Pages 645 to 650 of 678
In one line
Configuring IPsec is writing a policy that says which traffic must be protected, which may pass in clear and which must be dropped, and then proving from the packets that the policy is in force.
Aim
To configure IPsec between two hosts so that traffic between them is protected, to read the resulting policy and security associations back from the system, and to demonstrate from the packets themselves that protection is being applied.
What is needed
- Two machines, or one machine and a virtual one, that can reach each other: call them the client
10.21.4.9and the server10.21.50.8. - Administrator rights on both, and a packet capture tool such as tcpdump or Wireshark.
- On Linux, the
ip xfrmcommands; on Windows, the IP Security Policy snap-in; the shapes are the same.
Theory in four lines
- The Security Policy Database (SPD) says, for each kind of traffic, whether to protect it, bypass it or discard it.
- The Security Association Database (SAD) holds the actual associations: for each, an SPI, the algorithms and the keys.
- A security association is one way, so a two-way conversation needs two, with different SPIs.
- Transport mode protects the payload of a packet between two hosts; tunnel mode wraps the whole packet in a new one, which is what gateways do.
Procedure
- On both machines, define the policy: protect traffic between the two addresses, allow the rest of the campus in clear, and discard anything else leaving.
- Create the security associations, one in each direction, with different SPIs and their own keys.
- Read the policy and the associations back and check them against what was intended, line by line.
- Generate some traffic between the two machines.
- Capture the packets and look at them: the protocol number, the SPI, the sequence number, and whether the data is readable.
- Check the association's counters: they rise only if the association is being used.
- Record all of it in the journal, including the capture.
What the commands look like
On Linux the policy and the associations are written like this, and read back with the matching show commands. The keys below are examples, and in a real system come from IKE rather than from the keyboard.
# on the client 10.21.4.9, for traffic to the server 10.21.50.8
ip xfrm policy add dir out src 10.21.4.9/32 dst 10.21.50.8/32 \
tmpl proto esp mode transport
ip xfrm policy add dir in src 10.21.50.8/32 dst 10.21.4.9/32 \
tmpl proto esp mode transport
# one association for each direction, with different SPIs
ip xfrm state add src 10.21.4.9 dst 10.21.50.8 proto esp spi 0x2A01 \
mode transport enc 'cbc(aes)' 0x... auth 'hmac(sha256)' 0x...
ip xfrm state add src 10.21.50.8 dst 10.21.4.9 proto esp spi 0x5B01 \
mode transport enc 'cbc(aes)' 0x... auth 'hmac(sha256)' 0x...
# read back what the kernel actually holds
ip xfrm policy show
ip xfrm state showPractical: Configuring IPsec, and Reading the Policy Back
Read the output of the last two commands, not the absence of an error from the first four. A policy can be accepted and still not match the traffic you meant, and an association with the wrong address is simply never used.
The program: the policy applied, and the packets compared
The listing takes the same policy, in the form the kernel keeps it, decides what happens to four packets, and then builds the packet that leaves the machine with and without the policy, so that the two can be compared.
# Practical 5, exercise 6: read the policy back, then prove from the packets that it is applied.
# The policy below is the one in the configuration, typed out again in the form the kernel keeps
# it. Nothing is sent: the packets are byte strings built here.
import hashlib, ipaddress
# ---- the policy, as the configuration wrote it -------------------------------------------------
# direction, source network, destination network, upper protocol, what to do
POLICY = [
('out', '10.21.4.9/32', '10.21.50.8/32', 'any', 'protect: esp transport, SPI 0x2A01'),
('in', '10.21.50.8/32', '10.21.4.9/32', 'any', 'protect: esp transport, SPI 0x5B01'),
('out', '10.21.4.9/32', '10.21.0.0/16', 'any', 'bypass: the rest of the campus in clear'),
('out', '10.21.4.9/32', '0.0.0.0/0', 'any', 'discard: nothing else leaves this host'),
]
SPI_OUT, SPI_IN = 0x2A01, 0x5B01
def lookup(direction, src, dst):
"""Ordered, first match wins: the same rule a firewall follows, and the kernel too."""
for d, s_net, d_net, proto, action in POLICY:
if (d == direction and ipaddress.ip_address(src) in ipaddress.ip_network(s_net)
and ipaddress.ip_address(dst) in ipaddress.ip_network(d_net)):
return action
return 'discard: no policy matched'
print('1. THE POLICY, READ BACK')
for d, s_net, d_net, proto, action in POLICY:
print(' %-4s %-16s to %-16s %s' % (d, s_net, d_net, action))
print()
print('2. WHAT HAPPENS TO EACH PACKET')
TRAFFIC = [('out', '10.21.4.9', '10.21.50.8', 'to the server the policy protects'),
('out', '10.21.4.9', '10.21.60.2', 'to another campus machine'),
('out', '10.21.4.9', '203.0.113.5', 'to the Internet'),
('in', '10.21.50.8', '10.21.4.9', 'the server answering')]
for direction, src, dst, what in TRAFFIC:
print(' %-4s %-12s to %-12s %-34s %s'
% (direction, src, dst, what, lookup(direction, src, dst).split(':')[0].upper()))
# ---- 3. build the packet the policy asks for, and look at what is visible ----------------------
PAYLOAD = b'GET /marks/rollno/4417 HTTP/1.1\r\nHost: results.example.edu\r\n\r\n'
def keystream(key, spi, seq, length):
"""A stand-in for the cipher the two ends negotiate. The practical's point is the
headers and what remains readable, not which cipher was chosen."""
out = b''
while len(out) < length:
out += hashlib.sha256(key + spi.to_bytes(4, 'big') + seq.to_bytes(4, 'big')
+ len(out).to_bytes(4, 'big')).digest()
return out[:length]
def esp_transport(payload, spi, seq, key):
"""RFC 4303: outer IP header, then SPI, sequence number, the encrypted part, then the ICV."""
pad = (-(len(payload) + 2)) % 4
body = payload + bytes(range(1, pad + 1)) + bytes([pad]) + bytes([6]) # 6 = TCP
encrypted = bytes(a ^ b for a, b in zip(body, keystream(key, spi, seq, len(body))))
header = spi.to_bytes(4, 'big') + seq.to_bytes(4, 'big')
icv = hashlib.sha256(key + header + encrypted).digest()[:12]
ip = bytes([0x45, 0x00]) + (20 + len(header + encrypted + icv)).to_bytes(2, 'big')
ip += bytes([0, 0, 0, 0, 64, 50]) # protocol 50 is ESP
ip += bytes([0, 0]) + ipaddress.ip_address('10.21.4.9').packed
ip += ipaddress.ip_address('10.21.50.8').packed
return ip + header + encrypted + icv
KEY = b'the key IKE negotiated, kept by the two hosts only'
protected = esp_transport(PAYLOAD, SPI_OUT, 1, KEY)
plain = (bytes([0x45, 0x00]) + (20 + len(PAYLOAD)).to_bytes(2, 'big')
+ bytes([0, 0, 0, 0, 64, 6, 0, 0]) + ipaddress.ip_address('10.21.4.9').packed
+ ipaddress.ip_address('10.21.50.8').packed + PAYLOAD)
print()
print('3. THE TWO PACKETS ON THE WIRE')
for name, packet in (('without the policy', plain), ('with the policy', protected)):
print(' %-20s %3d bytes, protocol %-3d %s'
% (name, len(packet), packet[9],
'ESP' if packet[9] == 50 else 'TCP, carried in the clear'))
print(' the roll number is readable in it: %s' % (b'4417' in packet))
print(' the first 16 bytes after the IP header: %s' % packet[20:36].hex())
print()
print('4. THE TEST THAT PROVES NOTHING, AND THE ONE THAT DOES')
print(' "the connection still works" is true of both packets above')
print(' "protocol 50 and the data is not readable" is true of only one')
print(' counters rise only when a security association is actually used:')
for seq in (1, 2, 3):
packet = esp_transport(PAYLOAD, SPI_OUT, seq, KEY)
print(' SPI 0x%04X sequence %d, %d bytes'
% (int.from_bytes(packet[20:24], 'big'), int.from_bytes(packet[24:28], 'big'),
len(packet)))Practical: Configuring IPsec, and Reading the Policy Back
1. THE POLICY, READ BACK
out 10.21.4.9/32 to 10.21.50.8/32 protect: esp transport, SPI 0x2A01
in 10.21.50.8/32 to 10.21.4.9/32 protect: esp transport, SPI 0x5B01
out 10.21.4.9/32 to 10.21.0.0/16 bypass: the rest of the campus in clear
out 10.21.4.9/32 to 0.0.0.0/0 discard: nothing else leaves this host
2. WHAT HAPPENS TO EACH PACKET
out 10.21.4.9 to 10.21.50.8 to the server the policy protects PROTECT
out 10.21.4.9 to 10.21.60.2 to another campus machine BYPASS
out 10.21.4.9 to 203.0.113.5 to the Internet DISCARD
in 10.21.50.8 to 10.21.4.9 the server answering PROTECT
3. THE TWO PACKETS ON THE WIRE
without the policy 82 bytes, protocol 6 TCP, carried in the clear
the roll number is readable in it: True
the first 16 bytes after the IP header: 474554202f6d61726b732f726f6c6c6e
with the policy 104 bytes, protocol 50 ESP
the roll number is readable in it: False
the first 16 bytes after the IP header: 00002a01000000018ef2ca283e1550e9
4. THE TEST THAT PROVES NOTHING, AND THE ONE THAT DOES
"the connection still works" is true of both packets above
"protocol 50 and the data is not readable" is true of only one
counters rise only when a security association is actually used:
SPI 0x2A01 sequence 1, 104 bytes
SPI 0x2A01 sequence 2, 104 bytes
SPI 0x2A01 sequence 3, 104 bytesPractical: Configuring IPsec, and Reading the Policy Back
What the output proves
The policy is ordered, and the order decides. The rule protecting traffic to the server is above the rule that lets the rest of the campus pass in clear, so the protected case is matched first. Written the other way round, everything would go in clear and nothing would report an error.
Three outcomes, not two. Traffic to the server is protected, traffic to the rest of the campus bypasses, traffic to the Internet is discarded. Discard is a policy decision, not a failure, and it is the one students most often forget to test.
The protected packet is a different packet. Its IP header says protocol 50, ESP; it carries an SPI and a sequence number in the clear, because the receiver needs them to find the right association; and the roll number that was plainly visible in the unprotected packet cannot be found in it. It is also longer, by the ESP header, the padding and the integrity check value.
The sequence number rises. Successive packets on the same association carry 1, 2, 3, which is what the anti-replay window checks and what makes the counters on the association meaningful.
Questions the examiner asks
Why two security associations? Because an association is one-way. Each direction has its own SPI, its own keys and its own sequence numbers.
What is the SPI for? The receiver uses it, with the destination address and the protocol, to find which association to use. It is not secret, and it is not the same in both directions.
Transport or tunnel mode here? Transport, because the two ends are the hosts themselves. Tunnel mode is for gateways, where the whole original packet must be hidden inside a new one.
How do you know it is working? From the packets: protocol 50, an SPI you recognise, a rising sequence number, and data that is no longer readable. Not from the fact that the application still works.
Common mistakes in the laboratory
- Configuring one side only. The other end discards what it cannot verify, and the symptom is a silence, not an error.
- Using the same SPI in both directions, or the same key, which the IPsec chapter recorded as a fault worth catching.
- Putting the bypass rule above the protect rule, so that nothing is ever protected and nothing complains.
- Forgetting the inbound policy, so protected replies are dropped.
- Testing with ping only. ICMP may match a different rule than the traffic you care about.
- Believing a capture taken on the sending machine: on some systems the capture point is before IPsec is applied, so capture on the wire or on the receiver as well.
Practical: Configuring IPsec, and Reading the Policy Back
Quick revision
- SPD: protect, bypass, discard, in order, first match wins. SAD: SPI, algorithms, keys.
- A security association is one way: two are needed, with different SPIs.
- Transport mode between hosts; tunnel mode between gateways.
- Read back with
ip xfrm policy showandip xfrm state show. - Proof is in the packet: protocol 50, SPI, sequence number, payload not readable, length grown.
- "It still works" is not proof; a policy that never matches also still works.
Test yourself
1. What three actions can an IPsec policy take, and how are they chosen? A policy entry can protect the traffic, applying AH or ESP with a security association; bypass it, letting it pass without IPsec; or discard it. The entries are searched in order and the first that matches the packet's selectors, such as source and destination addresses, protocol and ports, decides, so the order of entries is part of the policy.
2. Why are two security associations needed between two hosts, and what distinguishes them? Because a security association is unidirectional: it protects traffic in one direction only. The pair is distinguished by the SPI, by the destination address, and normally by separate keys and independent sequence numbers, so that the two directions cannot be confused and replaying a packet from one direction into the other is impossible.
3. How would you prove that your IPsec configuration is actually in use? By capturing the traffic and examining the packets: the IP header's protocol field should be 50 for ESP or 51 for AH, the packet should carry the SPI configured for that direction and a sequence number that rises with each packet, and the data that was readable without IPsec should no longer be found in it. The counters on the security association should also rise. The fact that the application still works proves nothing, because a policy that is never matched leaves the traffic exactly as it was.
4. Write the policy for a host that must protect its traffic to one server, reach the rest of its campus in clear, and send nothing else. Three outbound entries in this order: first, for source the host and destination the server, protect with ESP in transport mode; second, for source the host and destination the campus network, bypass; third, for source the host and any destination, discard. A matching inbound entry protects the traffic coming back from the server. If the second entry were placed first, traffic to the server would match it and go in clear.
Practical: Configuring IPsec, and Reading the Policy Back
5. What goes wrong if you configure only one end? The configured end protects its outgoing traffic, and the other end receives packets with protocol 50 for which it has no security association, so it discards them; traffic in the other direction arrives unprotected at the configured end, whose inbound policy requires protection, so it is discarded too. The result is that the connection fails silently in both directions, with no error message that names the cause, which is why the policy and the associations must be read back on both machines.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.