]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
child-create: Add support for multiple key exchanges
authorTobias Brunner <tobias@strongswan.org>
Thu, 25 Jun 2020 08:26:38 +0000 (10:26 +0200)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Mon, 1 Apr 2024 10:26:36 +0000 (12:26 +0200)
It also changes that payloads are built before installing the CHILD_SA
on the responder, that is, the KE payload is generated before keys are
derived, so that key_exchange_t::get_public_key() is called before
get_shared_secret(), or its internal equivalent, which could be relevant
for KE implementations that want to ensure that the key can't be
accessed again after the key derivation.

src/charon-tkm/src/tkm/tkm_kernel_ipsec.c
src/charon-tkm/src/tkm/tkm_keymat.c
src/charon-tkm/src/tkm/tkm_types.h
src/charon-tkm/tests/keymat_tests.c
src/libcharon/sa/ikev2/tasks/child_create.c

index 1e680258d84969d7c856f75d4b707186e2435c59..d5e412db78762a4c0532c67eacc675989d4cc36a 100644 (file)
@@ -107,29 +107,27 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
        }
        esa = *(esa_info_t *)(data->enc_key.ptr);
 
-       /* only handle the case where we have both distinct ESP spi's available */
-       if (esa.spi_r == id->spi)
+       /* only handle the case where we have both distinct ESP SPI's available,
+        * which is always the outbound SA */
+       if (esa.spi_l == id->spi)
        {
                chunk_free(&esa.nonce_i);
                chunk_free(&esa.nonce_r);
                return SUCCESS;
        }
 
+       spi_loc = esa.spi_l;
+       spi_rem = id->spi;
+       local = id->src;
+       peer = id->dst;
+
        if (data->initiator)
        {
-               spi_loc = id->spi;
-               spi_rem = esa.spi_r;
-               local = id->dst;
-               peer = id->src;
                nonce_loc = &esa.nonce_i;
                nonce_rem = &esa.nonce_r;
        }
        else
        {
-               spi_loc = esa.spi_r;
-               spi_rem = id->spi;
-               local = id->src;
-               peer = id->dst;
                nonce_loc = &esa.nonce_r;
                nonce_rem = &esa.nonce_i;
        }
