ikev2: accept rekey request for IKE SA

RFC 7296 describes the way to rekey IKE SAs: to rekey an IKE SA,
establish a new equivalent IKE SA with the peer to whom the old
IKE SA is shared using a CREATE_CHILD_SA within the existing IKE
SA.  An IKE SA so created inherits all of the original IKE SA's
Child SAs, and the new IKE SA is used for all control messages
needed to maintain those Child SAs.

Type: improvement
Signed-off-by: Atzm Watanabe <atzmism@gmail.com>
Change-Id: Icdf43b67c38bf183913a28a08a85236ba16343af
diff --git a/src/plugins/ikev2/ikev2.c b/src/plugins/ikev2/ikev2.c
index ad36068..4ee2f60 100644
--- a/src/plugins/ikev2/ikev2.c
+++ b/src/plugins/ikev2/ikev2.c
@@ -211,6 +211,8 @@
 	rv->proposal_num = proposal->proposal_num;
 	rv->protocol_id = proposal->protocol_id;
 	RAND_bytes ((u8 *) & rv->spi, sizeof (rv->spi));
+	if (rv->protocol_id != IKEV2_PROTOCOL_IKE)
+	  rv->spi &= 0xffffffff;
 	goto done;
       }
     else
@@ -480,11 +482,10 @@
 }
 
 static void
-ikev2_calc_keys (ikev2_sa_t * sa)
+ikev2_calc_keys_internal (ikev2_sa_t *sa, u8 *skeyseed)
 {
   u8 *tmp;
   /* calculate SKEYSEED = prf(Ni | Nr, g^ir) */
-  u8 *skeyseed = 0;
   u8 *s = 0;
   u16 integ_key_len = 0, salt_len = 0;
   ikev2_sa_transform_t *tr_encr, *tr_prf, *tr_integ;
@@ -502,7 +503,6 @@
 
   vec_append (s, sa->i_nonce);
   vec_append (s, sa->r_nonce);
-  skeyseed = ikev2_calc_prf (tr_prf, s, sa->dh_shared_key);
 
   /* Calculate S = Ni | Nr | SPIi | SPIr */
   u64 *spi;
@@ -520,7 +520,6 @@
     salt_len * 2;
 
   keymat = ikev2_calc_prfplus (tr_prf, skeyseed, s, len);
-  vec_free (skeyseed);
   vec_free (s);
 
   int pos = 0;
@@ -568,6 +567,41 @@
 }
 
 static void
+ikev2_calc_keys_rekey (ikev2_sa_t *sa_new, ikev2_sa_t *sa_old)
+{
+  u8 *s = 0, *skeyseed = 0;
+  ikev2_sa_transform_t *tr_prf =
+    ikev2_sa_get_td_for_type (sa_old->r_proposals, IKEV2_TRANSFORM_TYPE_PRF);
+
+  vec_append (s, sa_new->dh_shared_key);
+  vec_append (s, sa_new->i_nonce);
+  vec_append (s, sa_new->r_nonce);
+  skeyseed = ikev2_calc_prf (tr_prf, sa_old->sk_d, s);
+
+  ikev2_calc_keys_internal (sa_new, skeyseed);
+
+  vec_free (skeyseed);
+  vec_free (s);
+}
+
+static void
+ikev2_calc_keys (ikev2_sa_t *sa)
+{
+  u8 *s = 0, *skeyseed = 0;
+  ikev2_sa_transform_t *tr_prf =
+    ikev2_sa_get_td_for_type (sa->r_proposals, IKEV2_TRANSFORM_TYPE_PRF);
+
+  vec_append (s, sa->i_nonce);
+  vec_append (s, sa->r_nonce);
+  skeyseed = ikev2_calc_prf (tr_prf, s, sa->dh_shared_key);
+
+  ikev2_calc_keys_internal (sa, skeyseed);
+
+  vec_free (skeyseed);
+  vec_free (s);
+}
+
+static void
 ikev2_calc_child_keys (ikev2_sa_t *sa, ikev2_child_sa_t *child, u8 kex)
 {
   u8 *s = 0;
@@ -1410,6 +1444,110 @@
   return 1;
 }
 
