From: Atzm Watanabe Date: Fri, 12 Aug 2022 05:29:31 +0000 (+0900) Subject: ikev2: accept key exchange on CREATE_CHILD_SA X-Git-Tag: v23.02-rc0~92 X-Git-Url: https://gerrit.fd.io/r/gitweb?a=commitdiff_plain;h=c65921f7744a0da09ede876b6588628e3a188529;p=vpp.git ikev2: accept key exchange on CREATE_CHILD_SA In RFC 7296, CREATE_CHILD_SA Exchange may contain the KE payload to enable stronger guarantees of forward secrecy. When the KEi payload is included in the CREATE_CHILD_SA request, responder should reply with the KEr payload and complete the key exchange, in accordance with the RFC. Type: improvement Signed-off-by: Atzm Watanabe Change-Id: I13cf6cf24359c11c3366757e585195bb7e999638 --- diff --git a/src/plugins/ikev2/ikev2.c b/src/plugins/ikev2/ikev2.c index 5c1aa58814e..c1b7efdc98e 100644 --- a/src/plugins/ikev2/ikev2.c +++ b/src/plugins/ikev2/ikev2.c @@ -568,7 +568,7 @@ ikev2_calc_keys (ikev2_sa_t * sa) } static void -ikev2_calc_child_keys (ikev2_sa_t * sa, ikev2_child_sa_t * child) +ikev2_calc_child_keys (ikev2_sa_t *sa, ikev2_child_sa_t *child, u8 kex) { u8 *s = 0; u16 integ_key_len = 0; @@ -587,6 +587,8 @@ ikev2_calc_child_keys (ikev2_sa_t * sa, ikev2_child_sa_t * child) else salt_len = sizeof (u32); + if (kex) + vec_append (s, sa->dh_shared_key); vec_append (s, sa->i_nonce); vec_append (s, sa->r_nonce); /* calculate PRFplus */ @@ -1359,6 +1361,55 @@ ikev2_process_informational_req (vlib_main_t * vm, return 1; } +static int +ikev2_process_create_child_sa_rekey (ikev2_sa_t *sa, ikev2_sa_t *sar, + ikev2_rekey_t *rekey, + ikev2_sa_proposal_t *proposal, + ikev2_ts_t *tsi, ikev2_ts_t *tsr, + const u8 *nonce, int nonce_len) +{ + ikev2_sa_transform_t *tr; + + rekey->i_proposal = proposal; + rekey->r_proposal = ikev2_select_proposal (proposal, IKEV2_PROTOCOL_ESP); + + if (sar->dh_group) + { + tr = + ikev2_sa_get_td_for_type (rekey->r_proposal, IKEV2_TRANSFORM_TYPE_DH); + + if (!tr || tr->dh_type != sar->dh_group) + { + rekey->notify_type = IKEV2_NOTIFY_MSG_INVALID_KE_PAYLOAD; + ikev2_sa_free_proposal_vector (&rekey->r_proposal); + return 0; + } + + vec_free (sa->dh_shared_key); + vec_free (sa->dh_private_key); + vec_free (sa->i_dh_data); + vec_free (sa->r_dh_data); + + sa->dh_group = sar->dh_group; + sa->i_dh_data = sar->i_dh_data; + sar->i_dh_data = 0; + + ikev2_generate_dh (sa, tr); + rekey->kex = 1; + } + + vec_reset_length (sa->i_nonce); + vec_add (sa->i_nonce, nonce, nonce_len); + + vec_validate (sa->r_nonce, nonce_len - 1); + RAND_bytes ((u8 *) sa->r_nonce, nonce_len); + + rekey->tsi = tsi; + rekey->tsr = tsr; + + return 1; +} + static int ikev2_process_create_child_sa_req (vlib_main_t * vm, ikev2_sa_t * sa, ike_header_t * ike, @@ -1378,6 +1429,9 @@ ikev2_process_create_child_sa_req (vlib_main_t * vm, u16 plen; const u8 *nonce = 0; int nonce_len = 0; + ikev2_sa_t sar; + + clib_memset (&sar, 0, sizeof (sar)); if (sa->is_initiator) src = ip_addr_v4 (&sa->raddr).as_u32; @@ -1407,6 +1461,12 @@ ikev2_process_create_child_sa_req (vlib_main_t * vm, { proposal = ikev2_parse_sa_payload (ikep, current_length); } + else if (payload == IKEV2_PAYLOAD_KE) + { + if (!ikev2_parse_ke_payload (ikep, current_length, &sar, + &sar.i_dh_data)) + goto cleanup_and_exit; + } else if (payload == IKEV2_PAYLOAD_NOTIFY) { ikev2_notify_t *n0; @@ -1493,6 +1553,7 @@ ikev2_process_create_child_sa_req (vlib_main_t * vm, } vec_add2 (sa->rekey, rekey, 1); rekey->notify_type = 0; + rekey->kex = 0; rekey->protocol_id = n->protocol_id; rekey->spi = n->spi; if (sa->old_remote_id_present) @@ -1502,17 +1563,12 @@ ikev2_process_create_child_sa_req (vlib_main_t * vm, vec_free (tsr); vec_free (tsi); } - else + else if (!ikev2_process_create_child_sa_rekey ( + sa, &sar, rekey, proposal, tsi, tsr, nonce, nonce_len)) { - 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); + vec_free (proposal); + vec_free (tsr); + vec_free (tsi); } } else @@ -1520,20 +1576,18 @@ ikev2_process_create_child_sa_req (vlib_main_t * vm, /* 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); - /* 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); + rekey->kex = 0; + if (!ikev2_process_create_child_sa_rekey ( + sa, &sar, rekey, proposal, tsi, tsr, nonce, nonce_len)) + { + vec_free (proposal); + vec_free (tsr); + vec_free (tsi); + } } - rekey->tsi = tsi; - rekey->tsr = tsr; } vec_free (n); + ikev2_sa_free_all_vec (&sar); return 1; cleanup_and_exit: @@ -1541,6 +1595,7 @@ cleanup_and_exit: vec_free (proposal); vec_free (tsr); vec_free (tsi); + ikev2_sa_free_all_vec (&sar); return 0; } @@ -2013,10 +2068,9 @@ err0: } static int -ikev2_create_tunnel_interface (vlib_main_t * vm, - ikev2_sa_t * sa, - ikev2_child_sa_t * child, u32 sa_index, - u32 child_index, u8 is_rekey) +ikev2_create_tunnel_interface (vlib_main_t *vm, ikev2_sa_t *sa, + ikev2_child_sa_t *child, u32 sa_index, + u32 child_index, u8 is_rekey, u8 kex) { u32 thread_index = vlib_get_thread_index (); ikev2_main_t *km = &ikev2_main; @@ -2159,7 +2213,7 @@ ikev2_create_tunnel_interface (vlib_main_t * vm, } a.integ_type = integ_type; - ikev2_calc_child_keys (sa, child); + ikev2_calc_child_keys (sa, child, kex); if (sa->is_initiator) { @@ -2346,6 +2400,40 @@ ikev2_delete_tunnel_interface (vnet_main_t * vnm, ikev2_sa_t * sa, return 0; } +static void +ikev2_add_invalid_ke_payload (ikev2_sa_t *sa, ikev2_payload_chain_t *chain) +{ + u8 *data = vec_new (u8, 2); + ikev2_sa_transform_t *tr_dh = + ikev2_sa_get_td_for_type (sa->r_proposals, IKEV2_TRANSFORM_TYPE_DH); + ASSERT (tr_dh && tr_dh->dh_type); + data[0] = (tr_dh->dh_type >> 8) & 0xff; + data[1] = (tr_dh->dh_type) & 0xff; + ikev2_payload_add_notify (chain, IKEV2_NOTIFY_MSG_INVALID_KE_PAYLOAD, data); + vec_free (data); +} + +static void +ikev2_add_create_child_resp (ikev2_sa_t *sa, ikev2_rekey_t *rekey, + ikev2_payload_chain_t *chain) +{ + if (rekey->notify_type) + { + if (rekey->notify_type == IKEV2_NOTIFY_MSG_INVALID_KE_PAYLOAD) + ikev2_add_invalid_ke_payload (sa, chain); + else + ikev2_payload_add_notify (chain, rekey->notify_type, 0); + return; + } + + ikev2_payload_add_sa (chain, rekey->r_proposal); + ikev2_payload_add_nonce (chain, sa->r_nonce); + if (rekey->kex) + ikev2_payload_add_ke (chain, sa->dh_group, sa->r_dh_data); + ikev2_payload_add_ts (chain, rekey->tsi, IKEV2_PAYLOAD_TSI); + ikev2_payload_add_ts (chain, rekey->tsr, IKEV2_PAYLOAD_TSR); +} + static u32 ikev2_generate_message (vlib_buffer_t *b, ikev2_sa_t *sa, ike_header_t *ike, void *user, udp_header_t *udp, ikev2_stats_t *stats) @@ -2376,20 +2464,7 @@ ikev2_generate_message (vlib_buffer_t *b, ikev2_sa_t *sa, ike_header_t *ike, } else if (sa->dh_group == IKEV2_TRANSFORM_DH_TYPE_NONE) { - u8 *data = vec_new (u8, 2); - ikev2_sa_transform_t *tr_dh; - tr_dh = - ikev2_sa_get_td_for_type (sa->r_proposals, - IKEV2_TRANSFORM_TYPE_DH); - ASSERT (tr_dh && tr_dh->dh_type); - - data[0] = (tr_dh->dh_type >> 8) & 0xff; - data[1] = (tr_dh->dh_type) & 0xff; - - ikev2_payload_add_notify (chain, - IKEV2_NOTIFY_MSG_INVALID_KE_PAYLOAD, - data); - vec_free (data); + ikev2_add_invalid_ke_payload (sa, chain); ikev2_set_state (sa, IKEV2_STATE_NOTIFY_AND_DELETE); } else if (sa->state == IKEV2_STATE_NOTIFY_AND_DELETE) @@ -2568,27 +2643,12 @@ ikev2_generate_message (vlib_buffer_t *b, ikev2_sa_t *sa, ike_header_t *ike, } else if (vec_len (sa->rekey) > 0) { - 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); - } + ikev2_add_create_child_resp (sa, &sa->rekey[0], chain); vec_del1 (sa->rekey, 0); } else if (vec_len (sa->new_child) > 0) { - ikev2_payload_add_sa (chain, sa->new_child[0].r_proposal); - ikev2_payload_add_nonce (chain, sa->r_nonce); - ikev2_payload_add_ts (chain, sa->new_child[0].tsi, - IKEV2_PAYLOAD_TSI); - ikev2_payload_add_ts (chain, sa->new_child[0].tsr, - IKEV2_PAYLOAD_TSR); + ikev2_add_create_child_resp (sa, &sa->new_child[0], chain); vec_del1 (sa->new_child, 0); } else if (sa->unsupported_cp) @@ -3211,9 +3271,8 @@ ikev2_node_internal (vlib_main_t *vm, vlib_node_runtime_t *node, ikev2_initial_contact_cleanup (ptd, sa0); ikev2_sa_match_ts (sa0); if (sa0->state != IKEV2_STATE_TS_UNACCEPTABLE) - ikev2_create_tunnel_interface (vm, sa0, - &sa0->childs[0], - p[0], 0, 0); + ikev2_create_tunnel_interface (vm, sa0, &sa0->childs[0], + p[0], 0, 0, 0); } if (sa0->is_initiator) @@ -3353,7 +3412,8 @@ ikev2_node_internal (vlib_main_t *vm, vlib_node_runtime_t *node, child->tsi = sa0->rekey[0].tsi; child->tsr = sa0->rekey[0].tsr; ikev2_create_tunnel_interface (vm, sa0, child, p[0], - child - sa0->childs, 1); + child - sa0->childs, 1, + sa0->rekey[0].kex); } if (ike_hdr_is_response (ike0)) { @@ -3382,7 +3442,8 @@ ikev2_node_internal (vlib_main_t *vm, vlib_node_runtime_t *node, c->tsi = sa0->new_child[0].tsi; c->tsr = sa0->new_child[0].tsr; ikev2_create_tunnel_interface (vm, sa0, c, p[0], - c - sa0->childs, 0); + c - sa0->childs, 0, + sa0->new_child[0].kex); if (ike_hdr_is_request (ike0)) { ike0->flags = IKEV2_HDR_FLAG_RESPONSE; @@ -4736,6 +4797,7 @@ ikev2_rekey_child_sa_internal (vlib_main_t * vm, ikev2_sa_t * sa, ikev2_rekey_t *rekey; vec_reset_length (sa->rekey); vec_add2 (sa->rekey, rekey, 1); + rekey->kex = 0; ikev2_sa_proposal_t *proposals = vec_dup (csa->i_proposals); /*need new ispi */ diff --git a/src/plugins/ikev2/ikev2_priv.h b/src/plugins/ikev2/ikev2_priv.h index 379b68dbdfc..4ce147890d5 100644 --- a/src/plugins/ikev2/ikev2_priv.h +++ b/src/plugins/ikev2/ikev2_priv.h @@ -313,6 +313,7 @@ typedef struct typedef struct { u16 notify_type; + u8 kex; u8 protocol_id; u32 spi; u32 ispi; diff --git a/test/test_ikev2.py b/test/test_ikev2.py index 3c588719581..ffddc79dbcf 100644 --- a/test/test_ikev2.py +++ b/test/test_ikev2.py @@ -296,9 +296,11 @@ class IKEv2SA(object): def complete_dh_data(self): self.dh_shared_secret = self.compute_secret() - def calc_child_keys(self): + def calc_child_keys(self, kex=False): prf = self.ike_prf_alg.mod() s = self.i_nonce + self.r_nonce + if kex: + s = self.dh_shared_secret + s c = self.child_sas[0] encr_key_len = self.esp_crypto_key_len @@ -625,8 +627,8 @@ class IkePeer(VppTestCase): node_name = "/err/ikev2-%s/" % version + name self.assertEqual(count, self.statistics.get_err_counter(node_name)) - def create_rekey_request(self): - sa, first_payload = self.generate_auth_payload(is_rekey=True) + def create_rekey_request(self, kex=False): + sa, first_payload = self.generate_auth_payload(is_rekey=True, kex=kex) header = ikev2.IKEv2( init_SPI=self.sa.ispi, resp_SPI=self.sa.rspi, @@ -1345,9 +1347,10 @@ class TemplateResponder(IkePeer): capture = self.pg0.get_capture(1) self.verify_sa_init(capture[0]) - def generate_auth_payload(self, last_payload=None, is_rekey=False): + def generate_auth_payload(self, last_payload=None, is_rekey=False, kex=False): tr_attr = self.sa.esp_crypto_attr() last_payload = last_payload or "Notify" + trans_nb = 4 trans = ( ikev2.IKEv2_payload_Transform( transform_type="Encryption", @@ -1366,9 +1369,20 @@ class TemplateResponder(IkePeer): ) ) + if kex: + trans_nb += 1 + trans /= ikev2.IKEv2_payload_Transform( + transform_type="GroupDesc", transform_id=self.sa.ike_dh + ) + c = self.sa.child_sas[0] props = ikev2.IKEv2_payload_Proposal( - proposal=1, proto="ESP", SPIsize=4, SPI=c.ispi, trans_nb=4, trans=trans + proposal=1, + proto="ESP", + SPIsize=4, + SPI=c.ispi, + trans_nb=trans_nb, + trans=trans, ) tsi, tsr = self.sa.generate_ts(self.p.ts_is_ip4) @@ -1389,8 +1403,18 @@ class TemplateResponder(IkePeer): if is_rekey: first_payload = "Nonce" + if kex: + head = ikev2.IKEv2_payload_Nonce( + load=self.sa.i_nonce, next_payload="KE" + ) / ikev2.IKEv2_payload_KE( + group=self.sa.ike_dh, load=self.sa.my_dh_pub_key, next_payload="SA" + ) + else: + head = ikev2.IKEv2_payload_Nonce( + load=self.sa.i_nonce, next_payload="SA" + ) plain = ( - ikev2.IKEv2_payload_Nonce(load=self.sa.i_nonce, next_payload="SA") + head / plain / ikev2.IKEv2_payload_Notify( type="REKEY_SA", @@ -2079,8 +2103,12 @@ class TestResponderDpd(TestResponderPsk): class TestResponderRekey(TestResponderPsk): """test ikev2 responder - rekey""" + WITH_KEX = False + def send_rekey_from_initiator(self): - packet = self.create_rekey_request() + if self.WITH_KEX: + self.sa.generate_dh_data() + packet = self.create_rekey_request(kex=self.WITH_KEX) self.pg0.add_stream(packet) self.pg0.enable_capture() self.pg_start() @@ -2095,11 +2123,14 @@ class TestResponderRekey(TestResponderPsk): self.sa.r_nonce = sa[ikev2.IKEv2_payload_Nonce].load # update new responder SPI self.sa.child_sas[0].rspi = prop.SPI + if self.WITH_KEX: + self.sa.r_dh_data = sa[ikev2.IKEv2_payload_KE].load + self.sa.complete_dh_data() + self.sa.calc_child_keys(kex=self.WITH_KEX) def test_responder(self): super(TestResponderRekey, self).test_responder() 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) self.assert_counter(1, "rekey_req", "ip4") @@ -2128,11 +2159,24 @@ class TestResponderRekeyRepeat(TestResponderRekey): 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) +@tag_fixme_vpp_workers +class TestResponderRekeyKEX(TestResponderRekey): + """test ikev2 responder - rekey with key exchange""" + + WITH_KEX = True + + +@tag_fixme_vpp_workers +class TestResponderRekeyRepeatKEX(TestResponderRekeyRepeat): + """test ikev2 responder - rekey repeat with key exchange""" + + WITH_KEX = True + + class TestResponderVrf(TestResponderPsk, Ikev2Params): """test ikev2 responder - non-default table id"""