index d8b800f05a7fbe7bf0c510398b3c8c4cc3470d06..a435e29de0529f6e682456157a7e5b3d86086387 100644 (file)
@@ -221,7 +221,7 @@ METHOD(keymat_v2_t, derive_child_keys, bool,
 
        INIT(esa_info_i,
                 .isa_id = this->isa_ctx_id,
-                .spi_r = proposal->get_spi(proposal),
+                .spi_l = proposal->get_spi(proposal),
                 .nonce_i = chunk_clone(nonce_i),
                 .nonce_r = chunk_clone(nonce_r),
                 .is_encr_r = FALSE,
@@ -230,15 +230,15 @@ METHOD(keymat_v2_t, derive_child_keys, bool,
 
        INIT(esa_info_r,
                 .isa_id = this->isa_ctx_id,
-                .spi_r = proposal->get_spi(proposal),
+                .spi_l = proposal->get_spi(proposal),
                 .nonce_i = chunk_clone(nonce_i),
                 .nonce_r = chunk_clone(nonce_r),
                 .is_encr_r = TRUE,
                 .dh_id = dh_id,
        );
 
-       DBG1(DBG_CHD, "passing on esa info (isa: %llu, spi_r: %x, dh_id: %llu)",
-                esa_info_i->isa_id, ntohl(esa_info_i->spi_r), esa_info_i->dh_id);
+       DBG1(DBG_CHD, "passing on esa info (isa: %llu, spi_l: %x, dh_id: %llu)",
+                esa_info_i->isa_id, ntohl(esa_info_i->spi_l), esa_info_i->dh_id);
 
        /* store ESA info in encr_i/r, which is passed to add_sa */
        *encr_i = chunk_create((u_char *)esa_info_i, sizeof(esa_info_t));
@@ -296,6 +296,12 @@ METHOD(keymat_v2_t, get_skd, pseudo_random_function_t,
 {
        isa_info_t *isa_info;
 
+       if (!this->ae_ctx_id)
+       {
+               *skd = chunk_empty;
+               return PRF_UNDEFINED;
+       }
+
        INIT(isa_info,
                 .parent_isa_id = this->isa_ctx_id,
                 .ae_id = this->ae_ctx_id,
index 19124f25c79728b9b1b1bdec585a743b295a3d29..365f36678778d873cfadcf1a95904b63a62c057f 100644 (file)
@@ -49,9 +49,9 @@ struct esa_info_t {
        isa_id_type isa_id;
 
        /**
-        * Responder SPI of child SA.
+        * Local SPI of child SA.
         */
-       esp_spi_type spi_r;
+       esp_spi_type spi_l;
 
        /**
         * Initiator nonce.
index 56c8b6809dda098a9e7e3b5e8cede00a801e3b20..9cd6d5e029b0f983102b230dcd5900ebd824a4ab 100644 (file)
@@ -107,7 +107,7 @@ START_TEST(test_derive_child_keys)
        fail_if(!info, "encr_i does not contain esa information");
        fail_if(info->isa_id != keymat->get_isa_id(keymat),
                        "Isa context id mismatch (encr_i)");
-       fail_if(info->spi_r != 42,
+       fail_if(info->spi_l != 42,
                        "SPI mismatch (encr_i)");
        fail_unless(chunk_equals(info->nonce_i, nonce),
                                "nonce_i mismatch (encr_i)");
@@ -124,7 +124,7 @@ START_TEST(test_derive_child_keys)
        fail_if(!info, "encr_r does not contain esa information");
        fail_if(info->isa_id != keymat->get_isa_id(keymat),
                        "Isa context id mismatch (encr_r)");
-       fail_if(info->spi_r != 42,
+       fail_if(info->spi_l != 42,
                        "SPI mismatch (encr_r)");
        fail_unless(chunk_equals(info->nonce_i, nonce),
                                "nonce_i mismatch (encr_r)");
index acc1a928f4e49ed134965ae4e31eb3829313a813..05f09f62c005f5fcede1611873ba41a74b036255 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2019 Tobias Brunner
+ * Copyright (C) 2008-2020 Tobias Brunner
  * Copyright (C) 2005-2008 Martin Willi
  * Copyright (C) 2005 Jan Hutter
  *
 #include <processing/jobs/inactivity_job.h>
 #include <processing/jobs/initiate_tasks_job.h>
 
+/** Maximum number of key exchanges (including the initial one, if any) */
+#define MAX_KEY_EXCHANGES (ADDITIONAL_KEY_EXCHANGE_7 - \
+                                                  ADDITIONAL_KEY_EXCHANGE_1 + 2)
+
 typedef struct private_child_create_t private_child_create_t;
 
 /**
@@ -115,19 +119,43 @@ struct private_child_create_t {
        traffic_selector_t *packet_tsr;
 
        /**
-        * optional diffie hellman exchange
+        * Key exchanges to perform
+        */
+       struct {
+               transform_type_t type;
+               key_exchange_method_t method;
+               bool done;
+       } key_exchanges[MAX_KEY_EXCHANGES];
+
+       /**
+        * Current key exchange
         */
-       key_exchange_t *dh;
+       int ke_index;
 
        /**
-        * Applying DH public value failed?
+        * Kex exchange method from the parsed or sent KE payload
         */
-       bool dh_failed;
+       key_exchange_method_t ke_method;
 
        /**
-        * group used for DH exchange
+        * Current key exchange object (if any)
         */
-       key_exchange_method_t dh_group;
+       key_exchange_t *ke;
+
+       /**
+        * All key exchanges performed (key_exchange_t)
+        */
+       array_t *kes;
+
+       /**
+        * Applying KE public key failed?
+        */
+       bool ke_failed;
+
+       /**
+        * Link value for current key exchange
+        */
+       chunk_t link;
 
        /**
         * IKE_SAs keymat
@@ -304,6 +332,10 @@ static bool allocate_spi(private_child_create_t *this)
                this->proto = this->proposal->get_protocol(this->proposal);
        }
        this->my_spi = this->child_sa->alloc_spi(this->child_sa, this->proto);
+       if (!this->my_spi)
+       {
+               DBG1(DBG_IKE, "unable to allocate SPI from kernel");
+       }
        return this->my_spi != 0;
 }
 
@@ -325,11 +357,11 @@ static bool update_and_check_proposals(private_child_create_t *this)
                proposal->set_spi(proposal, this->my_spi);
 
                /* move the selected DH group to the front, if any */
-               if (this->dh_group != KE_NONE)
+               if (this->ke_method != KE_NONE)
                {       /* proposals that don't contain the selected group are
                         * moved to the back */
                        if (!proposal->promote_transform(proposal, KEY_EXCHANGE_METHOD,
-                                                                                        this->dh_group))
+                                                                                        this->ke_method))
                        {
                                this->proposals->remove_at(this->proposals, enumerator);
                                other_dh_groups->insert_last(other_dh_groups, proposal);
@@ -349,7 +381,7 @@ static bool update_and_check_proposals(private_child_create_t *this)
        enumerator->destroy(enumerator);
        other_dh_groups->destroy(other_dh_groups);
 
-       return this->dh_group == KE_NONE || found;
+       return this->ke_method == KE_NONE || found;
 }
 
 /**
@@ -486,107 +518,27 @@ static bool check_mode(private_child_create_t *this, host_t *i, host_t *r)
 }
 
 /**
- * Install a CHILD_SA for usage, return value:
- * - FAILED: no acceptable proposal
- * - INVALID_ARG: diffie hellman group unacceptable
+ * Do traffic selector narrowing and check mode:
+ * - FAILED: mode mismatch
  * - NOT_FOUND: TS unacceptable
  */
-static status_t select_and_install(private_child_create_t *this,
-                                                                  bool no_dh, bool ike_auth)
+static status_t narrow_and_check_ts(private_child_create_t *this, bool ike_auth)
 {
-       status_t status, status_i, status_o;
-       chunk_t nonce_i, nonce_r;
-       chunk_t encr_i = chunk_empty, encr_r = chunk_empty;
-       chunk_t integ_i = chunk_empty, integ_r = chunk_empty;
        linked_list_t *my_ts, *other_ts;
        host_t *me, *other;
-       array_t *kes = NULL;
-       proposal_selection_flag_t flags = 0;
-
-       if (this->proposals == NULL)
-       {
-               DBG1(DBG_IKE, "SA payload missing in message");
-               return FAILED;
-       }
-       if (this->tsi == NULL || this->tsr == NULL)
-       {
-               DBG1(DBG_IKE, "TS payloads missing in message");
-               return NOT_FOUND;
-       }
 
        me = this->ike_sa->get_my_host(this->ike_sa);
        other = this->ike_sa->get_other_host(this->ike_sa);
 
-       if (no_dh)
-       {
-               flags |= PROPOSAL_SKIP_KE;
-       }
-       if (!this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN) &&
-               !lib->settings->get_bool(lib->settings, "%s.accept_private_algs",
-                                                                FALSE, lib->ns))
-       {
-               flags |= PROPOSAL_SKIP_PRIVATE;
-       }
-       if (!lib->settings->get_bool(lib->settings,
-                                                       "%s.prefer_configured_proposals", TRUE, lib->ns))
-       {
-               flags |= PROPOSAL_PREFER_SUPPLIED;
-       }
-       this->proposal = this->config->select_proposal(this->config,
-                                                                                                  this->proposals, flags);
-       if (this->proposal == NULL)
-       {
-               DBG1(DBG_IKE, "no acceptable proposal found");
-               charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_CHILD,
-                                                  this->proposals);
-               return FAILED;
-       }
-       this->other_spi = this->proposal->get_spi(this->proposal);
-
-       if (!this->initiator)
-       {
-               if (!allocate_spi(this))
-               {
-                       /* responder has no SPI allocated yet */
-                       DBG1(DBG_IKE, "allocating SPI failed");
-                       return FAILED;
-               }
-               this->proposal->set_spi(this->proposal, this->my_spi);
-       }
        this->child_sa->set_proposal(this->child_sa, this->proposal);
 
-       if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
-                                                                          this->dh_group))
-       {
-               uint16_t group;
-
-               if (this->proposal->get_algorithm(this->proposal, KEY_EXCHANGE_METHOD,
-                                                                                 &group, NULL))
-               {
-                       DBG1(DBG_IKE, "DH group %N unacceptable, requesting %N",
-                                key_exchange_method_names, this->dh_group,
-                                key_exchange_method_names, group);
-                       this->dh_group = group;
-                       return INVALID_ARG;
-               }
-               /* the selected proposal does not use a DH group */
-               DBG1(DBG_IKE, "ignoring KE exchange, agreed on a non-PFS proposal");
-               DESTROY_IF(this->dh);
-               this->dh = NULL;
-               this->dh_group = KE_NONE;
-       }
-
        if (this->initiator)
        {
-               nonce_i = this->my_nonce;
-               nonce_r = this->other_nonce;
                my_ts = narrow_ts(this, TRUE, this->tsi);
                other_ts = narrow_ts(this, FALSE, this->tsr);
        }
        else
        {
-               nonce_r = this->my_nonce;
-               nonce_i = this->other_nonce;
                my_ts = narrow_ts(this, TRUE, this->tsr);
                other_ts = narrow_ts(this, FALSE, this->tsi);
        }
@@ -621,6 +573,7 @@ static status_t select_and_install(private_child_create_t *this,
 
        this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
        this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy));
+
        if (this->initiator)
        {
                this->tsi = my_ts;
@@ -643,9 +596,38 @@ static status_t select_and_install(private_child_create_t *this,
                        this->mode = MODE_TUNNEL;
                }
        }
+       return SUCCESS;
+}
 