+static void
+ikev2_complete_sa_rekey (ikev2_sa_t *sa_new, ikev2_sa_t *sa_old,
+			 ikev2_sa_rekey_t *sa_rekey)
+{
+  sa_new->del = 0;
+  sa_new->rekey = 0;
+  sa_new->new_child = 0;
+  sa_new->sa_rekey = 0;
+  sa_new->last_sa_init_req_packet_data = 0;
+  sa_new->last_sa_init_res_packet_data = 0;
+  sa_new->last_msg_id = ~0;
+  sa_new->last_res_packet_data = 0;
+  sa_new->last_init_msg_id = 0;
+  clib_memset (&sa_new->stats, 0, sizeof (sa_new->stats));
+
+  sa_new->ispi = sa_rekey->ispi;
+  sa_new->rspi = sa_rekey->rspi;
+  sa_new->i_nonce = sa_rekey->i_nonce;
+  sa_new->r_nonce = sa_rekey->r_nonce;
+  sa_new->dh_group = sa_rekey->dh_group;
+  sa_new->dh_shared_key = sa_rekey->dh_shared_key;
+  sa_new->dh_private_key = sa_rekey->dh_private_key;
+  sa_new->i_dh_data = sa_rekey->i_dh_data;
+  sa_new->r_dh_data = sa_rekey->r_dh_data;
+  sa_new->i_proposals = sa_rekey->i_proposals;
+  sa_new->r_proposals = sa_rekey->r_proposals;
+
+  sa_new->sk_d = 0;
+  sa_new->sk_ai = 0;
+  sa_new->sk_ar = 0;
+  sa_new->sk_ei = 0;
+  sa_new->sk_er = 0;
+  sa_new->sk_pi = 0;
+  sa_new->sk_pr = 0;
+  ikev2_calc_keys_rekey (sa_new, sa_old);
+
+  sa_new->i_auth.data = vec_dup (sa_old->i_auth.data);
+  sa_new->i_auth.key = sa_old->i_auth.key;
+  if (sa_new->i_auth.key)
+    EVP_PKEY_up_ref (sa_new->i_auth.key);
+
+  sa_new->r_auth.data = vec_dup (sa_old->r_auth.data);
+  sa_new->r_auth.key = sa_old->r_auth.key;
+  if (sa_new->r_auth.key)
+    EVP_PKEY_up_ref (sa_new->r_auth.key);
+
+  sa_new->i_id.data = vec_dup (sa_old->i_id.data);
+  sa_new->r_id.data = vec_dup (sa_old->r_id.data);
+
+  sa_old->is_tun_itf_set = 0;
+  sa_old->tun_itf = ~0;
+  sa_old->old_id_expiration = 0;
+  sa_old->current_remote_id_mask = 0;
+  sa_old->old_remote_id = 0;
+  sa_old->old_remote_id_present = 0;
+  sa_old->childs = 0;
+  sa_old->sw_if_index = ~0;
+}
+
+static void
+ikev2_process_sa_rekey (ikev2_sa_t *sa_new, ikev2_sa_t *sa_old,
+			ikev2_sa_rekey_t *sa_rekey)
+{
+  ikev2_sa_transform_t *tr;
+
+  if (ikev2_generate_sa_init_data (sa_new) != IKEV2_GENERATE_SA_INIT_OK)
+    {
+      sa_rekey->notify_type = IKEV2_NOTIFY_MSG_INVALID_KE_PAYLOAD;
+      return;
+    }
+
+  sa_new->r_proposals =
+    ikev2_select_proposal (sa_new->i_proposals, IKEV2_PROTOCOL_IKE);
+
+  tr = ikev2_sa_get_td_for_type (sa_new->r_proposals, IKEV2_TRANSFORM_TYPE_DH);
+  if (!tr || tr->dh_type != sa_new->dh_group)
+    {
+      sa_rekey->notify_type = IKEV2_NOTIFY_MSG_INVALID_KE_PAYLOAD;
+      return;
+    }
+
+  sa_rekey->notify_type = 0;
+  sa_rekey->ispi = sa_new->i_proposals[0].spi;
+  sa_rekey->rspi = sa_new->r_proposals[0].spi;
+  sa_rekey->i_nonce = sa_new->i_nonce;
+  sa_rekey->r_nonce = sa_new->r_nonce;
+  sa_rekey->dh_group = sa_new->dh_group;
+  sa_rekey->dh_shared_key = sa_new->dh_shared_key;
+  sa_rekey->dh_private_key = sa_new->dh_private_key;
+  sa_rekey->i_dh_data = sa_new->i_dh_data;
+  sa_rekey->r_dh_data = sa_new->r_dh_data;
+  sa_rekey->i_proposals = sa_new->i_proposals;
+  sa_rekey->r_proposals = sa_new->r_proposals;
+
+  sa_new->i_nonce = 0;
+  sa_new->r_nonce = 0;
+  sa_new->dh_shared_key = 0;
+  sa_new->dh_private_key = 0;
+  sa_new->i_dh_data = 0;
+  sa_new->r_dh_data = 0;
+  sa_new->i_proposals = 0;
+  sa_new->r_proposals = 0;
+}
+
 static int
 ikev2_process_create_child_sa_req (vlib_main_t * vm,
 				   ikev2_sa_t * sa, ike_header_t * ike,
@@ -1515,7 +1653,9 @@
       p += plen;
     }
 
