From 7e6ffba672875f1070348753890d023695d73be6 Mon Sep 17 00:00:00 2001 From: Atzm Watanabe Date: Tue, 9 Aug 2022 14:00:03 +0900 Subject: [PATCH] ikev2: do not accept rekey until old SA is deleted Type: fix Signed-off-by: Atzm Watanabe Change-Id: I11b6107492004a45104857dc2dae01b9a5a01e3b --- src/plugins/ikev2/ikev2.c | 49 ++++++++++++++++++++++++++++++------------ src/plugins/ikev2/ikev2_priv.h | 1 + test/test_ikev2.py | 48 +++++++++++++++++++++++++++++++++-------- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/plugins/ikev2/ikev2.c b/src/plugins/ikev2/ikev2.c index 20be89e1aaf..f8b9c6fb04b 100644 --- a/src/plugins/ikev2/ikev2.c +++ b/src/plugins/ikev2/ikev2.c @@ -1463,6 +1463,7 @@ ikev2_process_create_child_sa_req (vlib_main_t * vm, rekey = sa->rekey; if (vec_len (rekey) == 0) goto cleanup_and_exit; + rekey->notify_type = 0; rekey->protocol_id = proposal->protocol_id; rekey->i_proposal = ikev2_select_proposal (proposal, IKEV2_PROTOCOL_ESP); @@ -1491,22 +1492,34 @@ ikev2_process_create_child_sa_req (vlib_main_t * vm, goto cleanup_and_exit; } vec_add2 (sa->rekey, rekey, 1); + rekey->notify_type = 0; rekey->protocol_id = n->protocol_id; rekey->spi = n->spi; - rekey->i_proposal = proposal; - rekey->r_proposal = - ikev2_select_proposal (proposal, IKEV2_PROTOCOL_ESP); - /* update Ni */ - vec_reset_length (sa->i_nonce); - vec_add (sa->i_nonce, nonce, nonce_len); - /* generate new Nr */ - vec_validate (sa->r_nonce, nonce_len - 1); - RAND_bytes ((u8 *) sa->r_nonce, nonce_len); + if (sa->old_remote_id_present) + { + rekey->notify_type = IKEV2_NOTIFY_MSG_TEMPORARY_FAILURE; + vec_free (proposal); + vec_free (tsr); + vec_free (tsi); + } + else + { + rekey->i_proposal = proposal; + rekey->r_proposal = + ikev2_select_proposal (proposal, IKEV2_PROTOCOL_ESP); + /* update Ni */ + vec_reset_length (sa->i_nonce); + vec_add (sa->i_nonce, nonce, nonce_len); + /* generate new Nr */ + vec_validate (sa->r_nonce, nonce_len - 1); + RAND_bytes ((u8 *) sa->r_nonce, nonce_len); + } } else { /* create new child SA */ vec_add2 (sa->new_child, rekey, 1); + rekey->notify_type = 0; rekey->i_proposal = proposal; rekey->r_proposal = ikev2_select_proposal (proposal, IKEV2_PROTOCOL_ESP); @@ -2555,10 +2568,17 @@ ikev2_generate_message (vlib_buffer_t *b, ikev2_sa_t *sa, ike_header_t *ike, } else if (vec_len (sa->rekey) > 0) { - ikev2_payload_add_sa (chain, sa->rekey[0].r_proposal); - ikev2_payload_add_nonce (chain, sa->r_nonce); - ikev2_payload_add_ts (chain, sa->rekey[0].tsi, IKEV2_PAYLOAD_TSI); - ikev2_payload_add_ts (chain, sa->rekey[0].tsr, IKEV2_PAYLOAD_TSR); + if (sa->rekey[0].notify_type) + ikev2_payload_add_notify (chain, sa->rekey[0].notify_type, 0); + else + { + ikev2_payload_add_sa (chain, sa->rekey[0].r_proposal); + ikev2_payload_add_nonce (chain, sa->r_nonce); + ikev2_payload_add_ts (chain, sa->rekey[0].tsi, + IKEV2_PAYLOAD_TSI); + ikev2_payload_add_ts (chain, sa->rekey[0].tsr, + IKEV2_PAYLOAD_TSR); + } vec_del1 (sa->rekey, 0); } else if (vec_len (sa->new_child) > 0) @@ -3320,7 +3340,8 @@ ikev2_node_internal (vlib_main_t *vm, vlib_node_runtime_t *node, if (sa0->rekey) { - if (sa0->rekey[0].protocol_id != IKEV2_PROTOCOL_IKE) + if (!sa0->rekey[0].notify_type && + sa0->rekey[0].protocol_id != IKEV2_PROTOCOL_IKE) { if (sa0->childs) ikev2_sa_free_all_child_sa (&sa0->childs); diff --git a/src/plugins/ikev2/ikev2_priv.h b/src/plugins/ikev2/ikev2_priv.h index 0a6036891b2..379b68dbdfc 100644 --- a/src/plugins/ikev2/ikev2_priv.h +++ b/src/plugins/ikev2/ikev2_priv.h @@ -312,6 +312,7 @@ typedef struct typedef struct { + u16 notify_type; u8 protocol_id; u32 spi; u32 ispi; diff --git a/test/test_ikev2.py b/test/test_ikev2.py index ac77a4163a1..3c588719581 100644 --- a/test/test_ikev2.py +++ b/test/test_ikev2.py @@ -752,14 +752,15 @@ class IkePeer(VppTestCase): else: self.assertNotIn(e.IPSEC_API_SAD_FLAG_UDP_ENCAP, ipsec_sa.flags) - def verify_ipsec_sas(self, is_rekey=False): + def verify_ipsec_sas(self, is_rekey=False, sa_count=None): sas = self.vapi.ipsec_sa_dump() - if is_rekey: - # after rekey there is a short period of time in which old - # inbound SA is still present - sa_count = 3 - else: - sa_count = 2 + if sa_count is None: + if is_rekey: + # after rekey there is a short period of time in which old + # inbound SA is still present + sa_count = 3 + else: + sa_count = 2 self.assertEqual(len(sas), sa_count) if self.sa.is_initiator: if is_rekey: @@ -2078,12 +2079,15 @@ class TestResponderDpd(TestResponderPsk): class TestResponderRekey(TestResponderPsk): """test ikev2 responder - rekey""" - def rekey_from_initiator(self): + def send_rekey_from_initiator(self): packet = self.create_rekey_request() self.pg0.add_stream(packet) self.pg0.enable_capture() self.pg_start() capture = self.pg0.get_capture(1) + return capture + + def process_rekey_response(self, capture): ih = self.get_ike_header(capture[0]) plain = self.sa.hmac_and_decrypt(ih) sa = ikev2.IKEv2_payload_SA(plain) @@ -2094,7 +2098,7 @@ class TestResponderRekey(TestResponderPsk): def test_responder(self): super(TestResponderRekey, self).test_responder() - self.rekey_from_initiator() + self.process_rekey_response(self.send_rekey_from_initiator()) self.sa.calc_child_keys() self.verify_ike_sas() self.verify_ipsec_sas(is_rekey=True) @@ -2103,6 +2107,32 @@ class TestResponderRekey(TestResponderPsk): self.assertEqual(r[0].sa.stats.n_rekey_req, 1) +@tag_fixme_vpp_workers +class TestResponderRekeyRepeat(TestResponderRekey): + """test ikev2 responder - rekey repeat""" + + def test_responder(self): + super(TestResponderRekeyRepeat, self).test_responder() + # rekey request is not accepted until old IPsec SA is expired + capture = self.send_rekey_from_initiator() + ih = self.get_ike_header(capture[0]) + plain = self.sa.hmac_and_decrypt(ih) + notify = ikev2.IKEv2_payload_Notify(plain) + self.assertEqual(notify.type, 43) + self.assertEqual(len(self.vapi.ipsec_sa_dump()), 3) + # rekey request is accepted after old IPsec SA was expired + for _ in range(50): + if len(self.vapi.ipsec_sa_dump()) != 3: + break + time.sleep(0.2) + else: + self.fail("old IPsec SA not expired") + self.process_rekey_response(self.send_rekey_from_initiator()) + self.sa.calc_child_keys() + self.verify_ike_sas() + self.verify_ipsec_sas(sa_count=3) + + class TestResponderVrf(TestResponderPsk, Ikev2Params): """test ikev2 responder - non-default table id""" -- 2.16.6