-       if (!this->initiator)
+/**
+ * Install a CHILD_SA:
+ * - FAILED: failure to install
+ * - NOT_FOUND: TS unacceptable
+ */
+static status_t install_child_sa(private_child_create_t *this)
+{
+       status_t status, status_i, status_o;
+       chunk_t nonce_i, nonce_r;
+       chunk_t encr_i = chunk_empty, encr_r = chunk_empty;
+       chunk_t integ_i = chunk_empty, integ_r = chunk_empty;
+       linked_list_t *my_ts, *other_ts;
+
+       if (this->initiator)
+       {
+               nonce_i = this->my_nonce;
+               nonce_r = this->other_nonce;
+
+               my_ts = this->tsi;
+               other_ts = this->tsr;
+       }
+       else
        {
+               nonce_r = this->my_nonce;
+               nonce_i = this->other_nonce;
+
+               my_ts = this->tsr;
+               other_ts = this->tsi;
+
                /* use a copy of the traffic selectors, as the POST hook should not
                 * change payloads */
                my_ts = this->tsr->clone_offset(this->tsr,
@@ -673,7 +655,9 @@ static status_t select_and_install(private_child_create_t *this,
 
        /* addresses might have changed since we originally sent the request, update
         * them before we configure any policies and install the SAs */
-       this->child_sa->update(this->child_sa, me, other, NULL,
+       this->child_sa->update(this->child_sa,
+                                                  this->ike_sa->get_my_host(this->ike_sa),
+                                                  this->ike_sa->get_other_host(this->ike_sa), NULL,
                                                   this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY));
 
        this->child_sa->set_policies(this->child_sa, my_ts, other_ts);
@@ -691,12 +675,8 @@ static status_t select_and_install(private_child_create_t *this,
                this->ipcomp = IPCOMP_NONE;
        }
        status_i = status_o = FAILED;
-       if (this->dh)
-       {
-               array_insert_create(&kes, ARRAY_HEAD, this->dh);
-       }
        if (this->keymat->derive_child_keys(this->keymat, this->proposal,
-                               kes, nonce_i, nonce_r, &encr_i, &integ_i, &encr_r, &integ_r))
+                       this->kes, nonce_i, nonce_r, &encr_i, &integ_i, &encr_r, &integ_r))
        {
                if (this->initiator)
                {
@@ -769,14 +749,13 @@ static status_t select_and_install(private_child_create_t *this,
                                                                                        this->initiator, encr_i, encr_r,
                                                                                        integ_i, integ_r);
                        charon->bus->child_keys(charon->bus, this->child_sa,
-                                                                       this->initiator, kes, nonce_i, nonce_r);
+                                                               this->initiator, this->kes, nonce_i, nonce_r);
                }
        }
        chunk_clear(&integ_i);
        chunk_clear(&integ_r);
        chunk_clear(&encr_i);
        chunk_clear(&encr_r);
-       array_destroy(kes);
 
        if (status != SUCCESS)
        {
@@ -813,6 +792,92 @@ static status_t select_and_install(private_child_create_t *this,
        return SUCCESS;
 }
 
+/**
+ * Select a proposal
+ */
+static bool select_proposal(private_child_create_t *this, bool no_ke)
+{
+       proposal_selection_flag_t flags = 0;
+
+       if (!this->proposals)
+       {
+               DBG1(DBG_IKE, "SA payload missing in message");
+               return FALSE;
+       }
+
+       if (no_ke)
+       {
+               flags |= PROPOSAL_SKIP_KE;
+       }
+       if (!this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN) &&
+               !lib->settings->get_bool(lib->settings, "%s.accept_private_algs",
+                                                                FALSE, lib->ns))
+       {
+               flags |= PROPOSAL_SKIP_PRIVATE;
+       }
+       if (!lib->settings->get_bool(lib->settings,
+                                                       "%s.prefer_configured_proposals", TRUE, lib->ns))
+       {
+               flags |= PROPOSAL_PREFER_SUPPLIED;
+       }
+       this->proposal = this->config->select_proposal(this->config,
+                                                                                                  this->proposals, flags);
+       if (!this->proposal)
+       {
+               DBG1(DBG_IKE, "no acceptable proposal found");
+               charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_CHILD,
+                                                  this->proposals);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * Add a KE payload if a key exchange is used.  As responder we might already
+ * have stored the object in the list of completed exchanges.
+ */
+static bool add_ke_payload(private_child_create_t *this,
+                                                  message_t *message)
+{
+       key_exchange_t *ke;
+       ke_payload_t *pld;
+
+       if (this->ke)
+       {
+               ke = this->ke;
+       }
+       else if (!array_get(this->kes, ARRAY_TAIL, &ke))
+       {
+               return TRUE;
+       }
+
+       pld = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE, ke);
+       if (!pld)
+       {
+               DBG1(DBG_IKE, "creating KE payload failed");
+               return FALSE;
+       }
+       message->add_payload(message, (payload_t*)pld);
+       return TRUE;
+}
+
+/**
+ * Build payloads in additional exchanges when using multiple key exchanges
+ */
+static bool build_payloads_multi_ke(private_child_create_t *this,
+                                                                       message_t *message)
+{
+       if (!add_ke_payload(this, message))
+       {
+               return FALSE;
+       }
+       if (this->link.ptr)
+       {
+               message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
+       }
+       return TRUE;
+}
+
 /**
  * build the payloads for the message
  */
@@ -820,11 +885,14 @@ static bool build_payloads(private_child_create_t *this, message_t *message)
 {
        sa_payload_t *sa_payload;
        nonce_payload_t *nonce_payload;
-       ke_payload_t *ke_payload;
        ts_payload_t *ts_payload;
        kernel_feature_t features;
 
-       /* add SA payload */
+       if (message->get_exchange_type(message) == IKE_FOLLOWUP_KE)
+       {
+               return build_payloads_multi_ke(this, message);
+       }
+
        if (this->initiator)
        {
                sa_payload = sa_payload_create_from_proposals_v2(this->proposals);
@@ -843,17 +911,14 @@ static bool build_payloads(private_child_create_t *this, message_t *message)
                message->add_payload(message, (payload_t*)nonce_payload);
        }
 
-       /* diffie hellman exchange, if PFS enabled */
-       if (this->dh)
+       if (this->link.ptr)
        {
-               ke_payload = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE,
-                                                                                                                this->dh);
-               if (!ke_payload)
-               {
-                       DBG1(DBG_IKE, "creating KE payload failed");
-                       return FALSE;
-               }
-               message->add_payload(message, (payload_t*)ke_payload);
+               message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
+       }
+
+       if (!add_ke_payload(this, message))
+       {
+               return FALSE;
        }
 
        /* add TSi/TSr payloads */
@@ -962,6 +1027,180 @@ static void handle_notify(private_child_create_t *this, notify_payload_t *notify
        }
 }
 
