]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
wip: ike-init: Prototypical IKE_INTERMEDIATE exchange for additional KE methods
authorTobias Brunner <tobias@strongswan.org>
Thu, 31 Oct 2019 16:16:44 +0000 (17:16 +0100)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Sun, 29 Mar 2020 11:47:23 +0000 (13:47 +0200)
src/libcharon/sa/ikev2/tasks/ike_init.c

index e3ee9d7892e2e957c8e80ea1c6fee04a804efdce..d5933ee13953b57c15ec00dd5958d120b6a0a776 100644 (file)
 /** maximum retries to do with cookies/other dh groups */
 #define MAX_RETRIES 5
 
+/** maximum number of key exchanges (including the initial one) */
+#define MAX_KEY_EXCHANGES (ADDITIONAL_KEY_EXCHANGE_7 - \
+                                                  ADDITIONAL_KEY_EXCHANGE_1 + 2)
+
 typedef struct private_ike_init_t private_ike_init_t;
 
 /**
@@ -55,19 +59,33 @@ struct private_ike_init_t {
        bool initiator;
 
        /**
-        * diffie hellman group to use
+        * Key exchanges to perform
+        */
+       struct {
+               transform_type_t type;
+               key_exchange_method_t method;
+               bool done;
+       } key_exchanges[MAX_KEY_EXCHANGES];
+
+       /**
+        * Current key exchange
+        */
+       int ke_index;
+
+       /**
+        * Key exchange method from the parsed or sent KE payload
         */
-       key_exchange_method_t dh_group;
+       key_exchange_method_t ke_method;
 
        /**
-        * diffie hellman key exchange
+        * Key exchange object
         */
-       key_exchange_t *dh;
+       key_exchange_t *ke;
 
        /**
-        * Applying DH public value failed?
+        * Applying KE public key failed?
         */
-       bool dh_failed;
+       bool ke_failed;
 
        /**
         * Keymat derivation (from IKE_SA)
@@ -333,7 +351,7 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
                        }
                        /* move the selected DH group to the front of the proposal */
                        if (!proposal->promote_transform(proposal, KEY_EXCHANGE_METHOD,
-                                                                                        this->dh_group))
+                                                                                        this->ke_method))
                        {       /* the proposal does not include the group, move to the back */
                                proposal_list->remove_at(proposal_list, enumerator);
                                other_dh_groups->insert_last(other_dh_groups, proposal);
@@ -364,25 +382,17 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
        message->add_payload(message, (payload_t*)sa_payload);
 
        ke_payload = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE,
-                                                                                                        this->dh);
+                                                                                                        this->ke);
        if (!ke_payload)
        {
                DBG1(DBG_IKE, "creating KE payload failed");
                return FALSE;
        }
+       message->add_payload(message, (payload_t*)ke_payload);
+
        nonce_payload = nonce_payload_create(PLV2_NONCE);
        nonce_payload->set_nonce(nonce_payload, this->my_nonce);
-
-       if (this->old_sa)
-       {       /* payload order differs if we are rekeying */
-               message->add_payload(message, (payload_t*)nonce_payload);
-               message->add_payload(message, (payload_t*)ke_payload);
-       }
-       else
-       {
-               message->add_payload(message, (payload_t*)ke_payload);
-               message->add_payload(message, (payload_t*)nonce_payload);
-       }
+       message->add_payload(message, (payload_t*)nonce_payload);
 
        /* negotiate fragmentation if we are not rekeying */
        if (!this->old_sa &&
@@ -512,6 +522,102 @@ static void process_sa_payload(private_ike_init_t *this, message_t *message,
                                                                  offsetof(proposal_t, destroy));
 }
 
+/**
+ * Collect all key exchanges from the proposal
+ */
+static void determine_key_exchanges(private_ike_init_t *this)
+{
+       transform_type_t t = KEY_EXCHANGE_METHOD;
+       uint16_t alg;
+       int i = 1;
+
+       this->proposal->get_algorithm(this->proposal, t, &alg, NULL);
+       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_ike_init_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_ike_init_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;
+}
+
+/**
+ * Process a KE payload
+ */
+static void process_ke_payload(private_ike_init_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);
+
+       if (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;
+       }
+
+       if (!this->initiator)
+       {
+               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);
+               }
+       }
+       else if (this->ke)
+       {
+               this->ke_failed = this->ke->get_method(this->ke) != received;
+       }
+
+       if (this->ke && !this->ke_failed)
+       {
+               this->ke_failed = !this->ke->set_public_key(this->ke,
+                                                                                               ke->get_key_exchange_data(ke));
+       }
+}
+
 /**
  * Read payloads from message
  */
@@ -519,7 +625,7 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
 {
        enumerator_t *enumerator;
        payload_t *payload;
-       ke_payload_t *ke_payload = NULL;
+       ke_payload_t *ke_pld = NULL;
 
        enumerator = message->create_payload_enumerator(message);
        while (enumerator->enumerate(enumerator, &payload))
@@ -533,9 +639,9 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
                        }
                        case PLV2_KEY_EXCHANGE:
                        {
-                               ke_payload = (ke_payload_t*)payload;
+                               ke_pld = (ke_payload_t*)payload;
 
-                               this->dh_group = ke_payload->get_key_exchange_method(ke_payload);
+                               this->ke_method = ke_pld->get_key_exchange_method(ke_pld);
                                break;
                        }
                        case PLV2_NONCE:
@@ -614,29 +720,69 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
        if (this->proposal)
        {
                this->ike_sa->set_proposal(this->ike_sa, this->proposal);
-       }
+               determine_key_exchanges(this);
 
-       if (ke_payload && this->proposal &&
-               this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
-                                                                         this->dh_group))
-       {
-               if (!this->initiator)
+               if (ke_pld)
                {
-                       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) != this->dh_group;
-               }
-               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_pld);
                }
        }
 }
 
+/**
+ * Build payloads during IKE_INTERMEDIATE exchanges
+ */
+static bool build_payloads_intermediate(private_ike_init_t *this,
+                                                                               message_t *message)
+{
+       ke_payload_t *ke;
+       nonce_payload_t *nonce;
+
+       ke = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE, this->ke);
+       if (!ke)
+       {
+               DBG1(DBG_IKE, "creating KE payload failed");
+               return FALSE;
+       }
+       message->add_payload(message, (payload_t*)ke);
+
+       nonce = nonce_payload_create(PLV2_NONCE);
+       nonce->set_nonce(nonce, this->my_nonce);
+       message->add_payload(message, (payload_t*)nonce);
+       return TRUE;
+}
+
+METHOD(task_t, build_i_intermediate, status_t,
+       private_ike_init_t *this, message_t *message)
+{
+       key_exchange_method_t method;
+
+       message->set_exchange_type(message, IKE_INTERMEDIATE);
+
+       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;
+       }
+
+       chunk_free(&this->my_nonce);
+       if (!generate_nonce(this))
+       {
+               return FAILED;
+       }
+
+       if (!build_payloads_intermediate(this, message))
+       {
+               return FAILED;
+       }
+       return NEED_MORE;
+}
+
 METHOD(task_t, build_i, status_t,
        private_ike_init_t *this, message_t *message)
 {
@@ -657,9 +803,10 @@ METHOD(task_t, build_i, status_t,
        }
 
        /* if we are retrying after an INVALID_KE_PAYLOAD we already have one */
-       if (!this->dh)
+       if (!this->ke)
        {
-               if (this->old_sa && lib->settings->get_bool(lib->settings,
+               if (this->old_sa &&
+                       lib->settings->get_bool(lib->settings,
                                                                "%s.prefer_previous_dh_group", TRUE, lib->ns))
                {       /* reuse the DH group we used for the old IKE_SA when rekeying */
                        proposal_t *proposal;
@@ -669,37 +816,37 @@ METHOD(task_t, build_i, status_t,
                        if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD,
                                                                                &dh_group, NULL))
                        {
-                               this->dh_group = dh_group;
+                               this->ke_method = dh_group;
                        }
                        else
                        {       /* this shouldn't happen, but let's be safe */
-                               this->dh_group = ike_cfg->get_algorithm(ike_cfg,
-                                                                                                               KEY_EXCHANGE_METHOD);
+                               this->ke_method = ike_cfg->get_algorithm(ike_cfg,
+                                                                                                                KEY_EXCHANGE_METHOD);
                        }
                }
                else
                {
-                       this->dh_group = ike_cfg->get_algorithm(ike_cfg,
-                                                                                                       KEY_EXCHANGE_METHOD);
+                       this->ke_method = ike_cfg->get_algorithm(ike_cfg,
+                                                                                                        KEY_EXCHANGE_METHOD);
                }
-               this->dh = this->keymat->keymat.create_ke(&this->keymat->keymat,
-                                                                                                 this->dh_group);
-               if (!this->dh)
+               this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
+                                                                                                 this->ke_method);
+               if (!this->ke)
                {
                        DBG1(DBG_IKE, "configured DH group %N not supported",
-                               key_exchange_method_names, this->dh_group);
+                                key_exchange_method_names, this->ke_method);
                        return FAILED;
                }
        }
-       else if (this->dh->get_method(this->dh) != this->dh_group)
+       else if (this->ke->get_method(this->ke) != this->ke_method)
        {       /* reset DH instance if group changed (INVALID_KE_PAYLOAD) */
-               this->dh->destroy(this->dh);
-               this->dh = this->keymat->keymat.create_ke(&this->keymat->keymat,
-                                                                                                 this->dh_group);
-               if (!this->dh)
+               this->ke->destroy(this->ke);
+               this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
+                                                                                                 this->ke_method);
+               if (!this->ke)
                {
                        DBG1(DBG_IKE, "requested DH group %N not supported",
-                                key_exchange_method_names, this->dh_group);
+                                key_exchange_method_names, this->ke_method);
                        return FAILED;
                }
        }
@@ -736,6 +883,53 @@ METHOD(task_t, build_i, status_t,
        return NEED_MORE;
 }
 
+/**
+ * Process payloads in IKE_INTERMEDIATE exchanges
+ */
+static void process_payloads_intermediate(private_ike_init_t *this,
+                                                                                 message_t *message)
+{
+       ke_payload_t *ke;
+       nonce_payload_t *nonce;
+
+       chunk_clear(&this->other_nonce);
+       nonce = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE);
+       if (nonce)
+       {
+               this->other_nonce = nonce->get_nonce(nonce);
+       }
+       else
+       {
+               DBG1(DBG_IKE, "nonce payload missing in message");
+       }
+
+       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");
+       }
+}
+
+METHOD(task_t, process_r_intermediate, status_t,
+       private_ike_init_t *this, message_t *message)
+{
+       if (message->get_exchange_type(message) == IKE_INTERMEDIATE)
+       {
+               DESTROY_IF(this->ke);
+               chunk_clear(&this->my_nonce);
+               if (!generate_nonce(this))
+               {
+                       return FAILED;
+               }
+               process_payloads_intermediate(this, message);
+       }
+       return NEED_MORE;
+}
+
 METHOD(task_t, process_r,  status_t,
        private_ike_init_t *this, message_t *message)
 {
@@ -777,11 +971,8 @@ static bool derive_keys(private_ike_init_t *this,
        ike_sa_id_t *id;
 
        id = this->ike_sa->get_id(this->ike_sa);
-       if (this->old_sa)
-       {
-               /* rekeying: Include old SKd, use old PRF, apply SPI */
-               old_keymat = (keymat_v2_t*)this->old_sa->get_keymat(this->old_sa);
-               prf_alg = old_keymat->get_skd(old_keymat, &skd);
+       if (this->old_sa && this->old_sa != this->ike_sa)
+       {       /* only for rekeying, not for additional key exchanges */
                if (this->initiator)
                {
                        id->set_responder_spi(id, this->proposal->get_spi(this->proposal));
@@ -791,16 +982,62 @@ static bool derive_keys(private_ike_init_t *this,
                        id->set_initiator_spi(id, this->proposal->get_spi(this->proposal));
                }
        }
-       if (!this->keymat->derive_ike_keys(this->keymat, this->proposal, this->dh,
+       if (this->old_sa)
+       {
+               old_keymat = (keymat_v2_t*)this->old_sa->get_keymat(this->old_sa);
+               prf_alg = old_keymat->get_skd(old_keymat, &skd);
+       }
+
+       if (!this->keymat->derive_ike_keys(this->keymat, this->proposal, this->ke,
                                                                           nonce_i, nonce_r, id, prf_alg, skd))
        {
                return FALSE;
        }
-       charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, chunk_empty,
+       this->key_exchanges[this->ke_index++].done = TRUE;
+
+       charon->bus->ike_keys(charon->bus, this->ike_sa, this->ke, chunk_empty,
                                                  nonce_i, nonce_r, this->old_sa, NULL, AUTH_NONE);
        return TRUE;
 }
 
+METHOD(task_t, post_build_r, status_t,
+       private_ike_init_t *this, message_t *message)
+{
+       /* key derivation for additional key exchanges is like rekeying, so set our
+        * own SA as old SA to get SK_d */
+       this->old_sa = this->ike_sa;
+       if (!derive_keys(this, this->other_nonce, this->my_nonce))
+       {
+               DBG1(DBG_IKE, "%N key derivation failed", transform_type_names,
+                        this->key_exchanges[this->ke_index].type);
+               return FAILED;
+       }
+       return additional_key_exchange_required(this) ? NEED_MORE : SUCCESS;
+}
+
+METHOD(task_t, build_r_intermediate, status_t,
+       private_ike_init_t *this, message_t *message)
+{
+       if (!this->ke || !this->other_nonce.ptr)
+       {
+               message->add_notify(message, FALSE, INVALID_SYNTAX, chunk_empty);
+               return FAILED;
+       }
+       if (this->ke_failed)
+       {
+               message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               return FAILED;
+       }
+       if (!build_payloads_intermediate(this, message))
+       {
+               return FAILED;
+       }
+       /* we do the key derivation in post_build(), otherwise the response
+        * would already be generated using the new keys */
+       this->public.task.post_build = _post_build_r;
+       return NEED_MORE;
+}
+
 METHOD(task_t, build_r, status_t,
        private_ike_init_t *this, message_t *message)
 {
@@ -831,19 +1068,20 @@ METHOD(task_t, build_r, status_t,
                return FAILED;
        }
 
-       if (this->dh == NULL ||
+       if (!this->ke ||
                !this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
-                                                                          this->dh_group))
+                                                                          this->ke_method))
        {
                uint16_t group;
 
                if (this->proposal->get_algorithm(this->proposal, KEY_EXCHANGE_METHOD,
-                                                                                 &group, NULL))
+                                                                                 &group, NULL) &&
+                       this->ke_method != group)
                {
                        DBG1(DBG_IKE, "DH group %N unacceptable, requesting %N",
-                                key_exchange_method_names, this->dh_group,
+                                key_exchange_method_names, this->ke_method,
                                 key_exchange_method_names, group);
-                       this->dh_group = group;
+                       this->ke_method = group;
                        group = htons(group);
                        message->add_notify(message, FALSE, INVALID_KE_PAYLOAD,
                                                                chunk_from_thing(group));
@@ -856,7 +1094,7 @@ METHOD(task_t, build_r, status_t,
                return FAILED;
        }
 
-       if (this->dh_failed)
+       if (this->ke_failed)
        {
                DBG1(DBG_IKE, "applying DH public value failed");
                message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
@@ -874,6 +1112,12 @@ METHOD(task_t, build_r, status_t,
                message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
                return FAILED;
        }
+       if (additional_key_exchange_required(this))
+       {       /* use IKE_INTERMEDIATE for additional key exchanges */
+               this->public.task.build = _build_r_intermediate;
+               this->public.task.process = _process_r_intermediate;
+               return NEED_MORE;
+       }
        return SUCCESS;
 }
 
@@ -960,6 +1204,36 @@ METHOD(task_t, pre_process_i, status_t,
        return SUCCESS;
 }
 
+METHOD(task_t, post_process_i, status_t,
+       private_ike_init_t *this, message_t *message)
+{
+       /* key derivation for additional key exchanges is like rekeying, so set our
+        * own SA as old SA to get SK_d */
+       this->old_sa = this->ike_sa;
+       if (!derive_keys(this, this->my_nonce, this->other_nonce))
+       {
+               DBG1(DBG_IKE, "%N key derivation failed", transform_type_names,
+                        this->key_exchanges[this->ke_index].type);
+               return FAILED;
+       }
+       return additional_key_exchange_required(this) ? NEED_MORE : SUCCESS;
+}
+
+METHOD(task_t, process_i_intermediate, status_t,
+       private_ike_init_t *this, message_t *message)
+{
+       process_payloads_intermediate(this, message);
+
+       if (this->ke_failed || !this->other_nonce.ptr)
+       {
+               return FAILED;
+       }
+       /* we do the key derivation in post_process(), otherwise calculating IntAuth
+        * would be done with the wrong keys */
+       this->public.task.post_process = _post_process_i;
+       return NEED_MORE;
+}
+
 METHOD(task_t, process_i, status_t,
        private_ike_init_t *this, message_t *message)
 {
@@ -982,14 +1256,14 @@ METHOD(task_t, process_i, status_t,
                                        chunk_t data;
                                        key_exchange_method_t bad_group;
 
-                                       bad_group = this->dh_group;
+                                       bad_group = this->ke_method;
                                        data = notify->get_notification_data(notify);
-                                       this->dh_group = ntohs(*((uint16_t*)data.ptr));
+                                       this->ke_method = ntohs(*((uint16_t*)data.ptr));
                                        DBG1(DBG_IKE, "peer didn't accept DH group %N, "
                                                 "it requested %N", key_exchange_method_names,
-                                                bad_group, key_exchange_method_names, this->dh_group);
+                                                bad_group, key_exchange_method_names, this->ke_method);
 
-                                       if (this->old_sa == NULL)
+                                       if (!this->old_sa)
                                        {       /* reset the IKE_SA if we are not rekeying */
                                                this->ike_sa->reset(this->ike_sa, FALSE);
                                        }
@@ -1063,19 +1337,18 @@ METHOD(task_t, process_i, status_t,
        if (this->proposal == NULL ||
                this->other_nonce.len == 0 || this->my_nonce.len == 0)
        {
-               DBG1(DBG_IKE, "peers proposal selection invalid");
+               DBG1(DBG_IKE, "peer's proposal selection invalid");
                return FAILED;
        }
 
-       if (this->dh == NULL ||
-               !this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
-                                                                          this->dh_group))
+       if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
+                                                                          this->ke_method))
        {
-               DBG1(DBG_IKE, "peer DH group selection invalid");
+               DBG1(DBG_IKE, "peer's DH group selection invalid");
                return FAILED;
        }
 
-       if (this->dh_failed)
+       if (this->ke_failed)
        {
                DBG1(DBG_IKE, "applying DH public value failed");
                return FAILED;
@@ -1086,6 +1359,12 @@ METHOD(task_t, process_i, status_t,
                DBG1(DBG_IKE, "key derivation failed");
                return FAILED;
        }
+       if (additional_key_exchange_required(this))
+       {       /* use IKE_INTERMEDIATE for additional key exchanges */
+               this->public.task.build = _build_i_intermediate;
+               this->public.task.process = _process_i_intermediate;
+               return NEED_MORE;
+       }
        return SUCCESS;
 }
 
@@ -1104,13 +1383,14 @@ METHOD(task_t, migrate, void,
        this->ike_sa = ike_sa;
        this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
        this->proposal = NULL;
-       this->dh_failed = FALSE;
+       this->ke_failed = FALSE;
+       clear_key_exchanges(this);
 }
 
 METHOD(task_t, destroy, void,
        private_ike_init_t *this)
 {
-       DESTROY_IF(this->dh);
+       DESTROY_IF(this->ke);
        DESTROY_IF(this->proposal);
        DESTROY_IF(this->nonceg);
        chunk_free(&this->my_nonce);
@@ -1151,7 +1431,7 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa)
                },
                .ike_sa = ike_sa,
                .initiator = initiator,
-               .dh_group = MODP_NONE,
+               .ke_method = MODP_NONE,
                .keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa),
                .old_sa = old_sa,
                .signature_authentication = lib->settings->get_bool(lib->settings,