ikev2: accept key exchange on CREATE_CHILD_SA 79/36879/4
authorAtzm Watanabe <atzmism@gmail.com>
Fri, 12 Aug 2022 05:29:31 +0000 (14:29 +0900)
committerBeno�t Ganne <bganne@cisco.com>
Thu, 18 Aug 2022 08:03:13 +0000 (08:03 +0000)
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 <atzmism@gmail.com>
Change-Id: I13cf6cf24359c11c3366757e585195bb7e999638

src/plugins/ikev2/ikev2.c
src/plugins/ikev2/ikev2_priv.h
test/test_ikev2.py

index 5c1aa58..c1b7efd 100644 (file)
@@ -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 */
index 379b68d..4ce1478 100644 (file)
@@ -313,6 +313,7 @@ typedef struct
 typedef struct
 {
   u16 notify_type;
+  u8 kex;
   u8 protocol_id;
   u32 spi;
   u32 ispi;
index 3c58871..ffddc79 100644 (file)
@@ -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"""