+/**
+ * Collect all key exchanges from the proposal
+ */
+static void determine_key_exchanges(private_child_create_t *this)
+{
+       transform_type_t t = KEY_EXCHANGE_METHOD;
+       uint16_t alg;
+       int i = 1;
+
+       if (!this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
+       {       /* no PFS */
+               return;
+       }
+
+       this->key_exchanges[0].type = t;
+       this->key_exchanges[0].method = alg;
+
+       for (t = ADDITIONAL_KEY_EXCHANGE_1; t <= ADDITIONAL_KEY_EXCHANGE_7; t++)
+       {
+               if (this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
+               {
+                       this->key_exchanges[i].type = t;
+                       this->key_exchanges[i].method = alg;
+                       i++;
+               }
+       }
+}
+
+/**
+ * Check if additional key exchanges are required
+ */
+static bool additional_key_exchange_required(private_child_create_t *this)
+{
+       int i;
+
+       for (i = this->ke_index; i < MAX_KEY_EXCHANGES; i++)
+       {
+               if (this->key_exchanges[i].type && !this->key_exchanges[i].done)
+               {
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+/**
+ * Clear data on key exchanges
+ */
+static void clear_key_exchanges(private_child_create_t *this)
+{
+       int i;
+
+       for (i = 0; i < MAX_KEY_EXCHANGES; i++)
+       {
+               this->key_exchanges[i].type = 0;
+               this->key_exchanges[i].method = 0;
+               this->key_exchanges[i].done = FALSE;
+       }
+       this->ke_index = 0;
+
+       array_destroy_offset(this->kes, offsetof(key_exchange_t, destroy));
+       this->kes = NULL;
+}
+
+/**
+ * Process a KE payload
+ */
+static void process_ke_payload(private_child_create_t *this, ke_payload_t *ke)
+{
+       key_exchange_method_t method = this->key_exchanges[this->ke_index].method;
+       key_exchange_method_t received = ke->get_key_exchange_method(ke);
+
+       /* the proposal is selected after processing the KE payload, so this is
+        * only relevant for additional key exchanges */
+       if (method && method != received)
+       {
+               DBG1(DBG_IKE, "key exchange method in received payload %N doesn't "
+                        "match negotiated %N", key_exchange_method_names, received,
+                        key_exchange_method_names, method);
+               this->ke_failed = TRUE;
+               return;
+       }
+
+       this->ke_method = received;
+
+       if (!this->initiator)
+       {
+               DESTROY_IF(this->ke);
+               this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
+                                                                                                 received);
+               if (!this->ke)
+               {
+                       DBG1(DBG_IKE, "key exchange method %N not supported",
+                                key_exchange_method_names, received);
+               }
+       }
+       else if (this->ke)
+       {
+               if (this->ke->get_method(this->ke) != received)
+               {
+                       DBG1(DBG_IKE, "key exchange method %N in received payload doesn't "
+                                "match %N", key_exchange_method_names, received,
+                                key_exchange_method_names, this->ke->get_method(this->ke));
+                       this->ke_failed = TRUE;
+               }
+       }
+
+       if (this->ke && !this->ke_failed)
+       {
+               if (!this->ke->set_public_key(this->ke, ke->get_key_exchange_data(ke)))
+               {
+                       DBG1(DBG_IKE, "applying key exchange public key failed");
+                       this->ke_failed = TRUE;
+               }
+       }
+}
+
+/**
+ * Check if the proposed KE method in CREATE_CHILD_SA (received via KE payload)
+ * is valid according to the selected proposal.
+ */
+static bool check_ke_method(private_child_create_t *this, uint16_t *req)
+{
+       uint16_t alg;
+
+       if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
+                                                                          this->ke_method))
+       {
+               if (this->proposal->get_algorithm(this->proposal, KEY_EXCHANGE_METHOD,
+                                                                                 &alg, NULL))
+               {
+                       if (req)
+                       {
+                               *req = alg;
+                       }
+                       return FALSE;
+               }
+               /* the selected proposal does not use a key exchange method */
+               DBG1(DBG_IKE, "ignoring KE payload, agreed on a non-PFS proposal");
+               DESTROY_IF(this->ke);
+               this->ke = NULL;
+               this->ke_method = KE_NONE;
+               /* ignore errors that occurred while handling the KE payload */
+               this->ke_failed = FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * Check if the proposed key exchange method is valid as responder or whether
+ * we should request another KE payload.
+ */
+static bool check_ke_method_r(private_child_create_t *this, message_t *message)
+{
+       uint16_t alg;
+
+       if (!check_ke_method(this, &alg))
+       {
+               DBG1(DBG_IKE, "DH group %N unacceptable, requesting %N",
+                        key_exchange_method_names, this->ke_method,
+                        key_exchange_method_names, alg);
+               alg = htons(alg);
+               message->add_notify(message, FALSE, INVALID_KE_PAYLOAD,
+                                                       chunk_from_thing(alg));
+               return FALSE;
+       }
+       else if (this->ke_method != KE_NONE && !this->ke)
+       {
+               message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               return FALSE;
+       }
+       return TRUE;
+}
+
 /**
  * Read payloads from message
  */
@@ -970,7 +1209,6 @@ static void process_payloads(private_child_create_t *this, message_t *message)
        enumerator_t *enumerator;
        payload_t *payload;
        sa_payload_t *sa_payload;
-       ke_payload_t *ke_payload;
        ts_payload_t *ts_payload;
 
        /* defaults to TUNNEL mode */
@@ -986,24 +1224,7 @@ static void process_payloads(private_child_create_t *this, message_t *message)
                                this->proposals = sa_payload->get_proposals(sa_payload);
                                break;
                        case PLV2_KEY_EXCHANGE:
-                               ke_payload = (ke_payload_t*)payload;
-                               if (!this->initiator)
-                               {
-                                       this->dh_group = ke_payload->get_key_exchange_method(
-                                                                                                                                       ke_payload);
-                                       this->dh = this->keymat->keymat.create_ke(
-                                                                               &this->keymat->keymat, this->dh_group);
-                               }
-                               else if (this->dh)
-                               {
-                                       this->dh_failed = this->dh->get_method(this->dh) !=
-                                                               ke_payload->get_key_exchange_method(ke_payload);
-                               }
-                               if (this->dh && !this->dh_failed)
-                               {
-                                       this->dh_failed = !this->dh->set_public_key(this->dh,
-                                                               ke_payload->get_key_exchange_data(ke_payload));
-                               }
+                               process_ke_payload(this, (ke_payload_t*)payload);
                                break;
                        case PLV2_TS_INITIATOR:
                                ts_payload = (ts_payload_t*)payload;
@@ -1149,6 +1370,36 @@ static bool check_for_generic_label(private_child_create_t *this)
        return FALSE;
 }
 
+METHOD(task_t, build_i_multi_ke, status_t,
+       private_child_create_t *this, message_t *message)
+{
+       key_exchange_method_t method;
+
+       message->set_exchange_type(message, IKE_FOLLOWUP_KE);
+       DESTROY_IF(this->ke);
+       method = this->key_exchanges[this->ke_index].method;
+       this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
+                                                                                         method);
+       if (!this->ke)
+       {
+               DBG1(DBG_IKE, "negotiated key exchange method %N not supported",
+                        key_exchange_method_names, method);
+               return FAILED;
+       }
+       if (!this->link.ptr)
+       {
+               DBG1(DBG_IKE, "%N notify missing", notify_type_names,
+                        ADDITIONAL_KEY_EXCHANGE);
+               return FAILED;
+       }
+
+       if (!build_payloads_multi_ke(this, message))
+       {
+               return FAILED;
+       }
+       return NEED_MORE;
+}
+
 METHOD(task_t, build_i, status_t,
        private_child_create_t *this, message_t *message)
 {
@@ -1156,6 +1407,7 @@ METHOD(task_t, build_i, status_t,
        host_t *vip;
        peer_cfg_t *peer_cfg;
        linked_list_t *list;
+       bool no_ke = TRUE;
 
        switch (message->get_exchange_type(message))
        {
@@ -1167,11 +1419,7 @@ METHOD(task_t, build_i, status_t,
                                message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
                                return SUCCESS;
                        }
-                       if (!this->retry && this->dh_group == KE_NONE)
-                       {       /* during a rekeying the group might already be set */
-                               this->dh_group = this->config->get_algorithm(this->config,
-                                                                                                               KEY_EXCHANGE_METHOD);
-                       }
+                       no_ke = FALSE;
                        break;
                case IKE_AUTH:
                        switch (defer_child_sa(this))
@@ -1253,8 +1501,7 @@ METHOD(task_t, build_i, status_t,
                         this->child.label->get_string(this->child.label));
        }
 
-       this->proposals = this->config->get_proposals(this->config,
-                                                                                                 this->dh_group == KE_NONE);
+       this->proposals = this->config->get_proposals(this->config, no_ke);
        this->mode = this->config->get_mode(this->config);
 
        this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
@@ -1289,22 +1536,36 @@ METHOD(task_t, build_i, status_t,
 
        if (!allocate_spi(this))
        {
-               DBG1(DBG_IKE, "unable to allocate SPIs from kernel");
                return FAILED;
        }
 
+       if (!no_ke && !this->retry)
+       {       /* during a rekeying the method might already be set */
+               if (this->ke_method == KE_NONE)
+               {
+                       this->ke_method = this->config->get_algorithm(this->config,
+                                                                                                                 KEY_EXCHANGE_METHOD);
+               }
+       }
+
        if (!update_and_check_proposals(this))
        {
-               DBG1(DBG_IKE, "requested DH group %N not contained in any of our "
-                        "proposals",
-                        key_exchange_method_names, this->dh_group);
+               DBG1(DBG_IKE, "requested key exchange method %N not contained in any "
+                        "of our proposals",
+                        key_exchange_method_names, this->ke_method);
                return FAILED;
        }
 
-       if (this->dh_group != KE_NONE)
+       if (this->ke_method != KE_NONE)
        {
-               this->dh = this->keymat->keymat.create_ke(&this->keymat->keymat,
-                                                                                                 this->dh_group);
+               this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
+                                                                                                 this->ke_method);
+               if (!this->ke)
+               {
+                       DBG1(DBG_IKE, "negotiated key exchange method %N not supported",
+                                key_exchange_method_names, this->ke_method);
+                       return FAILED;
+               }
        }
 
        if (this->config->has_option(this->config, OPT_IPCOMP))
@@ -1339,6 +1600,67 @@ METHOD(task_t, build_i, status_t,
        return NEED_MORE;
 }
 
+/**
+ * Process payloads in a IKE_FOLLOWUP_KE message or a CREATE_CHILD_SA response
+ */
+static void process_link(private_child_create_t *this, message_t *message)
+{
+       notify_payload_t *notify;
+       chunk_t link;
+
+       notify = message->get_notify(message, ADDITIONAL_KEY_EXCHANGE);
+       if (notify)
+       {
+               link = notify->get_notification_data(notify);
+               if (this->initiator)
+               {
+                       chunk_free(&this->link);
+                       this->link = chunk_clone(link);
+               }
+               else if (!chunk_equals_const(this->link, link))
+               {
+                       DBG1(DBG_IKE, "data in %N notify doesn't match", notify_type_names,
+                                ADDITIONAL_KEY_EXCHANGE);
+                       chunk_free(&this->link);
+               }
+       }
+       else
+       {
+               chunk_free(&this->link);
+       }
+}
+
+/**
+ * Process payloads in additional exchanges when using multiple key exchanges
+ */
+static void process_payloads_multi_ke(private_child_create_t *this,
+                                                                         message_t *message)
+{
+       ke_payload_t *ke;
+
+       ke = (ke_payload_t*)message->get_payload(message, PLV2_KEY_EXCHANGE);
+       if (ke)
+       {
+               process_ke_payload(this, ke);
+       }
+       else
+       {
+               DBG1(DBG_IKE, "KE payload missing in message");
+               this->ke_failed = TRUE;
+       }
+       process_link(this, message);
+}
+
+METHOD(task_t, process_r_multi_ke, status_t,
+       private_child_create_t *this, message_t *message)
+{
+       if (message->get_exchange_type(message) == IKE_FOLLOWUP_KE)
+       {
+               process_payloads_multi_ke(this, message);
+       }
+       return NEED_MORE;
+}
+
 METHOD(task_t, process_r, status_t,
        private_child_create_t *this, message_t *message)
 {
@@ -1533,12 +1855,112 @@ static bool select_label(private_child_create_t *this)
        return TRUE;
 }
 
+/**
+ * Called when a key exchange is done, returns TRUE once all are done.
+ */
+static bool key_exchange_done(private_child_create_t *this)
+{
+       bool additional_ke;
+
+       if (!this->ke)
+       {
+               return TRUE;
+       }
+
+       this->key_exchanges[this->ke_index++].done = TRUE;
+       additional_ke = additional_key_exchange_required(this);
+
+       array_insert_create(&this->kes, ARRAY_TAIL, this->ke);
+       this->ke = NULL;
+
+       return additional_ke ? FALSE : TRUE;
+}
+
+/**
+ * Complete the current key exchange and install the CHILD_SA if all are done
+ * as responder.
+ */
+static bool key_exchange_done_and_install_r(private_child_create_t *this,
+                                                                                       message_t *message, bool ike_auth)
+{
+       if (key_exchange_done(this))
+       {
+               chunk_clear(&this->link);
+       }
+       else if (!this->link.ptr)
+       {
+               this->link = chunk_clone(chunk_from_chars(0x42));
+       }
+
+       if (!build_payloads(this, message))
+       {
+               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               handle_child_sa_failure(this, message);
+               return TRUE;
+       }
+
+       if (!this->link.ptr)
+       {
+               switch (install_child_sa(this))
+               {
+                       case SUCCESS:
+                               break;
+                       case NOT_FOUND:
+                               message->add_notify(message, FALSE, TS_UNACCEPTABLE,
+                                                                       chunk_empty);
+                               handle_child_sa_failure(this, message);
+                               return TRUE;
+                       case FAILED:
+                       default:
+                               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN,
+                                                                       chunk_empty);
+                               handle_child_sa_failure(this, message);
+                               return TRUE;
+               }
+               if (!this->rekey)
+               {       /* invoke the child_up() hook if we are not rekeying */
+                       charon->bus->child_updown(charon->bus, this->child_sa, TRUE);
+               }
+       }
+       return !this->link.ptr;
+}
+
+METHOD(task_t, build_r_multi_ke, status_t,
+       private_child_create_t *this, message_t *message)
+{
+       if (!this->ke)
+       {
+               message->add_notify(message, FALSE, INVALID_SYNTAX, chunk_empty);
+               handle_child_sa_failure(this, message);
+               return SUCCESS;
+       }
+       if (this->ke_failed)
+       {
+               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               handle_child_sa_failure(this, message);
+               return SUCCESS;
+       }
+       if (!this->link.ptr)
+       {
+               DBG1(DBG_IKE, "%N notify missing", notify_type_names,
+                        ADDITIONAL_KEY_EXCHANGE);
+               message->add_notify(message, FALSE, STATE_NOT_FOUND, chunk_empty);
+               handle_child_sa_failure(this, message);
+               return SUCCESS;
+       }
+       if (!key_exchange_done_and_install_r(this, message, FALSE))
+       {
+               return NEED_MORE;
+       }
+       return SUCCESS;
+}
+
 METHOD(task_t, build_r, status_t,
        private_child_create_t *this, message_t *message)
 {
        payload_t *payload;
        enumerator_t *enumerator;
-       bool no_dh = TRUE, ike_auth = FALSE;
+       bool no_ke = TRUE, ike_auth = FALSE;
 
        switch (message->get_exchange_type(message))
        {
@@ -1551,18 +1973,11 @@ METHOD(task_t, build_r, status_t,
                                                                        chunk_empty);
                                return SUCCESS;
                        }
-                       if (this->dh_failed)
-                       {
-                               DBG1(DBG_IKE, "applying DH public value failed");
-                               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN,
-                                                                       chunk_empty);
-                               return SUCCESS;
-                       }
-                       no_dh = FALSE;
+                       no_ke = FALSE;
                        break;
                case IKE_AUTH:
                        if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
-                       {       /* wait until all authentication round completed */
+                       {       /* wait until all authentication rounds completed */
                                return NEED_MORE;
                        }
                        if (this->ike_sa->has_condition(this->ike_sa, COND_REDIRECTED))
@@ -1601,15 +2016,23 @@ METHOD(task_t, build_r, status_t,
                return SUCCESS;
        }
 
-       if (this->config == NULL)
+       if (!this->config)
        {
                this->config = select_child_cfg(this);
        }
-       if (this->config == NULL)
+       if (!this->config || !this->tsi || !this->tsr)
        {
-               DBG1(DBG_IKE, "traffic selectors %#R === %#R unacceptable",
-                        this->tsr, this->tsi);
-               charon->bus->alert(charon->bus, ALERT_TS_MISMATCH, this->tsi, this->tsr);
+               if (!this->tsi || !this->tsr)
+               {
+                       DBG1(DBG_IKE, "TS payloads missing in message");
+               }
+               else
+               {
+                       DBG1(DBG_IKE, "traffic selectors %#R === %#R unacceptable",
+                                this->tsr, this->tsi);
+                       charon->bus->alert(charon->bus, ALERT_TS_MISMATCH, this->tsi,
+                                                          this->tsr);
+               }
                message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
                handle_child_sa_failure(this, message);
                return SUCCESS;
@@ -1641,6 +2064,29 @@ METHOD(task_t, build_r, status_t,
        }
        enumerator->destroy(enumerator);
 
+       if (!select_proposal(this, no_ke))
+       {
+               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               handle_child_sa_failure(this, message);
+               return SUCCESS;
+       }
+
+       if (!check_ke_method_r(this, message))
+       {       /* the peer will retry, we don't handle this as failure */
+               return SUCCESS;
+       }
+
+       /* this flag might get reset if the check above notices a proposal without
+        * KE was selected */
+       if (this->ke_failed)
+       {
+               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               handle_child_sa_failure(this, message);
+               return SUCCESS;
+       }
+
+       determine_key_exchanges(this);
+
        if (!select_label(this))
        {
                message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
@@ -1655,6 +2101,15 @@ METHOD(task_t, build_r, status_t,
                                                                         this->ike_sa->get_other_host(this->ike_sa),
                                                                         this->config, &this->child);
 
+       this->other_spi = this->proposal->get_spi(this->proposal);
+       if (!allocate_spi(this))
+       {
+               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               handle_child_sa_failure(this, message);
+               return SUCCESS;
+       }
+       this->proposal->set_spi(this->proposal, this->my_spi);
+
        if (this->ipcomp_received != IPCOMP_NONE)
        {
                if (this->config->has_option(this->config, OPT_IPCOMP))
@@ -1668,7 +2123,7 @@ METHOD(task_t, build_r, status_t,
                }
        }
 
-       switch (select_and_install(this, no_dh, ike_auth))
+       switch (narrow_and_check_ts(this, ike_auth))
        {
                case SUCCESS:
                        break;
@@ -1676,13 +2131,6 @@ METHOD(task_t, build_r, status_t,
                        message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
                        handle_child_sa_failure(this, message);
                        return SUCCESS;
-               case INVALID_ARG:
-               {
-                       uint16_t group = htons(this->dh_group);
-                       message->add_notify(message, FALSE, INVALID_KE_PAYLOAD,
-                                                               chunk_from_thing(group));
-                       return SUCCESS;
-               }
                case FAILED:
                default:
                        message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
@@ -1690,16 +2138,11 @@ METHOD(task_t, build_r, status_t,
                        return SUCCESS;
        }
 
-       if (!build_payloads(this, message))
+       if (!key_exchange_done_and_install_r(this, message, ike_auth))
        {
-               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
-               handle_child_sa_failure(this, message);
-               return SUCCESS;
-       }
-
-       if (!this->rekey)
-       {       /* invoke the child_up() hook if we are not rekeying */
-               charon->bus->child_updown(charon->bus, this->child_sa, TRUE);
+               this->public.task.build = _build_r_multi_ke;
+               this->public.task.process = _process_r_multi_ke;
+               return NEED_MORE;
        }
        return SUCCESS;
 }
@@ -1755,12 +2198,50 @@ static status_t delete_failed_sa(private_child_create_t *this)
        return SUCCESS;
 }
 
+/**
+ * Complete the current key exchange and install the CHILD_SA if all are done
+ * as initiator.
+ */
+static status_t key_exchange_done_and_install_i(private_child_create_t *this,
+                                                                                       message_t *message, bool ike_auth)
+{
+       if (key_exchange_done(this))
+       {
+               if (install_child_sa(this) == SUCCESS)
+               {
+                       if (!this->rekey)
+                       {       /* invoke the child_up() hook if we are not rekeying */
+                               charon->bus->child_updown(charon->bus, this->child_sa,
+                                                                                 TRUE);
+                       }
+                       return SUCCESS;
+               }
+               handle_child_sa_failure(this, message);
+               return delete_failed_sa(this);
+       }
+       return NEED_MORE;
+}
+
+METHOD(task_t, process_i_multi_ke, status_t,
+       private_child_create_t *this, message_t *message)
+{
+       process_payloads_multi_ke(this, message);
+
+       if (this->ke_failed)
+       {
+               handle_child_sa_failure(this, message);
+               return delete_failed_sa(this);
+       }
+
+       return key_exchange_done_and_install_i(this, message, FALSE);
+}
+
 METHOD(task_t, process_i, status_t,
        private_child_create_t *this, message_t *message)
 {
        enumerator_t *enumerator;
        payload_t *payload;
-       bool no_dh = TRUE, ike_auth = FALSE;
+       bool no_ke = TRUE, ike_auth = FALSE;
 
        switch (message->get_exchange_type(message))
        {
@@ -1768,11 +2249,11 @@ METHOD(task_t, process_i, status_t,
                        return get_nonce(message, &this->other_nonce);
                case CREATE_CHILD_SA:
                        get_nonce(message, &this->other_nonce);
-                       no_dh = FALSE;
+                       no_ke = FALSE;
                        break;
                case IKE_AUTH:
                        if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
-                       {       /* wait until all authentication round completed */
+                       {       /* wait until all authentication rounds completed */
                                return NEED_MORE;
                        }
                        if (defer_child_sa(this) == NEED_MORE)
@@ -1828,28 +2309,27 @@ METHOD(task_t, process_i, status_t,
                                case INVALID_KE_PAYLOAD:
                                {
                                        chunk_t data;
-                                       uint16_t group = KE_NONE;
+                                       uint16_t alg = KE_NONE;
 
                                        data = notify->get_notification_data(notify);
-                                       if (data.len == sizeof(group))
+                                       if (data.len == sizeof(alg))
                                        {
-                                               memcpy(&group, data.ptr, data.len);
-                                               group = ntohs(group);
+                                               alg = untoh16(data.ptr);
                                        }
                                        if (this->retry)
                                        {
                                                DBG1(DBG_IKE, "already retried with DH group %N, "
                                                         "ignore requested %N", key_exchange_method_names,
-                                                        this->dh_group, key_exchange_method_names, group);
+                                                        this->ke_method, key_exchange_method_names, alg);
                                                handle_child_sa_failure(this, message);
                                                /* an error in CHILD_SA creation is not critical */
                                                return SUCCESS;
                                        }
                                        DBG1(DBG_IKE, "peer didn't accept DH group %N, "
                                                 "it requested %N", key_exchange_method_names,
-                                                this->dh_group, key_exchange_method_names, group);
+                                                this->ke_method, key_exchange_method_names, alg);
                                        this->retry = TRUE;
-                                       this->dh_group = group;
+                                       this->ke_method = alg;
                                        this->child_sa->set_state(this->child_sa, CHILD_RETRYING);
                                        this->public.task.migrate(&this->public.task, this->ike_sa);
                                        enumerator->destroy(enumerator);
@@ -1899,31 +2379,54 @@ METHOD(task_t, process_i, status_t,
                return delete_failed_sa(this);
        }
 
-       if (this->dh_failed)
+       if (!select_proposal(this, no_ke))
        {
-               DBG1(DBG_IKE, "applying DH public value failed");
                handle_child_sa_failure(this, message);
                return delete_failed_sa(this);
        }
 
-       if (!select_label(this))
+       this->other_spi = this->proposal->get_spi(this->proposal);
+       this->proposal->set_spi(this->proposal, this->my_spi);
+
+       if (!check_ke_method(this, NULL))
        {
                handle_child_sa_failure(this, message);
                return delete_failed_sa(this);
        }
 
-       if (select_and_install(this, no_dh, ike_auth) == SUCCESS)
+       if (this->ke_failed)
        {
-               if (!this->rekey)
-               {       /* invoke the child_up() hook if we are not rekeying */
-                       charon->bus->child_updown(charon->bus, this->child_sa, TRUE);
-               }
+               handle_child_sa_failure(this, message);
+               return delete_failed_sa(this);
        }
-       else
+
+       determine_key_exchanges(this);
+
+       if (!select_label(this))
+       {
+               handle_child_sa_failure(this, message);
+               return delete_failed_sa(this);
+       }
+
+       if (narrow_and_check_ts(this, ike_auth) != SUCCESS)
        {
                handle_child_sa_failure(this, message);
                return delete_failed_sa(this);
        }
+
+       if (key_exchange_done_and_install_i(this, message, ike_auth) == NEED_MORE)
+       {
+               /* make sure we are not deleting the failed SA before switching to
+                * multi-KE mode */
+               if (this->public.task.build == _build_i)
+               {
+                       /* if we don't have the notify we handle it in build() */
+                       process_link(this, message);
+                       this->public.task.build = _build_i_multi_ke;
+                       this->public.task.process = _process_i_multi_ke;
+               }
+               return NEED_MORE;
+       }
        return SUCCESS;
 }
 
@@ -1966,7 +2469,7 @@ METHOD(child_create_t, use_label, void,
 METHOD(child_create_t, use_dh_group, void,
        private_child_create_t *this, key_exchange_method_t dh_group)
 {
-       this->dh_group = dh_group;
+       this->ke_method = dh_group;
 }
 
 METHOD(child_create_t, get_child, child_sa_t*,
@@ -2007,6 +2510,7 @@ METHOD(task_t, migrate, void,
 {
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
+       chunk_free(&this->link);
        if (this->tsr)
        {
                this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
@@ -2026,15 +2530,16 @@ METHOD(task_t, migrate, void,
        DESTROY_IF(this->child_sa);
        DESTROY_IF(this->proposal);
        DESTROY_IF(this->nonceg);
-       DESTROY_IF(this->dh);
-       this->dh_failed = FALSE;
+       DESTROY_IF(this->ke);
+       this->ke_failed = FALSE;
+       clear_key_exchanges(this);
        if (this->proposals)
        {
                this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
        }
        if (!this->rekey && !this->retry)
        {
-               this->dh_group = KE_NONE;
+               this->ke_method = KE_NONE;
        }
        this->ike_sa = ike_sa;
        this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
@@ -2042,7 +2547,7 @@ METHOD(task_t, migrate, void,
        this->proposals = NULL;
        this->tsi = NULL;
        this->tsr = NULL;
-       this->dh = NULL;
+       this->ke = NULL;
        this->nonceg = NULL;
        this->child_sa = NULL;
        this->mode = MODE_TUNNEL;
@@ -2051,6 +2556,7 @@ METHOD(task_t, migrate, void,
        this->other_cpi = 0;
        this->established = FALSE;
        this->public.task.build = _build_i;
+       this->public.task.process = _process_i;
 }
 
 METHOD(task_t, destroy, void,
@@ -2058,6 +2564,7 @@ METHOD(task_t, destroy, void,
 {
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
+       chunk_free(&this->link);
        if (this->tsr)
        {
                this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
@@ -2085,7 +2592,8 @@ METHOD(task_t, destroy, void,
        DESTROY_IF(this->packet_tsi);
        DESTROY_IF(this->packet_tsr);
        DESTROY_IF(this->proposal);
-       DESTROY_IF(this->dh);
+       DESTROY_IF(this->ke);
+       clear_key_exchanges(this);
        if (this->proposals)
        {
                this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
@@ -2125,7 +2633,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa,
                .config = config,
                .packet_tsi = tsi ? tsi->clone(tsi) : NULL,
                .packet_tsr = tsr ? tsr->clone(tsr) : NULL,
-               .dh_group = KE_NONE,
+               .ke_method = KE_NONE,
                .keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa),
                .mode = MODE_TUNNEL,
                .tfcv3 = TRUE,