From 3833ffd6c648c5066448e598976810c85c66bd58 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Thu, 21 Mar 2019 14:34:09 +0000 Subject: IPSEC tests fnd fix or Extended Sequence Numbers Change-Id: Iad6c4b867961ec8036110a4e15a829ddb93193ed Signed-off-by: Neale Ranns --- test/patches/scapy-2.4/ipsec.patch | 156 +++++++++++++++++++++++++++++++++++++ test/template_ipsec.py | 103 ++++++++++++++++++------ test/test_ipsec_ah.py | 34 ++++++-- 3 files changed, 264 insertions(+), 29 deletions(-) create mode 100644 test/patches/scapy-2.4/ipsec.patch (limited to 'test') diff --git a/test/patches/scapy-2.4/ipsec.patch b/test/patches/scapy-2.4/ipsec.patch new file mode 100644 index 00000000000..5a644327596 --- /dev/null +++ b/test/patches/scapy-2.4/ipsec.patch @@ -0,0 +1,156 @@ +diff --git a/scapy/layers/ipsec.py b/scapy/layers/ipsec.py +index 69e7ae3b..99373466 100644 +--- a/scapy/layers/ipsec.py ++++ b/scapy/layers/ipsec.py +@@ -518,12 +517,16 @@ class AuthAlgo(object): + else: + return self.mac(key, self.digestmod(), default_backend()) + +- def sign(self, pkt, key): ++ def sign(self, pkt, key, trailer=None): + """ + Sign an IPsec (ESP or AH) packet with this algo. + + @param pkt: a packet that contains a valid encrypted ESP or AH layer + @param key: the authentication key, a byte string ++ @param trailer: additional data appended to the packet for ICV ++ calculation, but not trnasmitted with the packet. ++ For example, the high order bits of the exteneded ++ sequence number. + + @return: the signed packet + """ +@@ -539,11 +542,13 @@ class AuthAlgo(object): + elif pkt.haslayer(AH): + clone = zero_mutable_fields(pkt.copy(), sending=True) + mac.update(raw(clone)) ++ if trailer: ++ mac.update(trailer) + pkt[AH].icv = mac.finalize()[:self.icv_size] + + return pkt + +- def verify(self, pkt, key): ++ def verify(self, pkt, key, trailer): + """ + Check that the integrity check value (icv) of a packet is valid. + +@@ -574,6 +579,8 @@ class AuthAlgo(object): + clone = zero_mutable_fields(pkt.copy(), sending=False) + + mac.update(raw(clone)) ++ if trailer: ++ mac.update(trailer) # bytearray(4)) #raw(trailer)) + computed_icv = mac.finalize()[:self.icv_size] + + # XXX: Cannot use mac.verify because the ICV can be truncated +@@ -757,7 +764,8 @@ class SecurityAssociation(object): + SUPPORTED_PROTOS = (IP, IPv6) + + def __init__(self, proto, spi, seq_num=1, crypt_algo=None, crypt_key=None, +- auth_algo=None, auth_key=None, tunnel_header=None, nat_t_header=None): ++ auth_algo=None, auth_key=None, tunnel_header=None, nat_t_header=None, ++ use_esn=False): + """ + @param proto: the IPsec proto to use (ESP or AH) + @param spi: the Security Parameters Index of this SA +@@ -771,6 +779,7 @@ class SecurityAssociation(object): + to encapsulate the encrypted packets. + @param nat_t_header: an instance of a UDP header that will be used + for NAT-Traversal. ++ @param use_esn: Use Extended Sequence Numbers + """ + + if proto not in (ESP, AH, ESP.name, AH.name): +@@ -782,6 +791,7 @@ class SecurityAssociation(object): + + self.spi = spi + self.seq_num = seq_num ++ self.use_esn = use_esn + + if crypt_algo: + if crypt_algo not in CRYPT_ALGOS: +@@ -827,6 +837,17 @@ class SecurityAssociation(object): + raise TypeError('packet spi=0x%x does not match the SA spi=0x%x' % + (pkt.spi, self.spi)) + ++ def build_seq_num(self, num): ++ # only lower order bits are transmitted ++ # higher order bits are used in the ICV ++ lower = num & 0xffffffff ++ upper = num >> 32 ++ ++ if self.use_esn: ++ return lower, struct.pack(">I", upper) ++ else: ++ return lower, None ++ + def _encrypt_esp(self, pkt, seq_num=None, iv=None): + + if iv is None: +@@ -835,7 +856,8 @@ class SecurityAssociation(object): + if len(iv) != self.crypt_algo.iv_size: + raise TypeError('iv length must be %s' % self.crypt_algo.iv_size) + +- esp = _ESPPlain(spi=self.spi, seq=seq_num or self.seq_num, iv=iv) ++ low_seq_num, high_seq_num = self.build_seq_num(seq_num or self.seq_num) ++ esp = _ESPPlain(spi=self.spi, seq=low_seq_num, iv=iv) + + if self.tunnel_header: + tunnel = self.tunnel_header.copy() +@@ -857,7 +879,7 @@ class SecurityAssociation(object): + esp = self.crypt_algo.pad(esp) + esp = self.crypt_algo.encrypt(self, esp, self.crypt_key) + +- self.auth_algo.sign(esp, self.auth_key) ++ self.auth_algo.sign(esp, self.auth_key, high_seq_num) + + if self.nat_t_header: + nat_t_header = self.nat_t_header.copy() +@@ -884,7 +906,8 @@ class SecurityAssociation(object): + + def _encrypt_ah(self, pkt, seq_num=None): + +- ah = AH(spi=self.spi, seq=seq_num or self.seq_num, ++ low_seq_num, high_seq_num = self.build_seq_num(seq_num or self.seq_num) ++ ah = AH(spi=self.spi, seq=low_seq_num, + icv = b"\x00" * self.auth_algo.icv_size) + + if self.tunnel_header: +@@ -924,7 +947,8 @@ class SecurityAssociation(object): + else: + ip_header.plen = len(ip_header.payload) + len(ah) + len(payload) + +- signed_pkt = self.auth_algo.sign(ip_header / ah / payload, self.auth_key) ++ signed_pkt = self.auth_algo.sign(ip_header / ah / payload, ++ self.auth_key, high_seq_num) + + # sequence number must always change, unless specified by the user + if seq_num is None: +@@ -955,11 +979,12 @@ class SecurityAssociation(object): + + def _decrypt_esp(self, pkt, verify=True): + ++ low_seq_num, high_seq_num = self.build_seq_num(self.seq_num) + encrypted = pkt[ESP] + + if verify: + self.check_spi(pkt) +- self.auth_algo.verify(encrypted, self.auth_key) ++ self.auth_algo.verify(encrypted, self.auth_key, high_seq_num) + + esp = self.crypt_algo.decrypt(self, encrypted, self.crypt_key, + self.crypt_algo.icv_size or +@@ -998,9 +1023,11 @@ class SecurityAssociation(object): + + def _decrypt_ah(self, pkt, verify=True): + ++ low_seq_num, high_seq_num = self.build_seq_num(self.seq_num) ++ + if verify: + self.check_spi(pkt) +- self.auth_algo.verify(pkt, self.auth_key) ++ self.auth_algo.verify(pkt, self.auth_key, high_seq_num) + + ah = pkt[AH] + payload = ah.payload diff --git a/test/template_ipsec.py b/test/template_ipsec.py index 78d75844d5d..273865d407c 100644 --- a/test/template_ipsec.py +++ b/test/template_ipsec.py @@ -81,6 +81,8 @@ class IPsecIPv6Params(object): def config_tun_params(p, encryption_type, tun_if): ip_class_by_addr_type = {socket.AF_INET: IP, socket.AF_INET6: IPv6} + use_esn = bool(p.flags & (VppEnum.vl_api_ipsec_sad_flags_t. + IPSEC_API_SAD_FLAG_USE_EXTENDED_SEQ_NUM)) p.scapy_tun_sa = SecurityAssociation( encryption_type, spi=p.vpp_tun_spi, crypt_algo=p.crypt_algo, crypt_key=p.crypt_key, @@ -88,7 +90,8 @@ def config_tun_params(p, encryption_type, tun_if): tunnel_header=ip_class_by_addr_type[p.addr_type]( src=tun_if.remote_addr[p.addr_type], dst=tun_if.local_addr[p.addr_type]), - nat_t_header=p.nat_header) + nat_t_header=p.nat_header, + use_esn=use_esn) p.vpp_tun_sa = SecurityAssociation( encryption_type, spi=p.scapy_tun_spi, crypt_algo=p.crypt_algo, crypt_key=p.crypt_key, @@ -96,10 +99,13 @@ def config_tun_params(p, encryption_type, tun_if): tunnel_header=ip_class_by_addr_type[p.addr_type]( dst=tun_if.remote_addr[p.addr_type], src=tun_if.local_addr[p.addr_type]), - nat_t_header=p.nat_header) + nat_t_header=p.nat_header, + use_esn=use_esn) def config_tra_params(p, encryption_type): + use_esn = p.flags & (VppEnum.vl_api_ipsec_sad_flags_t. + IPSEC_API_SAD_FLAG_USE_EXTENDED_SEQ_NUM) p.scapy_tra_sa = SecurityAssociation( encryption_type, spi=p.vpp_tra_spi, @@ -107,7 +113,8 @@ def config_tra_params(p, encryption_type): crypt_key=p.crypt_key, auth_algo=p.auth_algo, auth_key=p.auth_key, - nat_t_header=p.nat_header) + nat_t_header=p.nat_header, + use_esn=use_esn) p.vpp_tra_sa = SecurityAssociation( encryption_type, spi=p.scapy_tra_spi, @@ -115,7 +122,8 @@ def config_tra_params(p, encryption_type): crypt_key=p.crypt_key, auth_algo=p.auth_algo, auth_key=p.auth_key, - nat_t_header=p.nat_header) + nat_t_header=p.nat_header, + use_esn=use_esn) class TemplateIpsec(VppTestCase): @@ -141,14 +149,16 @@ class TemplateIpsec(VppTestCase): """ empty method to be overloaded when necessary """ pass - def setUp(self): - super(TemplateIpsec, self).setUp() - + def setup_params(self): self.ipv4_params = IPsecIPv4Params() self.ipv6_params = IPsecIPv6Params() self.params = {self.ipv4_params.addr_type: self.ipv4_params, self.ipv6_params.addr_type: self.ipv6_params} + def setUp(self): + super(TemplateIpsec, self).setUp() + + self.setup_params() self.payload = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\ "XXXXXXXXXXXXXXXXXXXXX" @@ -243,6 +253,7 @@ class IpsecTra4Tests(object): def test_tra_anti_replay(self, count=1): """ ipsec v4 transport anti-reply test """ p = self.params[socket.AF_INET] + use_esn = p.vpp_tra_sa.use_esn # fire in a packet with seq number 1 pkt = (Ether(src=self.tra_if.remote_mac, @@ -262,6 +273,11 @@ class IpsecTra4Tests(object): seq_num=235)) recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if) + # replayed packets are dropped + self.send_and_assert_no_replies(self.tra_if, pkt * 3) + self.assert_packet_counter_equal( + '/err/%s/SA replayed packet' % self.tra4_decrypt_node_name, 3) + # the window size is 64 packets # in window are still accepted pkt = (Ether(src=self.tra_if.remote_mac, @@ -272,18 +288,6 @@ class IpsecTra4Tests(object): seq_num=172)) recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if) - # out of window are dropped - pkt = (Ether(src=self.tra_if.remote_mac, - dst=self.tra_if.local_mac) / - p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, - dst=self.tra_if.local_ip4) / - ICMP(), - seq_num=17)) - self.send_and_assert_no_replies(self.tra_if, pkt * 17) - - self.assert_packet_counter_equal( - '/err/%s/SA replayed packet' % self.tra4_decrypt_node_name, 17) - # a packet that does not decrypt does not move the window forward bogus_sa = SecurityAssociation(self.encryption_type, p.vpp_tra_spi) @@ -307,20 +311,71 @@ class IpsecTra4Tests(object): seq_num=234)) self.send_and_expect(self.tra_if, [pkt], self.tra_if) + # out of window are dropped + pkt = (Ether(src=self.tra_if.remote_mac, + dst=self.tra_if.local_mac) / + p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, + dst=self.tra_if.local_ip4) / + ICMP(), + seq_num=17)) + self.send_and_assert_no_replies(self.tra_if, pkt * 17) + + if use_esn: + # an out of window error with ESN looks like a high sequence + # wrap. but since it isn't then the verify will fail. + self.assert_packet_counter_equal( + '/err/%s/Integrity check failed' % + self.tra4_decrypt_node_name, 34) + + else: + self.assert_packet_counter_equal( + '/err/%s/SA replayed packet' % + self.tra4_decrypt_node_name, 20) + + # valid packet moves the window over to 236 + pkt = (Ether(src=self.tra_if.remote_mac, + dst=self.tra_if.local_mac) / + p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, + dst=self.tra_if.local_ip4) / + ICMP(), + seq_num=236)) + rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if) + decrypted = p.vpp_tra_sa.decrypt(rx[0][IP]) + # move VPP's SA to just before the seq-number wrap self.vapi.cli("test ipsec sa %d seq 0xffffffff" % p.scapy_tra_sa_id) # then fire in a packet that VPP should drop becuase it causes the - # seq number to wrap + # seq number to wrap, unless we're using exteneded. pkt = (Ether(src=self.tra_if.remote_mac, dst=self.tra_if.local_mac) / p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), - seq_num=236)) - self.send_and_assert_no_replies(self.tra_if, [pkt]) - self.assert_packet_counter_equal( - '/err/%s/sequence number cycled' % self.tra4_encrypt_node_name, 1) + seq_num=237)) + + if use_esn: + rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if) + # in order to decrpyt the high order number needs to wrap + p.vpp_tra_sa.seq_num = 0x100000000 + decrypted = p.vpp_tra_sa.decrypt(rx[0][IP]) + + # send packets with high bits set + p.scapy_tra_sa.seq_num = 0x100000005 + pkt = (Ether(src=self.tra_if.remote_mac, + dst=self.tra_if.local_mac) / + p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4, + dst=self.tra_if.local_ip4) / + ICMP(), + seq_num=0x100000005)) + rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if) + # in order to decrpyt the high order number needs to wrap + decrypted = p.vpp_tra_sa.decrypt(rx[0][IP]) + else: + self.send_and_assert_no_replies(self.tra_if, [pkt]) + self.assert_packet_counter_equal( + '/err/%s/sequence number cycled' % + self.tra4_encrypt_node_name, 1) # move the security-associations seq number on to the last we used self.vapi.cli("test ipsec sa %d seq 0x15f" % p.scapy_tra_sa_id) diff --git a/test/test_ipsec_ah.py b/test/test_ipsec_ah.py index 21080cad3d6..af65850253c 100644 --- a/test/test_ipsec_ah.py +++ b/test/test_ipsec_ah.py @@ -5,7 +5,7 @@ from scapy.layers.ipsec import AH from framework import VppTestRunner from template_ipsec import TemplateIpsec, IpsecTra46Tests, IpsecTun46Tests, \ - config_tun_params, config_tra_params + config_tun_params, config_tra_params, IPsecIPv4Params, IPsecIPv6Params from template_ipsec import IpsecTcpTests from vpp_ipsec import VppIpsecSA, VppIpsecSpd, VppIpsecSpdEntry,\ VppIpsecSpdItfBinding @@ -85,6 +85,7 @@ class TemplateIpsecAh(TemplateIpsec): remote_tun_if_host = params.remote_tun_if_host addr_any = params.addr_any addr_bcast = params.addr_bcast + flags = params.flags e = VppEnum.vl_api_ipsec_spd_action_t params.tun_sa_in = VppIpsecSA(self, scapy_tun_sa_id, scapy_tun_spi, @@ -92,14 +93,16 @@ class TemplateIpsecAh(TemplateIpsec): crypt_algo_vpp_id, crypt_key, self.vpp_ah_protocol, self.tun_if.local_addr[addr_type], - self.tun_if.remote_addr[addr_type]) + self.tun_if.remote_addr[addr_type], + flags=flags) params.tun_sa_in.add_vpp_config() params.tun_sa_out = VppIpsecSA(self, vpp_tun_sa_id, vpp_tun_spi, auth_algo_vpp_id, auth_key, crypt_algo_vpp_id, crypt_key, self.vpp_ah_protocol, self.tun_if.remote_addr[addr_type], - self.tun_if.local_addr[addr_type]) + self.tun_if.local_addr[addr_type], + flags=flags) params.tun_sa_out.add_vpp_config() params.spd_policy_in_any = VppIpsecSpdEntry(self, self.tun_spd, @@ -160,8 +163,8 @@ class TemplateIpsecAh(TemplateIpsec): crypt_key = params.crypt_key addr_any = params.addr_any addr_bcast = params.addr_bcast - flags = (VppEnum.vl_api_ipsec_sad_flags_t. - IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY) + flags = params.flags | (VppEnum.vl_api_ipsec_sad_flags_t. + IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY) e = VppEnum.vl_api_ipsec_spd_action_t params.tra_sa_in = VppIpsecSA(self, scapy_tra_sa_id, scapy_tra_spi, @@ -221,5 +224,26 @@ class TestIpsecAh2(TemplateIpsecAh, IpsecTcpTests): pass +class TestIpsecAh3(TemplateIpsecAh, IpsecTra46Tests, IpsecTun46Tests): + """ Ipsec AH w/ ESN - TCP tests """ + + tra4_encrypt_node_name = "ah4-encrypt" + tra4_decrypt_node_name = "ah4-decrypt" + tra6_encrypt_node_name = "ah6-encrypt" + tra6_decrypt_node_name = "ah6-decrypt" + tun4_encrypt_node_name = "ah4-encrypt" + tun4_decrypt_node_name = "ah4-decrypt" + tun6_encrypt_node_name = "ah6-encrypt" + tun6_decrypt_node_name = "ah6-decrypt" + + def setup_params(self): + self.ipv4_params = IPsecIPv4Params() + self.ipv6_params = IPsecIPv6Params() + self.params = {self.ipv4_params.addr_type: self.ipv4_params, + self.ipv6_params.addr_type: self.ipv6_params} + for _, p in self.params.items(): + p.flags = (VppEnum.vl_api_ipsec_sad_flags_t. + IPSEC_API_SAD_FLAG_USE_EXTENDED_SEQ_NUM) + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg