Cleaned up quick mode notify processing
[strongswan.git] / src / libcharon / sa / tasks / quick_mode.c
index 353a1db..6b6a935 100644 (file)
@@ -21,7 +21,9 @@
 #include <sa/keymat_v1.h>
 #include <encoding/payloads/sa_payload.h>
 #include <encoding/payloads/nonce_payload.h>
+#include <encoding/payloads/ke_payload.h>
 #include <encoding/payloads/id_payload.h>
+#include <encoding/payloads/payload.h>
 
 typedef struct private_quick_mode_t private_quick_mode_t;
 
@@ -96,6 +98,11 @@ struct private_quick_mode_t {
        keymat_v1_t *keymat;
 
        /**
+        * DH exchange, when PFS is in use
+        */
+       diffie_hellman_t *dh;
+
+       /**
         * Negotiated lifetime of new SA
         */
        u_int32_t lifetime;
@@ -133,7 +140,7 @@ static bool install(private_quick_mode_t *this)
        tsr = linked_list_create();
        tsi->insert_last(tsi, this->tsi);
        tsr->insert_last(tsr, this->tsr);
-       if (this->keymat->derive_child_keys(this->keymat, this->proposal, NULL,
+       if (this->keymat->derive_child_keys(this->keymat, this->proposal, this->dh,
                                                this->spi_i, this->spi_r, this->nonce_i, this->nonce_r,
                                                &encr_i, &integ_i, &encr_r, &integ_r))
        {
@@ -185,7 +192,7 @@ static bool install(private_quick_mode_t *this)
        }
 
        charon->bus->child_keys(charon->bus, this->child_sa, this->initiator,
-                                                       NULL, this->nonce_i, this->nonce_r);
+                                                       this->dh, this->nonce_i, this->nonce_r);
 
        /* add to IKE_SA, and remove from task */
        this->child_sa->set_state(this->child_sa, CHILD_INSTALLED);
@@ -252,6 +259,35 @@ static bool get_nonce(private_quick_mode_t *this, chunk_t *nonce,
 }
 
 /**
+ * Add KE payload to message
+ */
+static void add_ke(private_quick_mode_t *this, message_t *message)
+{
+       ke_payload_t *ke_payload;
+
+       ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE_V1, this->dh);
+       message->add_payload(message, &ke_payload->payload_interface);
+}
+
+/**
+ * Get DH value from a KE payload
+ */
+static bool get_ke(private_quick_mode_t *this, message_t *message)
+{
+       ke_payload_t *ke_payload;
+
+       ke_payload = (ke_payload_t*)message->get_payload(message, KEY_EXCHANGE_V1);
+       if (!ke_payload)
+       {
+               DBG1(DBG_IKE, "KE payload missing");
+               return FALSE;
+       }
+       this->dh->set_other_public_value(this->dh,
+                                                               ke_payload->get_key_exchange_data(ke_payload));
+       return TRUE;
+}
+
+/**
  * Select a traffic selector from configuration
  */
 static traffic_selector_t* select_ts(private_quick_mode_t *this, bool initiator)
@@ -404,6 +440,34 @@ static bool get_ts(private_quick_mode_t *this, message_t *message)
 }
 
 /**
+ * Add NAT-OA payloads
+ */
+static void add_nat_oa_payloads(private_quick_mode_t *this, message_t *message)
+{
+       identification_t *id;
+       id_payload_t *nat_oa;
+       host_t *src, *dst;
+
+       src = message->get_source(message);
+       dst = message->get_destination(message);
+
+       src = this->initiator ? src : dst;
+       dst = this->initiator ? dst : src;
+
+       /* first NAT-OA is the initiator's address */
+       id = identification_create_from_sockaddr(src->get_sockaddr(src));
+       nat_oa = id_payload_create_from_identification(NAT_OA_V1, id);
+       message->add_payload(message, (payload_t*)nat_oa);
+       id->destroy(id);
+
+       /* second NAT-OA is that of the responder */
+       id = identification_create_from_sockaddr(dst->get_sockaddr(dst));
+       nat_oa = id_payload_create_from_identification(NAT_OA_V1, id);
+       message->add_payload(message, (payload_t*)nat_oa);
+       id->destroy(id);
+}
+
+/**
  * Look up lifetimes
  */
 static void get_lifetimes(private_quick_mode_t *this)