-  if (!proposal || proposal->protocol_id != IKEV2_PROTOCOL_ESP || !nonce)
+  if (!proposal || !nonce ||
+      (proposal->protocol_id != IKEV2_PROTOCOL_ESP &&
+       proposal->protocol_id != IKEV2_PROTOCOL_IKE))
     goto cleanup_and_exit;
 
   if (sa->is_initiator)
@@ -1571,6 +1711,16 @@
 	      vec_free (tsi);
 	    }
 	}
+      else if (proposal[0].protocol_id == IKEV2_PROTOCOL_IKE)
+	{
+	  ikev2_sa_rekey_t *sa_rekey;
+	  if (tsi || tsr)
+	    goto cleanup_and_exit;
+	  sar.i_proposals = proposal;
+	  vec_add (sar.i_nonce, nonce, nonce_len);
+	  vec_add2 (sa->sa_rekey, sa_rekey, 1);
+	  ikev2_process_sa_rekey (&sar, sa, sa_rekey);
+	}
       else
 	{
 	  /* create new child SA */
@@ -2414,19 +2564,26 @@
 }
 
 static void
+ikev2_add_notify_payload (ikev2_sa_t *sa, ikev2_payload_chain_t *chain,
+			  u16 notify_type)
+{
+  if (notify_type == IKEV2_NOTIFY_MSG_INVALID_KE_PAYLOAD)
+    ikev2_add_invalid_ke_payload (sa, chain);
+  else
+    ikev2_payload_add_notify (chain, notify_type, 0);
+}
+
+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);
+      ikev2_add_notify_payload (sa, chain, rekey->notify_type);
       return;
     }
 
-  ikev2_payload_add_sa (chain, rekey->r_proposal);
+  ikev2_payload_add_sa (chain, rekey->r_proposal, 0);
   ikev2_payload_add_nonce (chain, sa->r_nonce);
   if (rekey->kex)
     ikev2_payload_add_ke (chain, sa->dh_group, sa->r_dh_data);
@@ -2482,7 +2639,7 @@
 	  ASSERT (udp);
 
 	  ike->rspi = clib_host_to_net_u64 (sa->rspi);
-	  ikev2_payload_add_sa (chain, sa->r_proposals);
+	  ikev2_payload_add_sa (chain, sa->r_proposals, 0);
 	  ikev2_payload_add_ke (chain, sa->dh_group, sa->r_dh_data);
 	  ikev2_payload_add_nonce (chain, sa->r_nonce);
 
@@ -2510,7 +2667,7 @@
 	{
 	  ikev2_payload_add_id (chain, &sa->r_id, IKEV2_PAYLOAD_IDR);
 	  ikev2_payload_add_auth (chain, &sa->r_auth);
-	  ikev2_payload_add_sa (chain, sa->childs[0].r_proposals);
+	  ikev2_payload_add_sa (chain, sa->childs[0].r_proposals, 0);
 	  ikev2_payload_add_ts (chain, sa->childs[0].tsi, IKEV2_PAYLOAD_TSI);
 	  ikev2_payload_add_ts (chain, sa->childs[0].tsr, IKEV2_PAYLOAD_TSR);
 	}
@@ -2555,7 +2712,7 @@
 	  if (sa->r_id.type != 0)
 	    ikev2_payload_add_id (chain, &sa->r_id, IKEV2_PAYLOAD_IDR);
 	  ikev2_payload_add_auth (chain, &sa->i_auth);
-	  ikev2_payload_add_sa (chain, sa->childs[0].i_proposals);
+	  ikev2_payload_add_sa (chain, sa->childs[0].i_proposals, 0);
 	  ikev2_payload_add_ts (chain, sa->childs[0].tsi, IKEV2_PAYLOAD_TSI);
 	  ikev2_payload_add_ts (chain, sa->childs[0].tsr, IKEV2_PAYLOAD_TSR);
 	  ikev2_payload_add_notify (chain, IKEV2_NOTIFY_MSG_INITIAL_CONTACT,
@@ -2632,7 +2789,7 @@
 	  notify.spi = sa->childs[0].i_proposals->spi;
 	  *(u32 *) data = clib_host_to_net_u32 (notify.spi);
 
-	  ikev2_payload_add_sa (chain, proposals);
+	  ikev2_payload_add_sa (chain, proposals, 0);
 	  ikev2_payload_add_nonce (chain, sa->i_nonce);
 	  ikev2_payload_add_ts (chain, sa->childs[0].tsi, IKEV2_PAYLOAD_TSI);
 	  ikev2_payload_add_ts (chain, sa->childs[0].tsr, IKEV2_PAYLOAD_TSR);
@@ -2651,6 +2808,19 @@
 	  ikev2_add_create_child_resp (sa, &sa->new_child[0], chain);
 	  vec_del1 (sa->new_child, 0);
 	}
+      else if (vec_len (sa->sa_rekey) > 0)
+	{
+	  if (sa->sa_rekey[0].notify_type)
+	    ikev2_add_notify_payload (sa, chain, sa->sa_rekey[0].notify_type);
+	  else
+	    {
+	      ikev2_payload_add_sa (chain, sa->sa_rekey[0].r_proposals, 1);
+	      ikev2_payload_add_nonce (chain, sa->sa_rekey[0].r_nonce);
+	      ikev2_payload_add_ke (chain, sa->sa_rekey[0].dh_group,
+				    sa->sa_rekey[0].r_dh_data);
+	    }
+	  vec_del1 (sa->sa_rekey, 0);
+	}
       else if (sa->unsupported_cp)
 	{
 	  u8 *data = vec_new (u8, 1);
@@ -3456,6 +3626,38 @@
 			  vm, node->node_index, IKEV2_ERROR_NO_BUFF_SPACE, 1);
 		    }
 		}
+	      else if (vec_len (sa0->sa_rekey) > 0)
+		{
+		  if (!sa0->sa_rekey[0].notify_type)
+		    {
+		      ikev2_sa_t *sar, *tmp = 0;
+		      pool_get (ptd->sas, tmp);
+		      sa0 = pool_elt_at_index (ptd->sas, p[0]);
+		      /* swap old/new SAs to keep index and inherit IPsec SA */
+		      clib_memcpy_fast (tmp, sa0, sizeof (*tmp));
+		      sar = sa0;
+		      sa0 = tmp;
+		      hash_set (ptd->sa_by_rspi, sa0->rspi, sa0 - ptd->sas);
+		      p = hash_get (ptd->sa_by_rspi, sa0->rspi);
+		      ikev2_complete_sa_rekey (sar, sa0, &sa0->sa_rekey[0]);
+		      hash_set (ptd->sa_by_rspi, sar->rspi, sar - ptd->sas);
+		    }
+		  if (ike_hdr_is_response (ike0))
+		    {
+		      vec_free (sa0->sa_rekey);
+		    }
+		  else
+		    {
+		      stats->n_rekey_req++;
+		      sa0->stats.n_rekey_req++;
+		      ike0->flags = IKEV2_HDR_FLAG_RESPONSE;
+		      slen =
+			ikev2_generate_message (b0, sa0, ike0, 0, udp0, stats);
+		      if (~0 == slen)
+			vlib_node_increment_counter (
+			  vm, node->node_index, IKEV2_ERROR_NO_BUFF_SPACE, 1);
+		    }
+		}
 	    }
 	}
       else
