ikev2: fix initial contact cleanup
[vpp.git] / src / plugins / ikev2 / test / test_ikev2.py
index 02d1bde..4f64b56 100644 (file)
@@ -9,15 +9,20 @@ from cryptography.hazmat.primitives.ciphers import (
     algorithms,
     modes,
 )
-from ipaddress import IPv4Address
+from ipaddress import IPv4Address, IPv6Address, ip_address
 from scapy.layers.ipsec import ESP
 from scapy.layers.inet import IP, UDP, Ether
+from scapy.layers.inet6 import IPv6
 from scapy.packet import raw, Raw
 from scapy.utils import long_converter
 from framework import VppTestCase, VppTestRunner
 from vpp_ikev2 import Profile, IDType, AuthMethod
 from vpp_papi import VppEnum
 
+try:
+    text_type = unicode
+except NameError:
+    text_type = str
 
 KEY_PAD = b"Key Pad for IKEv2"
 SALT_SIZE = 4
@@ -114,7 +119,7 @@ class CryptoAlgo(object):
     def pad(self, data):
         pad_len = (len(data) // self.bs + 1) * self.bs - len(data)
         data = data + b'\x00' * (pad_len - 1)
-        return data + bytes([pad_len])
+        return data + bytes([pad_len - 1])
 
 
 class AuthAlgo(object):
@@ -156,9 +161,9 @@ class IKEv2ChildSA(object):
 
 
 class IKEv2SA(object):
-    def __init__(self, test, is_initiator=True, spi=b'\x04' * 8,
-                 i_id=None, r_id=None, id_type='fqdn', nonce=None,
-                 auth_data=None, local_ts=None, remote_ts=None,
+    def __init__(self, test, is_initiator=True, i_id=None, r_id=None,
+                 spi=b'\x01\x02\x03\x04\x05\x06\x07\x08', id_type='fqdn',
+                 nonce=None, auth_data=None, local_ts=None, remote_ts=None,
                  auth_method='shared-key', priv_key=None, natt=False):
         self.natt = natt
         if natt:
@@ -167,6 +172,7 @@ class IKEv2SA(object):
         else:
             self.sport = 500
             self.dport = 500
+        self.msg_id = 0
         self.dh_params = None
         self.test = test
         self.priv_key = priv_key
@@ -181,40 +187,58 @@ class IKEv2SA(object):
             self.id_type = id_type
         self.auth_method = auth_method
         if self.is_initiator:
-            self.rspi = None
+            self.rspi = 8 * b'\x00'
             self.ispi = spi
             self.i_nonce = nonce
         else:
             self.rspi = spi
-            self.ispi = None
-            self.r_nonce = None
+            self.ispi = 8 * b'\x00'
+            self.r_nonce = nonce
         self.child_sas = [IKEv2ChildSA(local_ts, remote_ts)]
 
-    def dh_pub_key(self):
+    def new_msg_id(self):
+        self.msg_id += 1
+        return self.msg_id
+
+    @property
+    def my_dh_pub_key(self):
+        if self.is_initiator:
+            return self.i_dh_data
+        return self.r_dh_data
+
+    @property
+    def peer_dh_pub_key(self):
+        if self.is_initiator:
+            return self.r_dh_data
         return self.i_dh_data
 
     def compute_secret(self):
         priv = self.dh_private_key
-        peer = self.r_dh_data
+        peer = self.peer_dh_pub_key
         p, g, l = self.ike_group
         return pow(int.from_bytes(peer, 'big'),
                    int.from_bytes(priv, 'big'), p).to_bytes(l, 'big')
 
     def generate_dh_data(self):
         # generate DH keys
+        if self.ike_dh not in DH:
+            raise NotImplementedError('%s not in DH group' % self.ike_dh)
+
+        if self.dh_params is None:
+            dhg = DH[self.ike_dh]
+            pn = dh.DHParameterNumbers(dhg[0], dhg[1])
+            self.dh_params = pn.parameters(default_backend())
+
+        priv = self.dh_params.generate_private_key()
+        pub = priv.public_key()
+        x = priv.private_numbers().x
+        self.dh_private_key = x.to_bytes(priv.key_size // 8, 'big')
+        y = pub.public_numbers().y
+
         if self.is_initiator:
-            if self.ike_dh not in DH:
-                raise NotImplementedError('%s not in DH group' % self.ike_dh)
-            if self.dh_params is None:
-                dhg = DH[self.ike_dh]
-                pn = dh.DHParameterNumbers(dhg[0], dhg[1])
-                self.dh_params = pn.parameters(default_backend())
-            priv = self.dh_params.generate_private_key()
-            pub = priv.public_key()
-            x = priv.private_numbers().x
-            self.dh_private_key = x.to_bytes(priv.key_size // 8, 'big')
-            y = pub.public_numbers().y
             self.i_dh_data = y.to_bytes(pub.key_size // 8, 'big')
+        else:
+            self.r_dh_data = y.to_bytes(pub.key_size // 8, 'big')
 
     def complete_dh_data(self):
         self.dh_shared_secret = self.compute_secret()
@@ -325,13 +349,21 @@ class IKEv2SA(object):
             id = self.i_id
             nonce = self.r_nonce
             key = self.sk_pi
+        else:
+            id = self.r_id
+            nonce = self.i_nonce
+            key = self.sk_pr
         data = bytes([self.id_type, 0, 0, 0]) + id
         id_hash = self.calc_prf(prf, key, data)
         return packet + nonce + id_hash
 
     def auth_init(self):
         prf = self.ike_prf_alg.mod()
-        authmsg = self.generate_authmsg(prf, raw(self.init_req_packet))
+        if self.is_initiator:
+            packet = self.init_req_packet
+        else:
+            packet = self.init_resp_packet
+        authmsg = self.generate_authmsg(prf, raw(packet))
         if self.auth_method == 'shared-key':
             psk = self.calc_prf(prf, self.auth_data, KEY_PAD)
             self.auth_data = self.calc_prf(prf, psk, authmsg)
@@ -411,17 +443,29 @@ class IKEv2SA(object):
             ct = ep.load[:-integ_trunc]
             return self.decrypt(ct)
 
-    def generate_ts(self):
+    def build_ts_addr(self, ts, version):
+        return {'starting_address_v' + version: ts['start_addr'],
+                'ending_address_v' + version: ts['end_addr']}
+
+    def generate_ts(self, is_ip4):
         c = self.child_sas[0]
-        ts1 = ikev2.IPv4TrafficSelector(
-                IP_protocol_ID=0,
-                starting_address_v4=c.local_ts['start_addr'],
-                ending_address_v4=c.local_ts['end_addr'])
-        ts2 = ikev2.IPv4TrafficSelector(
-                IP_protocol_ID=0,
-                starting_address_v4=c.remote_ts['start_addr'],
-                ending_address_v4=c.remote_ts['end_addr'])
-        return ([ts1], [ts2])
+        ts_data = {'IP_protocol_ID': 0,
+                   'start_port': 0,
+                   'end_port': 0xffff}
+        if is_ip4:
+            ts_data.update(self.build_ts_addr(c.local_ts, '4'))
+            ts1 = ikev2.IPv4TrafficSelector(**ts_data)
+            ts_data.update(self.build_ts_addr(c.remote_ts, '4'))
+            ts2 = ikev2.IPv4TrafficSelector(**ts_data)
+        else:
+            ts_data.update(self.build_ts_addr(c.local_ts, '6'))
+            ts1 = ikev2.IPv6TrafficSelector(**ts_data)
+            ts_data.update(self.build_ts_addr(c.remote_ts, '6'))
+            ts2 = ikev2.IPv6TrafficSelector(**ts_data)
+
+        if self.is_initiator:
+            return ([ts1], [ts2])
+        return ([ts2], [ts1])
 
     def set_ike_props(self, crypto, crypto_key_len, integ, prf, dh):
         if crypto not in CRYPTO_ALGOS:
@@ -467,52 +511,438 @@ class IKEv2SA(object):
         return self.crypto_attr(self.esp_crypto_key_len)
 
     def compute_nat_sha1(self, ip, port):
-        data = self.ispi + b'\x00' * 8 + ip + (port).to_bytes(2, 'big')
+        data = self.ispi + self.rspi + ip + (port).to_bytes(2, 'big')
         digest = hashes.Hash(hashes.SHA1(), backend=default_backend())
         digest.update(data)
         return digest.finalize()
 
 
-class TemplateResponder(VppTestCase):
-    """ responder test template """
+class IkePeer(VppTestCase):
+    """ common class for initiator and responder """
 
     @classmethod
     def setUpClass(cls):
         import scapy.contrib.ikev2 as _ikev2
         globals()['ikev2'] = _ikev2
-        super(TemplateResponder, cls).setUpClass()
+        super(IkePeer, cls).setUpClass()
         cls.create_pg_interfaces(range(2))
         for i in cls.pg_interfaces:
             i.admin_up()
             i.config_ip4()
             i.resolve_arp()
+            i.config_ip6()
+            i.resolve_ndp()
 
     @classmethod
     def tearDownClass(cls):
-        super(TemplateResponder, cls).tearDownClass()
+        super(IkePeer, cls).tearDownClass()
 
     def setUp(self):
-        super(TemplateResponder, self).setUp()
+        super(IkePeer, self).setUp()
         self.config_tc()
         self.p.add_vpp_config()
         self.assertIsNotNone(self.p.query_vpp_config())
         self.sa.generate_dh_data()
-
-    def tearDown(self):
-        super(TemplateResponder, self).tearDown()
-        self.p.remove_vpp_config()
-        self.assertIsNone(self.p.query_vpp_config())
-
-    def create_ike_msg(self, src_if, msg, sport=500, dport=500, natt=False):
+        self.vapi.cli('ikev2 set logging level 4')
+        self.vapi.cli('event-lo clear')
+
+    def create_packet(self, src_if, msg, sport=500, dport=500, natt=False,
+                      use_ip6=False):
+        if use_ip6:
+            src_ip = src_if.remote_ip6
+            dst_ip = src_if.local_ip6
+            ip_layer = IPv6
+        else:
+            src_ip = src_if.remote_ip4
+            dst_ip = src_if.local_ip4
+            ip_layer = IP
         res = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
-               IP(src=src_if.remote_ip4, dst=src_if.local_ip4) /
+               ip_layer(src=src_ip, dst=dst_ip) /
                UDP(sport=sport, dport=dport))
         if natt:
             # insert non ESP marker
             res = res / Raw(b'\x00' * 4)
         return res / msg
 
-    def send_sa_init(self, behind_nat=False):
+    def verify_udp(self, udp):
+        self.assertEqual(udp.sport, self.sa.sport)
+        self.assertEqual(udp.dport, self.sa.dport)
+
+    def get_ike_header(self, packet):
+        try:
+            ih = packet[ikev2.IKEv2]
+        except IndexError as e:
+            # this is a workaround for getting IKEv2 layer as both ikev2 and
+            # ipsec register for port 4500
+            esp = packet[ESP]
+            ih = self.verify_and_remove_non_esp_marker(esp)
+        self.assertEqual(ih.version, 0x20)
+        return ih
+
+    def verify_and_remove_non_esp_marker(self, packet):
+        if self.sa.natt:
+            # if we are in nat traversal mode check for non esp marker
+            # and remove it
+            data = raw(packet)
+            self.assertEqual(data[:4], b'\x00' * 4)
+            return ikev2.IKEv2(data[4:])
+        else:
+            return packet
+
+    def encrypt_ike_msg(self, header, plain, first_payload):
+        if self.sa.ike_crypto == 'AES-GCM-16ICV':
+            data = self.sa.ike_crypto_alg.pad(raw(plain))
+            plen = len(data) + GCM_IV_SIZE + GCM_ICV_SIZE +\
+                len(ikev2.IKEv2_payload_Encrypted())
+            tlen = plen + len(ikev2.IKEv2())
+
+            # prepare aad data
+            sk_p = ikev2.IKEv2_payload_Encrypted(next_payload=first_payload,
+                                                 length=plen)
+            header.length = tlen
+            res = header / sk_p
+            encr = self.sa.encrypt(raw(plain), raw(res))
+            sk_p = ikev2.IKEv2_payload_Encrypted(next_payload=first_payload,
+                                                 length=plen, load=encr)
+            res = header / sk_p
+        else:
+            encr = self.sa.encrypt(raw(plain))
+            trunc_len = self.sa.ike_integ_alg.trunc_len
+            plen = len(encr) + len(ikev2.IKEv2_payload_Encrypted()) + trunc_len
+            tlen = plen + len(ikev2.IKEv2())
+
+            sk_p = ikev2.IKEv2_payload_Encrypted(next_payload=first_payload,
+                                                 length=plen, load=encr)
+            header.length = tlen
+            res = header / sk_p
+
+            integ_data = raw(res)
+            hmac_data = self.sa.compute_hmac(self.sa.ike_integ_alg.mod(),
+                                             self.sa.my_authkey, integ_data)
+            res = res / Raw(hmac_data[:trunc_len])
+        assert(len(res) == tlen)
+        return res
+
+    def verify_ipsec_sas(self):
+        sas = self.vapi.ipsec_sa_dump()
+        self.assertEqual(len(sas), 2)
+        e = VppEnum.vl_api_ipsec_sad_flags_t
+        if self.sa.is_initiator:
+            sa0 = sas[0].entry
+            sa1 = sas[1].entry
+        else:
+            sa1 = sas[0].entry
+            sa0 = sas[1].entry
+
+        c = self.sa.child_sas[0]
+
+        vpp_crypto_alg = self.vpp_enums[self.sa.vpp_esp_cypto_alg]
+        self.assertEqual(sa0.crypto_algorithm, vpp_crypto_alg)
+        self.assertEqual(sa1.crypto_algorithm, vpp_crypto_alg)
+
+        if self.sa.esp_integ is None:
+            vpp_integ_alg = 0
+        else:
+            vpp_integ_alg = self.vpp_enums[self.sa.esp_integ]
+        self.assertEqual(sa0.integrity_algorithm, vpp_integ_alg)
+        self.assertEqual(sa1.integrity_algorithm, vpp_integ_alg)
+
+        # verify crypto keys
+        self.assertEqual(sa0.crypto_key.length, len(c.sk_er))
+        self.assertEqual(sa1.crypto_key.length, len(c.sk_ei))
+        self.assertEqual(sa0.crypto_key.data[:len(c.sk_er)], c.sk_er)
+        self.assertEqual(sa1.crypto_key.data[:len(c.sk_ei)], c.sk_ei)
+
+        # verify integ keys
+        if vpp_integ_alg:
+            self.assertEqual(sa0.integrity_key.length, len(c.sk_ar))
+            self.assertEqual(sa1.integrity_key.length, len(c.sk_ai))
+            self.assertEqual(sa0.integrity_key.data[:len(c.sk_ar)], c.sk_ar)
+            self.assertEqual(sa1.integrity_key.data[:len(c.sk_ai)], c.sk_ai)
+        else:
+            self.assertEqual(sa0.salt.to_bytes(4, 'little'), c.salt_er)
+            self.assertEqual(sa1.salt.to_bytes(4, 'little'), c.salt_ei)
+
+    def verify_keymat(self, api_keys, keys, name):
+        km = getattr(keys, name)
+        api_km = getattr(api_keys, name)
+        api_km_len = getattr(api_keys, name + '_len')
+        self.assertEqual(len(km), api_km_len)
+        self.assertEqual(km, api_km[:api_km_len])
+
+    def verify_id(self, api_id, exp_id):
+        self.assertEqual(api_id.type, IDType.value(exp_id.type))
+        self.assertEqual(api_id.data_len, exp_id.data_len)
+        self.assertEqual(bytes(api_id.data, 'ascii'), exp_id.type)
+
+    def verify_ike_sas(self):
+        r = self.vapi.ikev2_sa_dump()
+        self.assertEqual(len(r), 1)
+        sa = r[0].sa
+        self.assertEqual(self.sa.ispi, (sa.ispi).to_bytes(8, 'big'))
+        self.assertEqual(self.sa.rspi, (sa.rspi).to_bytes(8, 'big'))
+        if self.ip6:
+            if self.sa.is_initiator:
+                self.assertEqual(sa.iaddr, IPv6Address(self.pg0.remote_ip6))
+                self.assertEqual(sa.raddr, IPv6Address(self.pg0.local_ip6))
+            else:
+                self.assertEqual(sa.iaddr, IPv6Address(self.pg0.local_ip6))
+                self.assertEqual(sa.raddr, IPv6Address(self.pg0.remote_ip6))
+        else:
+            if self.sa.is_initiator:
+                self.assertEqual(sa.iaddr, IPv4Address(self.pg0.remote_ip4))
+                self.assertEqual(sa.raddr, IPv4Address(self.pg0.local_ip4))
+            else:
+                self.assertEqual(sa.iaddr, IPv4Address(self.pg0.local_ip4))
+                self.assertEqual(sa.raddr, IPv4Address(self.pg0.remote_ip4))
+        self.verify_keymat(sa.keys, self.sa, 'sk_d')
+        self.verify_keymat(sa.keys, self.sa, 'sk_ai')
+        self.verify_keymat(sa.keys, self.sa, 'sk_ar')
+        self.verify_keymat(sa.keys, self.sa, 'sk_ei')
+        self.verify_keymat(sa.keys, self.sa, 'sk_er')
+        self.verify_keymat(sa.keys, self.sa, 'sk_pi')
+        self.verify_keymat(sa.keys, self.sa, 'sk_pr')
+
+        self.assertEqual(sa.i_id.type, self.sa.id_type)
+        self.assertEqual(sa.r_id.type, self.sa.id_type)
+        self.assertEqual(sa.i_id.data_len, len(self.sa.i_id))
+        self.assertEqual(sa.r_id.data_len, len(self.sa.r_id))
+        self.assertEqual(bytes(sa.i_id.data, 'ascii'), self.sa.i_id)
+        self.assertEqual(bytes(sa.r_id.data, 'ascii'), self.sa.r_id)
+
+        r = self.vapi.ikev2_child_sa_dump(sa_index=sa.sa_index)
+        self.assertEqual(len(r), 1)
+        csa = r[0].child_sa
+        self.assertEqual(csa.sa_index, sa.sa_index)
+        c = self.sa.child_sas[0]
+        if hasattr(c, 'sk_ai'):
+            self.verify_keymat(csa.keys, c, 'sk_ai')
+            self.verify_keymat(csa.keys, c, 'sk_ar')
+        self.verify_keymat(csa.keys, c, 'sk_ei')
+        self.verify_keymat(csa.keys, c, 'sk_er')
+
+        tsi, tsr = self.sa.generate_ts(self.p.ts_is_ip4)
+        tsi = tsi[0]
+        tsr = tsr[0]
+        r = self.vapi.ikev2_traffic_selector_dump(
+                is_initiator=True, sa_index=sa.sa_index,
+                child_sa_index=csa.child_sa_index)
+        self.assertEqual(len(r), 1)
+        ts = r[0].ts
+        self.verify_ts(r[0].ts, tsi[0], True)
+
+        r = self.vapi.ikev2_traffic_selector_dump(
+                is_initiator=False, sa_index=sa.sa_index,
+                child_sa_index=csa.child_sa_index)
+        self.assertEqual(len(r), 1)
+        self.verify_ts(r[0].ts, tsr[0], False)
+
+        n = self.vapi.ikev2_nonce_get(is_initiator=True,
+                                      sa_index=sa.sa_index)
+        self.verify_nonce(n, self.sa.i_nonce)
+        n = self.vapi.ikev2_nonce_get(is_initiator=False,
+                                      sa_index=sa.sa_index)
+        self.verify_nonce(n, self.sa.r_nonce)
+
+    def verify_nonce(self, api_nonce, nonce):
+        self.assertEqual(api_nonce.data_len, len(nonce))
+        self.assertEqual(api_nonce.nonce, nonce)
+
+    def verify_ts(self, api_ts, ts, is_initiator):
+        if is_initiator:
+            self.assertTrue(api_ts.is_local)
+        else:
+            self.assertFalse(api_ts.is_local)
+
+        if self.p.ts_is_ip4:
+            self.assertEqual(api_ts.start_addr,
+                             IPv4Address(ts.starting_address_v4))
+            self.assertEqual(api_ts.end_addr,
+                             IPv4Address(ts.ending_address_v4))
+        else:
+            self.assertEqual(api_ts.start_addr,
+                             IPv6Address(ts.starting_address_v6))
+            self.assertEqual(api_ts.end_addr,
+                             IPv6Address(ts.ending_address_v6))
+        self.assertEqual(api_ts.start_port, ts.start_port)
+        self.assertEqual(api_ts.end_port, ts.end_port)
+        self.assertEqual(api_ts.protocol_id, ts.IP_protocol_ID)
+
+
+class TemplateInitiator(IkePeer):
+    """ initiator test template """
+
+    def tearDown(self):
+        super(TemplateInitiator, self).tearDown()
+
+    def verify_sa_init_request(self, packet):
+        ih = packet[ikev2.IKEv2]
+        self.assertNotEqual(ih.init_SPI, 8 * b'\x00')
+        self.assertEqual(ih.exch_type, 34)  # SA_INIT
+        self.sa.ispi = ih.init_SPI
+        self.assertEqual(ih.resp_SPI, 8 * b'\x00')
+        self.assertIn('Initiator', ih.flags)
+        self.assertNotIn('Response', ih.flags)
+        self.sa.i_nonce = ih[ikev2.IKEv2_payload_Nonce].load
+        self.sa.i_dh_data = ih[ikev2.IKEv2_payload_KE].load
+
+        prop = packet[ikev2.IKEv2_payload_Proposal]
+        self.assertEqual(prop.proto, 1)  # proto = ikev2
+        self.assertEqual(prop.proposal, 1)
+        self.assertEqual(prop.trans[0].transform_type, 1)  # encryption
+        self.assertEqual(prop.trans[0].transform_id,
+                         self.p.ike_transforms['crypto_alg'])
+        self.assertEqual(prop.trans[1].transform_type, 2)  # prf
+        self.assertEqual(prop.trans[1].transform_id, 5)  # "hmac-sha2-256"
+        self.assertEqual(prop.trans[2].transform_type, 4)  # dh
+        self.assertEqual(prop.trans[2].transform_id,
+                         self.p.ike_transforms['dh_group'])
+
+        self.sa.complete_dh_data()
+        self.sa.calc_keys()
+
+    def verify_sa_auth_req(self, packet):
+        ih = self.get_ike_header(packet)
+        self.assertEqual(ih.resp_SPI, self.sa.rspi)
+        self.assertEqual(ih.init_SPI, self.sa.ispi)
+        self.assertEqual(ih.exch_type, 35)  # IKE_AUTH
+        self.assertIn('Initiator', ih.flags)
+        self.assertNotIn('Response', ih.flags)
+
+        udp = packet[UDP]
+        self.verify_udp(udp)
+        self.assertEqual(ih.id, self.sa.msg_id + 1)
+        self.sa.msg_id += 1
+        plain = self.sa.hmac_and_decrypt(ih)
+        idi = ikev2.IKEv2_payload_IDi(plain)
+        idr = ikev2.IKEv2_payload_IDr(idi.payload)
+        self.assertEqual(idi.load, self.sa.i_id)
+        self.assertEqual(idr.load, self.sa.r_id)
+
+    def send_init_response(self):
+        tr_attr = self.sa.ike_crypto_attr()
+        trans = (ikev2.IKEv2_payload_Transform(transform_type='Encryption',
+                 transform_id=self.sa.ike_crypto, length=tr_attr[1],
+                 key_length=tr_attr[0]) /
+                 ikev2.IKEv2_payload_Transform(transform_type='Integrity',
+                 transform_id=self.sa.ike_integ) /
+                 ikev2.IKEv2_payload_Transform(transform_type='PRF',
+                 transform_id=self.sa.ike_prf_alg.name) /
+                 ikev2.IKEv2_payload_Transform(transform_type='GroupDesc',
+                 transform_id=self.sa.ike_dh))
+        props = (ikev2.IKEv2_payload_Proposal(proposal=1, proto='IKEv2',
+                 trans_nb=4, trans=trans))
+        self.sa.init_resp_packet = (
+            ikev2.IKEv2(init_SPI=self.sa.ispi, resp_SPI=self.sa.rspi,
+                        exch_type='IKE_SA_INIT', flags='Response') /
+            ikev2.IKEv2_payload_SA(next_payload='KE', prop=props) /
+            ikev2.IKEv2_payload_KE(next_payload='Nonce',
+                                   group=self.sa.ike_dh,
+                                   load=self.sa.my_dh_pub_key) /
+            ikev2.IKEv2_payload_Nonce(load=self.sa.r_nonce))
+
+        ike_msg = self.create_packet(self.pg0, self.sa.init_resp_packet,
+                                     self.sa.sport, self.sa.dport,
+                                     self.sa.natt, self.ip6)
+        self.pg_send(self.pg0, ike_msg)
+        capture = self.pg0.get_capture(1)
+        self.verify_sa_auth_req(capture[0])
+
+    def initiate_sa_init(self):
+        self.pg0.enable_capture()
+        self.pg_start()
+        self.vapi.ikev2_initiate_sa_init(name=self.p.profile_name)
+
+        capture = self.pg0.get_capture(1)
+        self.verify_sa_init_request(capture[0])
+        self.send_init_response()
+
+    def send_auth_response(self):
+        tr_attr = self.sa.esp_crypto_attr()
+        trans = (ikev2.IKEv2_payload_Transform(transform_type='Encryption',
+                 transform_id=self.sa.esp_crypto, length=tr_attr[1],
+                 key_length=tr_attr[0]) /
+                 ikev2.IKEv2_payload_Transform(transform_type='Integrity',
+                 transform_id=self.sa.esp_integ) /
+                 ikev2.IKEv2_payload_Transform(
+                 transform_type='Extended Sequence Number',
+                 transform_id='No ESN') /
+                 ikev2.IKEv2_payload_Transform(
+                 transform_type='Extended Sequence Number',
+                 transform_id='ESN'))
+
+        props = (ikev2.IKEv2_payload_Proposal(proposal=1, proto='ESP',
+                 SPIsize=4, SPI=os.urandom(4), trans_nb=4, trans=trans))
+
+        tsi, tsr = self.sa.generate_ts(self.p.ts_is_ip4)
+        plain = (ikev2.IKEv2_payload_IDi(next_payload='IDr',
+                 IDtype=self.sa.id_type, load=self.sa.i_id) /
+                 ikev2.IKEv2_payload_IDr(next_payload='AUTH',
+                 IDtype=self.sa.id_type, load=self.sa.r_id) /
+                 ikev2.IKEv2_payload_AUTH(next_payload='SA',
+                 auth_type=AuthMethod.value(self.sa.auth_method),
+                 load=self.sa.auth_data) /
+                 ikev2.IKEv2_payload_SA(next_payload='TSi', prop=props) /
+                 ikev2.IKEv2_payload_TSi(next_payload='TSr',
+                 number_of_TSs=len(tsi),
+                 traffic_selector=tsi) /
+                 ikev2.IKEv2_payload_TSr(next_payload='Notify',
+                 number_of_TSs=len(tsr),
+                 traffic_selector=tsr) /
+                 ikev2.IKEv2_payload_Notify(type='INITIAL_CONTACT'))
+
+        header = ikev2.IKEv2(
+                init_SPI=self.sa.ispi,
+                resp_SPI=self.sa.rspi, id=self.sa.new_msg_id(),
+                flags='Response', exch_type='IKE_AUTH')
+
+        ike_msg = self.encrypt_ike_msg(header, plain, 'IDi')
+        packet = self.create_packet(self.pg0, ike_msg, self.sa.sport,
+                                    self.sa.dport, self.sa.natt, self.ip6)
+        self.pg_send(self.pg0, packet)
+
+    def test_initiator(self):
+        self.initiate_sa_init()
+        self.sa.auth_init()
+        self.sa.calc_child_keys()
+        self.send_auth_response()
+        self.verify_ike_sas()
+
+
+class TemplateResponder(IkePeer):
+    """ responder test template """
+
+    def tearDown(self):
+        super(TemplateResponder, self).tearDown()
+        if self.sa.is_initiator:
+            self.initiate_del_sa()
+            r = self.vapi.ikev2_sa_dump()
+            self.assertEqual(len(r), 0)
+
+        self.p.remove_vpp_config()
+        self.assertIsNone(self.p.query_vpp_config())
+
+    def verify_del_sa(self, packet):
+        ih = self.get_ike_header(packet)
+        self.assertEqual(ih.id, self.sa.msg_id)
+        self.assertEqual(ih.exch_type, 37)  # exchange informational
+
+    def initiate_del_sa(self):
+        header = ikev2.IKEv2(init_SPI=self.sa.ispi, resp_SPI=self.sa.rspi,
+                             flags='Initiator', exch_type='INFORMATIONAL',
+                             id=self.sa.new_msg_id())
+        del_sa = ikev2.IKEv2_payload_Delete(proto='IKEv2')
+        ike_msg = self.encrypt_ike_msg(header, del_sa, 'Delete')
+        packet = self.create_packet(self.pg0, ike_msg,
+                                    self.sa.sport, self.sa.dport,
+                                    self.sa.natt, self.ip6)
+        self.pg0.add_stream(packet)
+        self.pg0.enable_capture()
+        self.pg_start()
+        capture = self.pg0.get_capture(1)
+        self.verify_del_sa(capture[0])
+
+    def send_sa_init_req(self, behind_nat=False):
         tr_attr = self.sa.ike_crypto_attr()
         trans = (ikev2.IKEv2_payload_Transform(transform_type='Encryption',
                  transform_id=self.sa.ike_crypto, length=tr_attr[1],
@@ -538,21 +968,29 @@ class TemplateResponder(VppTestCase):
                 ikev2.IKEv2_payload_SA(next_payload='KE', prop=props) /
                 ikev2.IKEv2_payload_KE(next_payload='Nonce',
                                        group=self.sa.ike_dh,
-                                       load=self.sa.dh_pub_key()) /
+                                       load=self.sa.my_dh_pub_key) /
                 ikev2.IKEv2_payload_Nonce(next_payload=next_payload,
                                           load=self.sa.i_nonce))
 
         if behind_nat:
-            src_nat = self.sa.compute_nat_sha1(b'\x0a\x0a\x0a\x01',
-                                               self.sa.sport)
-            nat_detection = ikev2.IKEv2_payload_Notify(
-                    type='NAT_DETECTION_SOURCE_IP',
-                    load=src_nat)
-            self.sa.init_req_packet = self.sa.init_req_packet / nat_detection
-
-        ike_msg = self.create_ike_msg(self.pg0, self.sa.init_req_packet,
-                                      self.sa.sport, self.sa.dport,
-                                      self.sa.natt)
+            src_address = b'\x0a\x0a\x0a\x01'
+        else:
+            src_address = bytes(self.pg0.local_ip4, 'ascii')
+
+        src_nat = self.sa.compute_nat_sha1(src_address, self.sa.sport)
+        dst_nat = self.sa.compute_nat_sha1(bytes(self.pg0.remote_ip4, 'ascii'),
+                                           self.sa.sport)
+        nat_src_detection = ikev2.IKEv2_payload_Notify(
+                type='NAT_DETECTION_SOURCE_IP', load=src_nat)
+        nat_dst_detection = ikev2.IKEv2_payload_Notify(
+                type='NAT_DETECTION_DESTINATION_IP', load=dst_nat)
+        self.sa.init_req_packet = (self.sa.init_req_packet /
+                                   nat_src_detection /
+                                   nat_dst_detection)
+
+        ike_msg = self.create_packet(self.pg0, self.sa.init_req_packet,
+                                     self.sa.sport, self.sa.dport,
+                                     self.sa.natt, self.ip6)
         self.pg0.add_stream(ike_msg)
         self.pg0.enable_capture()
         self.pg_start()
@@ -576,7 +1014,7 @@ class TemplateResponder(VppTestCase):
         props = (ikev2.IKEv2_payload_Proposal(proposal=1, proto='ESP',
                  SPIsize=4, SPI=os.urandom(4), trans_nb=4, trans=trans))
 
-        tsi, tsr = self.sa.generate_ts()
+        tsi, tsr = self.sa.generate_ts(self.p.ts_is_ip4)
         plain = (ikev2.IKEv2_payload_IDi(next_payload='IDr',
                  IDtype=self.sa.id_type, load=self.sa.i_id) /
                  ikev2.IKEv2_payload_IDr(next_payload='AUTH',
@@ -593,67 +1031,24 @@ class TemplateResponder(VppTestCase):
                  traffic_selector=tsr) /
                  ikev2.IKEv2_payload_Notify(type='INITIAL_CONTACT'))
 
-        if self.sa.ike_crypto == 'AES-GCM-16ICV':
-            data = self.sa.ike_crypto_alg.pad(raw(plain))
-            plen = len(data) + GCM_IV_SIZE + GCM_ICV_SIZE +\
-                len(ikev2.IKEv2_payload_Encrypted())
-            tlen = plen + len(ikev2.IKEv2())
+        header = ikev2.IKEv2(
+                init_SPI=self.sa.ispi,
+                resp_SPI=self.sa.rspi, id=self.sa.new_msg_id(),
+                flags='Initiator', exch_type='IKE_AUTH')
 
-            # prepare aad data
-            sk_p = ikev2.IKEv2_payload_Encrypted(next_payload='IDi',
-                                                 length=plen)
-            sa_auth = (ikev2.IKEv2(init_SPI=self.sa.ispi,
-                       resp_SPI=self.sa.rspi, id=1,
-                       length=tlen, flags='Initiator', exch_type='IKE_AUTH'))
-            sa_auth /= sk_p
-
-            encr = self.sa.encrypt(raw(plain), raw(sa_auth))
-            sk_p = ikev2.IKEv2_payload_Encrypted(next_payload='IDi',
-                                                 length=plen, load=encr)
-            sa_auth = (ikev2.IKEv2(init_SPI=self.sa.ispi,
-                       resp_SPI=self.sa.rspi, id=1,
-                       length=tlen, flags='Initiator', exch_type='IKE_AUTH'))
-            sa_auth /= sk_p
-        else:
-            encr = self.sa.encrypt(raw(plain))
-            trunc_len = self.sa.ike_integ_alg.trunc_len
-            plen = len(encr) + len(ikev2.IKEv2_payload_Encrypted()) + trunc_len
-            tlen = plen + len(ikev2.IKEv2())
-
-            sk_p = ikev2.IKEv2_payload_Encrypted(next_payload='IDi',
-                                                 length=plen, load=encr)
-            sa_auth = (ikev2.IKEv2(init_SPI=self.sa.ispi,
-                       resp_SPI=self.sa.rspi, id=1,
-                       length=tlen, flags='Initiator', exch_type='IKE_AUTH'))
-            sa_auth /= sk_p
-
-            integ_data = raw(sa_auth)
-            hmac_data = self.sa.compute_hmac(self.sa.ike_integ_alg.mod(),
-                                             self.sa.my_authkey, integ_data)
-            sa_auth = sa_auth / Raw(hmac_data[:trunc_len])
-
-        assert(len(sa_auth) == tlen)
-        packet = self.create_ike_msg(self.pg0, sa_auth, self.sa.sport,
-                                     self.sa.dport, self.sa.natt)
+        ike_msg = self.encrypt_ike_msg(header, plain, 'IDi')
+        packet = self.create_packet(self.pg0, ike_msg, self.sa.sport,
+                                    self.sa.dport, self.sa.natt, self.ip6)
         self.pg0.add_stream(packet)
         self.pg0.enable_capture()
         self.pg_start()
         capture = self.pg0.get_capture(1)
-        self.verify_sa_auth(capture[0])
-
-    def get_ike_header(self, packet):
-        try:
-            ih = packet[ikev2.IKEv2]
-        except IndexError as e:
-            # this is a workaround for getting IKEv2 layer as both ikev2 and
-            # ipsec register for port 4500
-            esp = packet[ESP]
-            ih = self.verify_and_remove_non_esp_marker(esp)
-        return ih
+        self.verify_sa_auth_resp(capture[0])
 
     def verify_sa_init(self, packet):
         ih = self.get_ike_header(packet)
 
+        self.assertEqual(ih.id, self.sa.msg_id)
         self.assertEqual(ih.exch_type, 34)
         self.assertTrue('Response' in ih.flags)
         self.assertEqual(ih.init_SPI, self.sa.ispi)
@@ -671,65 +1066,19 @@ class TemplateResponder(VppTestCase):
         self.sa.calc_keys()
         self.sa.auth_init()
 
-    def verify_and_remove_non_esp_marker(self, packet):
-        if self.sa.natt:
-            # if we are in nat traversal mode check for non esp marker
-            # and remove it
-            data = raw(packet)
-            self.assertEqual(data[:4], b'\x00' * 4)
-            return ikev2.IKEv2(data[4:])
-        else:
-            return packet
-
-    def verify_udp(self, udp):
-        self.assertEqual(udp.sport, self.sa.sport)
-        self.assertEqual(udp.dport, self.sa.dport)
-
-    def verify_sa_auth(self, packet):
+    def verify_sa_auth_resp(self, packet):
         ike = self.get_ike_header(packet)
         udp = packet[UDP]
         self.verify_udp(udp)
+        self.assertEqual(ike.id, self.sa.msg_id)
         plain = self.sa.hmac_and_decrypt(ike)
         self.sa.calc_child_keys()
 
-    def verify_child_sas(self):
-        sas = self.vapi.ipsec_sa_dump()
-        self.assertEqual(len(sas), 2)
-        sa0 = sas[0].entry
-        sa1 = sas[1].entry
-        c = self.sa.child_sas[0]
-
-        vpp_crypto_alg = self.vpp_enums[self.sa.vpp_esp_cypto_alg]
-        self.assertEqual(sa0.crypto_algorithm, vpp_crypto_alg)
-        self.assertEqual(sa1.crypto_algorithm, vpp_crypto_alg)
-
-        if self.sa.esp_integ is None:
-            vpp_integ_alg = 0
-        else:
-            vpp_integ_alg = self.vpp_enums[self.sa.esp_integ]
-        self.assertEqual(sa0.integrity_algorithm, vpp_integ_alg)
-        self.assertEqual(sa1.integrity_algorithm, vpp_integ_alg)
-
-        # verify crypto keys
-        self.assertEqual(sa0.crypto_key.length, len(c.sk_er))
-        self.assertEqual(sa1.crypto_key.length, len(c.sk_ei))
-        self.assertEqual(sa0.crypto_key.data[:len(c.sk_er)], c.sk_er)
-        self.assertEqual(sa1.crypto_key.data[:len(c.sk_ei)], c.sk_ei)
-
-        # verify integ keys
-        if vpp_integ_alg:
-            self.assertEqual(sa0.integrity_key.length, len(c.sk_ar))
-            self.assertEqual(sa1.integrity_key.length, len(c.sk_ai))
-            self.assertEqual(sa0.integrity_key.data[:len(c.sk_ar)], c.sk_ar)
-            self.assertEqual(sa1.integrity_key.data[:len(c.sk_ai)], c.sk_ai)
-        else:
-            self.assertEqual(sa0.salt.to_bytes(4, 'little'), c.salt_er)
-            self.assertEqual(sa1.salt.to_bytes(4, 'little'), c.salt_ei)
-
     def test_responder(self):
-        self.send_sa_init(self.sa.natt)
+        self.send_sa_init_req(self.sa.natt)
         self.send_sa_auth()
-        self.verify_child_sas()
+        self.verify_ipsec_sas()
+        self.verify_ike_sas()
 
 
 class Ikev2Params(object):
@@ -751,6 +1100,7 @@ class Ikev2Params(object):
 
         is_natt = 'natt' in params and params['natt'] or False
         self.p = Profile(self, 'pr1')
+        self.ip6 = False if 'ip6' not in params else params['ip6']
 
         if 'auth' in params and params['auth'] == 'rsa-sig':
             auth_method = 'rsa-sig'
@@ -774,13 +1124,33 @@ class Ikev2Params(object):
             auth_method = 'shared-key'
             client_priv = None
 
-        self.p.add_local_id(id_type='fqdn', data=b'vpp.home')
-        self.p.add_remote_id(id_type='fqdn', data=b'roadwarrior.example.com')
-        self.p.add_local_ts(start_addr='10.10.10.0', end_addr='10.10.10.255')
-        self.p.add_remote_ts(start_addr='10.0.0.0', end_addr='10.0.0.255')
+        is_init = True if 'is_initiator' not in params else\
+            params['is_initiator']
 
-        self.sa = IKEv2SA(self, i_id=self.p.remote_id['data'],
-                          r_id=self.p.local_id['data'],
+        idr = {'id_type': 'fqdn', 'data': b'vpp.home'}
+        idi = {'id_type': 'fqdn', 'data': b'roadwarrior.example.com'}
+        if is_init:
+            self.p.add_local_id(**idr)
+            self.p.add_remote_id(**idi)
+        else:
+            self.p.add_local_id(**idi)
+            self.p.add_remote_id(**idr)
+
+        loc_ts = {'start_addr': '10.10.10.0', 'end_addr': '10.10.10.255'} if\
+            'loc_ts' not in params else params['loc_ts']
+        rem_ts = {'start_addr': '10.0.0.0', 'end_addr': '10.0.0.255'} if\
+            'rem_ts' not in params else params['rem_ts']
+        self.p.add_local_ts(**loc_ts)
+        self.p.add_remote_ts(**rem_ts)
+        if 'responder' in params:
+            self.p.add_responder(params['responder'])
+        if 'ike_transforms' in params:
+            self.p.add_ike_transforms(params['ike_transforms'])
+        if 'esp_transforms' in params:
+            self.p.add_esp_transforms(params['esp_transforms'])
+
+        self.sa = IKEv2SA(self, i_id=idi['data'], r_id=idr['data'],
+                          is_initiator=is_init,
                           id_type=self.p.local_id['id_type'], natt=is_natt,
                           priv_key=client_priv, auth_method=auth_method,
                           auth_data=auth_data,
@@ -790,7 +1160,8 @@ class Ikev2Params(object):
             params['ike-crypto']
         ike_integ = 'HMAC-SHA1-96' if 'ike-integ' not in params else\
             params['ike-integ']
-        ike_dh = '2048MODPgr' if 'ike-dh' not in params else params['ike-dh']
+        ike_dh = '2048MODPgr' if 'ike-dh' not in params else\
+            params['ike-dh']
 
         esp_crypto = ('AES-CBC', 32) if 'esp-crypto' not in params else\
             params['esp-crypto']
@@ -843,14 +1214,14 @@ class TestApi(VppTestCase):
 
     def test_profile_api(self):
         """ test profile dump API """
-        loc_ts = {
+        loc_ts4 = {
                     'proto': 8,
                     'start_port': 1,
                     'end_port': 19,
                     'start_addr': '3.3.3.2',
                     'end_addr': '3.3.3.3',
                 }
-        rem_ts = {
+        rem_ts4 = {
                     'proto': 9,
                     'start_port': 10,
                     'end_port': 119,
@@ -858,14 +1229,29 @@ class TestApi(VppTestCase):
                     'end_addr': '2.3.4.6',
                 }
 
+        loc_ts6 = {
+                    'proto': 8,
+                    'start_port': 1,
+                    'end_port': 19,
+                    'start_addr': 'ab::1',
+                    'end_addr': 'ab::4',
+                }
+        rem_ts6 = {
+                    'proto': 9,
+                    'start_port': 10,
+                    'end_port': 119,
+                    'start_addr': 'cd::12',
+                    'end_addr': 'cd::13',
+                }
+
         conf = {
             'p1': {
                 'name': 'p1',
                 'loc_id': ('fqdn', b'vpp.home'),
                 'rem_id': ('fqdn', b'roadwarrior.example.com'),
-                'loc_ts': loc_ts,
-                'rem_ts': rem_ts,
-                'responder': {'sw_if_index': 0, 'ip4': '5.6.7.8'},
+                'loc_ts': loc_ts4,
+                'rem_ts': rem_ts4,
+                'responder': {'sw_if_index': 0, 'addr': '5.6.7.8'},
                 'ike_ts': {
                         'crypto_alg': 20,
                         'crypto_key_size': 32,
@@ -887,10 +1273,10 @@ class TestApi(VppTestCase):
             'p2': {
                 'name': 'p2',
                 'loc_id': ('ip4-addr', b'192.168.2.1'),
-                'rem_id': ('ip4-addr', b'192.168.2.2'),
-                'loc_ts': loc_ts,
-                'rem_ts': rem_ts,
-                'responder': {'sw_if_index': 4, 'ip4': '5.6.7.99'},
+                'rem_id': ('ip6-addr', b'abcd::1'),
+                'loc_ts': loc_ts6,
+                'rem_ts': rem_ts6,
+                'responder': {'sw_if_index': 4, 'addr': 'def::10'},
                 'ike_ts': {
                         'crypto_alg': 12,
                         'crypto_key_size': 16,
@@ -921,12 +1307,14 @@ class TestApi(VppTestCase):
         self.assertEqual(api_ts.protocol_id, cfg_ts['proto'])
         self.assertEqual(api_ts.start_port, cfg_ts['start_port'])
         self.assertEqual(api_ts.end_port, cfg_ts['end_port'])
-        self.assertEqual(api_ts.start_addr, IPv4Address(cfg_ts['start_addr']))
-        self.assertEqual(api_ts.end_addr, IPv4Address(cfg_ts['end_addr']))
+        self.assertEqual(api_ts.start_addr,
+                         ip_address(text_type(cfg_ts['start_addr'])))
+        self.assertEqual(api_ts.end_addr,
+                         ip_address(text_type(cfg_ts['end_addr'])))
 
     def verify_responder(self, api_r, cfg_r):
         self.assertEqual(api_r.sw_if_index, cfg_r['sw_if_index'])
-        self.assertEqual(api_r.ip4, IPv4Address(cfg_r['ip4']))
+        self.assertEqual(api_r.addr, ip_address(cfg_r['addr']))
 
     def verify_transforms(self, api_ts, cfg_ts):
         self.assertEqual(api_ts.crypto_alg, cfg_ts['crypto_alg'])
@@ -971,6 +1359,29 @@ class TestApi(VppTestCase):
             self.assertEqual(ap.tun_itf, 0xffffffff)
 
 
+class TestInitiatorPsk(TemplateInitiator, Ikev2Params):
+    """ test ikev2 initiator - pre shared key auth """
+    def config_tc(self):
+        self.config_params({
+            'is_initiator': False,  # seen from test case perspective
+                                    # thus vpp is initiator
+            'responder': {'sw_if_index': self.pg0.sw_if_index,
+                           'addr': self.pg0.remote_ip4},
+            'ike-crypto': ('AES-GCM-16ICV', 32),
+            'ike-integ': 'NULL',
+            'ike-dh': '3072MODPgr',
+            'ike_transforms': {
+                'crypto_alg': 20,  # "aes-gcm-16"
+                'crypto_key_size': 256,
+                'dh_group': 15,  # "modp-3072"
+            },
+            'esp_transforms': {
+                'crypto_alg': 12,  # "aes-cbc"
+                'crypto_key_size': 256,
+                # "hmac-sha2-256-128"
+                'integ_alg': 12}})
+
+
 class TestResponderNATT(TemplateResponder, Ikev2Params):
     """ test ikev2 responder - nat traversal """
     def config_tc(self):
@@ -1029,9 +1440,53 @@ class Test_IKE_AES_GCM_16_256(TemplateResponder, Ikev2Params):
     """
     def config_tc(self):
         self.config_params({
+            'ip6': True,
+            'natt': True,
             'ike-crypto': ('AES-GCM-16ICV', 32),
             'ike-integ': 'NULL',
-            'ike-dh': '2048MODPgr'})
+            'ike-dh': '2048MODPgr',
+            'loc_ts': {'start_addr': 'ab:cd::0',
+                       'end_addr': 'ab:cd::10'},
+            'rem_ts': {'start_addr': '11::0',
+                       'end_addr': '11::100'}})
+
+
+class TestMalformedMessages(TemplateResponder, Ikev2Params):
+    """ malformed packet test """
+
+    def tearDown(self):
+        pass
+
+    def config_tc(self):
+        self.config_params()
+
+    def assert_counter(self, count, name, version='ip4'):
+        node_name = '/err/ikev2-%s/' % version + name
+        self.assertEqual(count, self.statistics.get_err_counter(node_name))
+
+    def create_ike_init_msg(self, length=None, payload=None):
+        msg = ikev2.IKEv2(length=length, init_SPI='\x11' * 8,
+                          flags='Initiator', exch_type='IKE_SA_INIT')
+        if payload is not None:
+            msg /= payload
+        return self.create_packet(self.pg0, msg, self.sa.sport,
+                                  self.sa.dport)
+
+    def verify_bad_packet_length(self):
+        ike_msg = self.create_ike_init_msg(length=0xdead)
+        self.send_and_assert_no_replies(self.pg0, ike_msg * self.pkt_count)
+        self.assert_counter(self.pkt_count, 'Bad packet length')
+
+    def verify_bad_sa_payload_length(self):
+        p = ikev2.IKEv2_payload_SA(length=0xdead)
+        ike_msg = self.create_ike_init_msg(payload=p)
+        self.send_and_assert_no_replies(self.pg0, ike_msg * self.pkt_count)
+        self.assert_counter(self.pkt_count, 'Malformed packet')
+
+    def test_responder(self):
+        self.pkt_count = 254
+        self.verify_bad_packet_length()
+        self.verify_bad_sa_payload_length()
 
 
 if __name__ == '__main__':