@@ -457,13 +521,16 @@ METHOD(task_t, build_i, status_t,
                        sa_payload_t *sa_payload;
                        linked_list_t *list;
                        proposal_t *proposal;
+                       ipsec_mode_t mode;
+                       diffie_hellman_group_t group;
+                       bool udp = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
 
                        this->child_sa = child_sa_create(
                                                                        this->ike_sa->get_my_host(this->ike_sa),
                                                                        this->ike_sa->get_other_host(this->ike_sa),
-                                                                       this->config, 0, FALSE);
+                                                                       this->config, 0, udp);
 
-                       list = this->config->get_proposals(this->config, TRUE);
+                       list = this->config->get_proposals(this->config, FALSE);
 
                        this->spi_i = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP);
                        if (!this->spi_i)
@@ -478,10 +545,17 @@ METHOD(task_t, build_i, status_t,
                        }
                        enumerator->destroy(enumerator);
 
+                       mode = this->config->get_mode(this->config);
+                       if (udp && mode == MODE_TRANSPORT)
+                       {
+                               /* TODO-IKEv1: disable NAT-T for TRANSPORT mode by default? */
+                               add_nat_oa_payloads(this, message);
+                       }
+
                        get_lifetimes(this);
                        sa_payload = sa_payload_create_from_proposals_v1(list,
                                                                this->lifetime, this->lifebytes, AUTH_NONE,
-                                                               this->config->get_mode(this->config), FALSE);
+                                                               mode, udp);
                        list->destroy_offset(list, offsetof(proposal_t, destroy));
                        message->add_payload(message, &sa_payload->payload_interface);
 
@@ -489,6 +563,20 @@ METHOD(task_t, build_i, status_t,
                        {
                                return FAILED;
                        }
+
+                       group = this->config->get_dh_group(this->config);
+                       if (group != MODP_NONE)
+                       {
+                               this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat,
+                                                                                                                 group);
+                               if (!this->dh)
+                               {
+                                       DBG1(DBG_IKE, "configured DH group %N not supported",
+                                                diffie_hellman_group_names, group);
+                                       return FAILED;
+                               }
+                               add_ke(this, message);
+                       }
                        this->tsi = select_ts(this, TRUE);
                        this->tsr = select_ts(this, FALSE);
                        if (!this->tsi || !this->tsr)
@@ -507,6 +595,42 @@ METHOD(task_t, build_i, status_t,
        }
 }
 