@@ -4526,7 +4728,7 @@
   proposals[0].protocol_id = IKEV2_PROTOCOL_IKE;
 
   /* Add and then cleanup proposal data */
-  ikev2_payload_add_sa (chain, proposals);
+  ikev2_payload_add_sa (chain, proposals, 0);
   ikev2_sa_free_proposal_vector (&proposals);
 
   sa.is_initiator = 1;
@@ -4560,6 +4762,7 @@
   sa.childs[0].i_proposals[0].protocol_id = IKEV2_PROTOCOL_ESP;
   RAND_bytes ((u8 *) & sa.childs[0].i_proposals[0].spi,
 	      sizeof (sa.childs[0].i_proposals[0].spi));
+  sa.childs[0].i_proposals[0].spi &= 0xffffffff;
 
   /* Add NAT detection notification messages (mandatory) */
   u8 *nat_detection_sha1 = ikev2_compute_nat_sha1 (
@@ -4807,6 +5010,7 @@
 
   /*need new ispi */
   RAND_bytes ((u8 *) & proposals[0].spi, sizeof (proposals[0].spi));
+  proposals[0].spi &= 0xffffffff;
   rekey->spi = proposals[0].spi;
   rekey->ispi = csa->i_proposals->spi;
   len = ikev2_generate_message (b0, sa, ike0, proposals, 0, 0);
diff --git a/src/plugins/ikev2/ikev2_payload.c b/src/plugins/ikev2/ikev2_payload.c
index 294864d..689c502 100644
--- a/src/plugins/ikev2/ikev2_payload.c
+++ b/src/plugins/ikev2/ikev2_payload.c
@@ -167,8 +167,8 @@
 }
 
 void