+/**
+ * Check for notify errors, return TRUE if error found
+ */
+static bool has_notify_errors(private_quick_mode_t *this, message_t *message)
+{
+       enumerator_t *enumerator;
+       payload_t *payload;
+       bool err = FALSE;
+
+       enumerator = message->create_payload_enumerator(message);
+       while (enumerator->enumerate(enumerator, &payload))
+       {
+               if (payload->get_type(payload) == NOTIFY_V1)
+               {
+                       notify_payload_t *notify;
+                       notify_type_t type;
+
+                       notify = (notify_payload_t*)payload;
+                       type = notify->get_notify_type(notify);
+                       if (type < 16384)
+                       {
+                               DBG1(DBG_IKE, "received %N error notify",
+                                        notify_type_names, type);
+                               err = TRUE;
+                       }
+                       else
+                       {
+                               DBG1(DBG_IKE, "received %N notify", notify_type_names, type);
+                       }
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       return err;
+}
+
 METHOD(task_t, process_r, status_t,
        private_quick_mode_t *this, message_t *message)
 {
@@ -518,6 +642,8 @@ METHOD(task_t, process_r, status_t,
                        linked_list_t *tsi, *tsr, *list;
                        peer_cfg_t *peer_cfg;
                        host_t *me, *other;
+                       u_int16_t group;
+                       bool udp = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
 
                        if (!get_ts(this, message))
                        {
@@ -557,7 +683,7 @@ METHOD(task_t, process_r, status_t,
                        }
                        list = sa_payload->get_proposals(sa_payload);
                        this->proposal = this->config->select_proposal(this->config,
-                                                                                                                  list, TRUE, FALSE);
+                                                                                                                  list, FALSE, FALSE);
                        list->destroy_offset(list, offsetof(proposal_t, destroy));
 
                        get_lifetimes(this);
@@ -575,15 +701,35 @@ METHOD(task_t, process_r, status_t,
                                return FAILED;
                        }
 
+                       if (this->proposal->get_algorithm(this->proposal,
+                                                                               DIFFIE_HELLMAN_GROUP, &group, NULL))
+                       {
+                               this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat,
+                                                                                                                 group);
+                               if (!this->dh)
+                               {
+                                       DBG1(DBG_IKE, "negotiated DH group %N not supported",
+                                                diffie_hellman_group_names, group);
+                                       return FAILED;
+                               }
+                               if (!get_ke(this, message))
+                               {
+                                       return FAILED;
+                               }
+                       }
 
                        this->child_sa = child_sa_create(
                                                                        this->ike_sa->get_my_host(this->ike_sa),
                                                                        this->ike_sa->get_other_host(this->ike_sa),
-                                                                       this->config, 0, FALSE);
+                                                                       this->config, 0, udp);
                        return NEED_MORE;
                }
                case QM_NEGOTIATED:
                {
+                       if (has_notify_errors(this, message))
+                       {
+                               return FAILED;
+                       }
                        if (!install(this))
                        {
                                return FAILED;
@@ -603,6 +749,8 @@ METHOD(task_t, build_r, status_t,
                case QM_INIT:
                {
                        sa_payload_t *sa_payload;
+                       ipsec_mode_t mode;
+                       bool udp = this->child_sa->has_encap(this->child_sa);
 
                        this->spi_r = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP);
                        if (!this->spi_r)
@@ -612,15 +760,27 @@ METHOD(task_t, build_r, status_t,
                        }
                        this->proposal->set_spi(this->proposal, this->spi_r);
 
+                       mode = this->config->get_mode(this->config);
+                       if (udp && mode == MODE_TRANSPORT)
+                       {
+                               /* TODO-IKEv1: disable NAT-T for TRANSPORT mode by default? */
+                               add_nat_oa_payloads(this, message);
+                       }
+
                        sa_payload = sa_payload_create_from_proposal_v1(this->proposal,
                                                                this->lifetime, this->lifebytes, AUTH_NONE,
-                                                               this->config->get_mode(this->config), FALSE);
+                                                               mode, udp);
                        message->add_payload(message, &sa_payload->payload_interface);
 
                        if (!add_nonce(this, &this->nonce_r, message))
                        {
                                return FAILED;
                        }
+                       if (this->dh)
+                       {
+                               add_ke(this, message);
+                       }
+
                        add_ts(this, message);
 
                        this->state = QM_NEGOTIATED;
@@ -650,7 +810,7 @@ METHOD(task_t, process_i, status_t,
                        }
                        list = sa_payload->get_proposals(sa_payload);
                        this->proposal = this->config->select_proposal(this->config,
-                                                                                                                  list, TRUE, FALSE);
+                                                                                                                  list, FALSE, FALSE);
                        list->destroy_offset(list, offsetof(proposal_t, destroy));
                        if (!this->proposal)
                        {
@@ -665,6 +825,10 @@ METHOD(task_t, process_i, status_t,
                        {
                                return FAILED;
                        }
+                       if (this->dh && !get_ke(this, message))
+                       {
+                               return FAILED;
+                       }
                        if (!get_ts(this, message))
                        {
                                return FAILED;
@@ -703,6 +867,7 @@ METHOD(task_t, destroy, void,
        DESTROY_IF(this->proposal);
        DESTROY_IF(this->child_sa);
        DESTROY_IF(this->config);
+       DESTROY_IF(this->dh);
        free(this);
 }