-ikev2_payload_add_sa (ikev2_payload_chain_t * c,
-		      ikev2_sa_proposal_t * proposals)
+ikev2_payload_add_sa (ikev2_payload_chain_t *c, ikev2_sa_proposal_t *proposals,
+		      u8 force_spi)
 {
   ike_payload_header_t *ph;
   ike_sa_proposal_data_t *prop;
@@ -184,7 +184,13 @@
 
   vec_foreach (p, proposals)
   {
-    int spi_size = (p->protocol_id == IKEV2_PROTOCOL_ESP) ? 4 : 0;
+    int spi_size = 0;
+
+    if (p->protocol_id == IKEV2_PROTOCOL_ESP)
+      spi_size = 4;
+    else if (force_spi && p->protocol_id == IKEV2_PROTOCOL_IKE)
+      spi_size = 8;
+
     pr_data = vec_new (u8, sizeof (ike_sa_proposal_data_t) + spi_size);
     prop = (ike_sa_proposal_data_t *) pr_data;
     prop->last_or_more = proposals - p + 1 < vec_len (proposals) ? 2 : 0;
@@ -193,8 +199,13 @@
     prop->spi_size = spi_size;
     prop->num_transforms = vec_len (p->transforms);
 
-    if (spi_size)
+    if (spi_size == 4)
       prop->spi[0] = clib_host_to_net_u32 (p->spi);
+    else if (spi_size == 8)
+      {
+	u64 s = clib_host_to_net_u64 (p->spi);
+	clib_memcpy_fast (prop->spi, &s, sizeof (s));
+      }
 
     vec_foreach (t, p->transforms)
     {
@@ -384,8 +395,9 @@
       sap = (ike_sa_proposal_data_t *) & ikep->payload[proposal_ptr];
       int i, transform_ptr;
 
-      /* IKE proposal should not have SPI */
-      if (sap->protocol_id == IKEV2_PROTOCOL_IKE && sap->spi_size != 0)
+      /* IKE proposal should have 8 bytes or no SPI */
+      if (sap->protocol_id == IKEV2_PROTOCOL_IKE && sap->spi_size != 0 &&
+	  sap->spi_size != 8)
 	goto data_corrupted;
 
       /* IKE proposal should not have SPI */
@@ -404,6 +416,12 @@
 	{
 	  proposal->spi = clib_net_to_host_u32 (sap->spi[0]);
 	}
+      else if (sap->spi_size == 8)
+	{
+	  u64 s;
+	  clib_memcpy_fast (&s, &sap->spi[0], sizeof (s));
+	  proposal->spi = clib_net_to_host_u64 (s);
+	}
 
       for (i = 0; i < sap->num_transforms; i++)
 	{
diff --git a/src/plugins/ikev2/ikev2_priv.h b/src/plugins/ikev2/ikev2_priv.h
index dca2fe8..a11538f 100644
--- a/src/plugins/ikev2/ikev2_priv.h
+++ b/src/plugins/ikev2/ikev2_priv.h
@@ -243,7 +243,7 @@
 {
   u8 proposal_num;
   ikev2_protocol_id_t protocol_id:8;
-  u32 spi;
+  u64 spi;
   ikev2_sa_transform_t *transforms;
 } ikev2_sa_proposal_t;
 
@@ -330,6 +330,22 @@
 
 typedef struct
 {
+  u16 notify_type;
+  u16 dh_group;
+  u64 ispi;
+  u64 rspi;
+  u8 *i_nonce;
+  u8 *r_nonce;
+  u8 *dh_shared_key;
+  u8 *dh_private_key;
+  u8 *i_dh_data;
+  u8 *r_dh_data;
+  ikev2_sa_proposal_t *i_proposals;
+  ikev2_sa_proposal_t *r_proposals;
+} ikev2_sa_rekey_t;
+
+typedef struct
+{
   u16 msg_type;
   u8 protocol_id;
   u32 spi;
@@ -432,6 +448,9 @@
 
   ikev2_rekey_t *new_child;
 
+  /* pending sa rekeyings */
+  ikev2_sa_rekey_t *sa_rekey;
+
   /* packet data */
   u8 *last_sa_init_req_packet_data;
   u8 *last_sa_init_res_packet_data;
@@ -601,8 +620,8 @@
 			       u8 * data);
 void ikev2_payload_add_notify_2 (ikev2_payload_chain_t * c, u16 msg_type,
 				 u8 * data, ikev2_notify_t * notify);
-void ikev2_payload_add_sa (ikev2_payload_chain_t * c,
-			   ikev2_sa_proposal_t * proposals);
+void ikev2_payload_add_sa (ikev2_payload_chain_t *c,
+			   ikev2_sa_proposal_t *proposals, u8 force_spi);
 void ikev2_payload_add_ke (ikev2_payload_chain_t * c, u16 dh_group,
 			   u8 * dh_data);
 void ikev2_payload_add_nonce (ikev2_payload_chain_t * c, u8 * nonce);
diff --git a/test/test_ikev2.py b/test/test_ikev2.py
index 30ee2b9..d2b4c69 100644
--- a/test/test_ikev2.py
+++ b/test/test_ikev2.py
@@ -361,14 +361,21 @@
         h.update(data)
         return h.finalize()
 
-    def calc_keys(self):
+    def calc_keys(self, sk_d=None):
         prf = self.ike_prf_alg.mod()
-        # SKEYSEED = prf(Ni | Nr, g^ir)
-        s = self.i_nonce + self.r_nonce
-        self.skeyseed = self.calc_prf(prf, s, self.dh_shared_secret)
+        if sk_d is None:
+            # SKEYSEED = prf(Ni | Nr, g^ir)
+            self.skeyseed = self.calc_prf(
+                prf, self.i_nonce + self.r_nonce, self.dh_shared_secret
+            )
+        else:
+            # SKEYSEED = prf(SK_d (old), g^ir (new) | Ni | Nr)
+            self.skeyseed = self.calc_prf(
+                prf, sk_d, self.dh_shared_secret + self.i_nonce + self.r_nonce
+            )
 
         # calculate S = Ni | Nr | SPIi SPIr
-        s = s + self.ispi + self.rspi
+        s = self.i_nonce + self.r_nonce + self.ispi + self.rspi
 
         prf_key_trunc = self.ike_prf_alg.trunc_len
         encr_key_len = self.ike_crypto_key_len
@@ -585,6 +592,45 @@
         digest.update(data)
         return digest.finalize()
 
+    def clone(self, test, **kwargs):
+        if "spi" not in kwargs:
+            kwargs["spi"] = self.ispi if self.is_initiator else self.rspi
+        if "nonce" not in kwargs:
+            kwargs["nonce"] = self.i_nonce if self.is_initiator else self.r_nonce
+        if self.child_sas:
+            if "local_ts" not in kwargs:
+                kwargs["local_ts"] = self.child_sas[0].local_ts
+            if "remote_ts" not in kwargs:
+                kwargs["remote_ts"] = self.child_sas[0].remote_ts
+        sa = type(self)(
+            test,
+            is_initiator=self.is_initiator,
+            i_id=self.i_id,
+            r_id=self.r_id,
+            id_type=self.id_type,
+            auth_data=self.auth_data,
+            auth_method=self.auth_method,
+            priv_key=self.priv_key,
+            i_natt=self.i_natt,
+            r_natt=self.r_natt,
+            udp_encap=self.udp_encap,
+            **kwargs,
+        )
+        if sa.is_initiator:
+            sa.set_ike_props(
+                crypto=self.ike_crypto,
+                crypto_key_len=self.ike_crypto_key_len,
+                integ=self.ike_integ,
+                prf=self.ike_prf,
+                dh=self.ike_dh,
+            )
+            sa.set_esp_props(
+                crypto=self.esp_crypto,
+                crypto_key_len=self.esp_crypto_key_len,
+                integ=self.esp_integ,
+            )
+        return sa
+
 
 @unittest.skipIf("ikev2" in config.excluded_plugins, "Exclude IKEv2 plugin tests")
 class IkePeer(VppTestCase):
@@ -650,6 +696,20 @@
             self.pg0, ike_msg, self.sa.sport, self.sa.dport, self.sa.natt, self.ip6
         )
 
+    def create_sa_rekey_request(self, **kwargs):
+        sa = self.generate_sa_init_payload(**kwargs)
+        header = ikev2.IKEv2(
+            init_SPI=self.sa.ispi,
+            resp_SPI=self.sa.rspi,
+            id=self.sa.new_msg_id(),
+            flags="Initiator",
+            exch_type="CREATE_CHILD_SA",
+        )
+        ike_msg = self.encrypt_ike_msg(header, sa, "SA")
+        return self.create_packet(
+            self.pg0, ike_msg, self.sa.sport, self.sa.dport, self.sa.natt, self.ip6
+        )
+
     def create_empty_request(self):
         header = ikev2.IKEv2(
             init_SPI=self.sa.ispi,
@@ -830,10 +890,15 @@
         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):
+    def verify_ike_sas(self, is_rekey=False):
         r = self.vapi.ikev2_sa_dump()
-        self.assertEqual(len(r), 1)
-        sa = r[0].sa
+        if is_rekey:
+            sa_count = 2
+            sa = r[1].sa
+        else:
+            sa_count = 1
+            sa = r[0].sa
+        self.assertEqual(len(r), sa_count)
         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:
@@ -865,7 +930,16 @@
         self.assertEqual(bytes(sa.i_id.data, "ascii"), self.sa.i_id)
         self.assertEqual(bytes(sa.r_id.data, "ascii"), self.idr)
 
+        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)
+
         r = self.vapi.ikev2_child_sa_dump(sa_index=sa.sa_index)
+        if is_rekey:
+            self.assertEqual(len(r), 0)
+            return
+
         self.assertEqual(len(r), 1)
         csa = r[0].child_sa
         self.assertEqual(csa.sa_index, sa.sa_index)
@@ -894,11 +968,6 @@
         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)
@@ -1281,7 +1350,9 @@
         capture = self.pg0.get_capture(1)
         self.verify_del_sa(capture[0])
 
-    def send_sa_init_req(self):
+    def generate_sa_init_payload(
+        self, spi=None, dh_pub_key=None, nonce=None, next_payload=None
+    ):
         tr_attr = self.sa.ike_crypto_attr()
         trans = (
             ikev2.IKEv2_payload_Transform(
@@ -1301,23 +1372,36 @@
             )
         )
 
+        if spi is None:
+            pargs = {}
+        else:
+            pargs = {"SPI": spi, "SPIsize": len(spi)}
         props = ikev2.IKEv2_payload_Proposal(
-            proposal=1, proto="IKEv2", trans_nb=4, trans=trans
+            proposal=1,
+            proto="IKEv2",
+            trans_nb=4,
+            trans=trans,
+            **pargs,
         )
 
-        next_payload = None if self.ip6 else "Notify"
-
-        self.sa.init_req_packet = (
-            ikev2.IKEv2(
-                init_SPI=self.sa.ispi, flags="Initiator", exch_type="IKE_SA_INIT"
-            )
-            / ikev2.IKEv2_payload_SA(next_payload="KE", prop=props)
+        return (
+            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
+                next_payload="Nonce",
+                group=self.sa.ike_dh,
+                load=self.sa.my_dh_pub_key if dh_pub_key is None else dh_pub_key,
             )
-            / ikev2.IKEv2_payload_Nonce(next_payload=next_payload, load=self.sa.i_nonce)
+            / ikev2.IKEv2_payload_Nonce(
+                next_payload=next_payload,
+                load=self.sa.i_nonce if nonce is None else nonce,
+            )
         )
 
+    def send_sa_init_req(self):
+        self.sa.init_req_packet = ikev2.IKEv2(
+            init_SPI=self.sa.ispi, flags="Initiator", exch_type="IKE_SA_INIT"
+        ) / self.generate_sa_init_payload(next_payload=None if self.ip6 else "Notify")
+
         if not self.ip6:
             if self.sa.i_natt:
                 src_address = b"\x0a\x0a\x0a\x01"
@@ -2186,6 +2270,50 @@
     WITH_KEX = True
 
 
+@tag_fixme_vpp_workers
+class TestResponderRekeySA(TestResponderPsk):
+    """test ikev2 responder - rekey IKE SA"""
+
+    def send_rekey_from_initiator(self, newsa):
+        packet = self.create_sa_rekey_request(
+            spi=newsa.ispi,
+            dh_pub_key=newsa.my_dh_pub_key,
+            nonce=newsa.i_nonce,
+        )
+        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, newsa, capture):
+        ih = self.get_ike_header(capture[0])
+        plain = self.sa.hmac_and_decrypt(ih)
+        sa = ikev2.IKEv2_payload_SA(plain)
+        prop = sa[ikev2.IKEv2_payload_Proposal]
+        newsa.rspi = prop.SPI
+        newsa.r_nonce = sa[ikev2.IKEv2_payload_Nonce].load
+        newsa.r_dh_data = sa[ikev2.IKEv2_payload_KE].load
+        newsa.complete_dh_data()
+        newsa.calc_keys(sk_d=self.sa.sk_d)
+        newsa.child_sas = self.sa.child_sas
+        self.sa.child_sas = []
+
+    def test_responder(self):
+        super(TestResponderRekeySA, self).test_responder()
+        newsa = self.sa.clone(self, spi=os.urandom(8))
+        newsa.generate_dh_data()
+        capture = self.send_rekey_from_initiator(newsa)
+        self.process_rekey_response(newsa, capture)
+        self.verify_ike_sas(is_rekey=True)
+        self.assert_counter(1, "rekey_req", "ip4")
+        r = self.vapi.ikev2_sa_dump()
+        self.assertEqual(r[1].sa.stats.n_rekey_req, 1)
+        self.initiate_del_sa_from_initiator()
+        self.sa = newsa
+        self.verify_ike_sas()
+
+
 @tag_fixme_ubuntu2204
 @tag_fixme_debian11
 class TestResponderVrf(TestResponderPsk, Ikev2Params):