ike-init: Send USE_PPK notify as appropriate
[strongswan.git] / src / libcharon / sa / ikev2 / tasks / ike_init.c
index ceae484..28e28e4 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (C) 2008-2009 Tobias Brunner
+ * Copyright (C) 2008-2018 Tobias Brunner
  * Copyright (C) 2005-2008 Martin Willi
  * Copyright (C) 2005 Jan Hutter
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
 #include <string.h>
 
 #include <daemon.h>
+#include <bio/bio_reader.h>
+#include <bio/bio_writer.h>
 #include <sa/ikev2/keymat_v2.h>
 #include <crypto/diffie_hellman.h>
+#include <crypto/hashers/hash_algorithm_set.h>
 #include <encoding/payloads/sa_payload.h>
 #include <encoding/payloads/ke_payload.h>
 #include <encoding/payloads/nonce_payload.h>
@@ -52,11 +55,6 @@ struct private_ike_init_t {
        bool initiator;
 
        /**
-        * IKE config to establish
-        */
-       ike_cfg_t *config;
-
-       /**
         * diffie hellman group to use
         */
        diffie_hellman_group_t dh_group;
@@ -67,6 +65,11 @@ struct private_ike_init_t {
        diffie_hellman_t *dh;
 
        /**
+        * Applying DH public value failed?
+        */
+       bool dh_failed;
+
+       /**
         * Keymat derivation (from IKE_SA)
         */
        keymat_v2_t *keymat;
@@ -82,6 +85,11 @@ struct private_ike_init_t {
        chunk_t other_nonce;
 
        /**
+        * nonce generator
+        */
+       nonce_gen_t *nonceg;
+
+       /**
         * Negotiated proposal used for IKE_SA
         */
        proposal_t *proposal;
@@ -100,38 +108,245 @@ struct private_ike_init_t {
         * retries done so far after failure (cookie or bad dh group)
         */
        u_int retry;
+
+       /**
+        * Whether to use Signature Authentication as per RFC 7427
+        */
+       bool signature_authentication;
+
+       /**
+        * Whether to follow IKEv2 redirects as per RFC 5685
+        */
+       bool follow_redirects;
 };
 
 /**
+ * Allocate our own nonce value
+ */
+static bool generate_nonce(private_ike_init_t *this)
+{
+       if (!this->nonceg)
+       {
+               DBG1(DBG_IKE, "no nonce generator found to create nonce");
+               return FALSE;
+       }
+       if (!this->nonceg->allocate_nonce(this->nonceg, NONCE_SIZE,
+                                                                         &this->my_nonce))
+       {
+               DBG1(DBG_IKE, "nonce allocation failed");
+               return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * Notify the peer about the hash algorithms we support or expect,
+ * as per RFC 7427
+ */
+static void send_supported_hash_algorithms(private_ike_init_t *this,
+                                                                                  message_t *message)
+{
+       hash_algorithm_set_t *algos;
+       enumerator_t *enumerator, *rounds;
+       bio_writer_t *writer;
+       hash_algorithm_t hash;
+       peer_cfg_t *peer;
+       auth_cfg_t *auth;
+       auth_rule_t rule;
+       signature_params_t *config;
+       int written;
+       size_t len = BUF_LEN;
+       char buf[len];
+       char *pos = buf;
+       char *plugin_name;
+
+       algos = hash_algorithm_set_create();
+       peer = this->ike_sa->get_peer_cfg(this->ike_sa);
+       if (peer)
+       {
+               rounds = peer->create_auth_cfg_enumerator(peer, FALSE);
+               while (rounds->enumerate(rounds, &auth))
+               {
+                       enumerator = auth->create_enumerator(auth);
+                       while (enumerator->enumerate(enumerator, &rule, &config))
+                       {
+                               if (rule == AUTH_RULE_IKE_SIGNATURE_SCHEME)
+                               {
+                                       hash = hasher_from_signature_scheme(config->scheme,
+                                                                                                               config->params);
+                                       if (hasher_algorithm_for_ikev2(hash))
+                                       {
+                                               algos->add(algos, hash);
+                                       }
+                               }
+                       }
+                       enumerator->destroy(enumerator);
+               }
+               rounds->destroy(rounds);
+       }
+
+       if (!algos->count(algos))
+       {
+               enumerator = lib->crypto->create_hasher_enumerator(lib->crypto);
+               while (enumerator->enumerate(enumerator, &hash, &plugin_name))
+               {
+                       if (hasher_algorithm_for_ikev2(hash))
+                       {
+                               algos->add(algos, hash);
+                       }
+               }
+               enumerator->destroy(enumerator);
+       }
+
+       if (algos->count(algos))
+       {
+               writer = bio_writer_create(0);
+               enumerator = algos->create_enumerator(algos);
+               while (enumerator->enumerate(enumerator, &hash))
+               {
+                       writer->write_uint16(writer, hash);
+
+                       /* generate debug output */
+                       written = snprintf(pos, len, " %N", hash_algorithm_short_names,
+                                                          hash);
+                       if (written > 0 && written < len)
+                       {
+                               pos += written;
+                               len -= written;
+                       }
+               }
+               enumerator->destroy(enumerator);
+               message->add_notify(message, FALSE, SIGNATURE_HASH_ALGORITHMS,
+                                                       writer->get_buf(writer));
+               writer->destroy(writer);
+
+               *pos = '\0';
+               DBG2(DBG_CFG, "sending supported signature hash algorithms:%s", buf);
+       }
+       algos->destroy(algos);
+}
+
+/**
+ * Store algorithms supported by other peer
+ */
+static void handle_supported_hash_algorithms(private_ike_init_t *this,
+                                                                                        notify_payload_t *notify)
+{
+       bio_reader_t *reader;
+       uint16_t algo;
+       int written;
+       size_t len = BUF_LEN;
+       char buf[len];
+       char *pos = buf;
+       bool added = FALSE;
+
+       reader = bio_reader_create(notify->get_notification_data(notify));
+       while (reader->remaining(reader) >= 2 && reader->read_uint16(reader, &algo))
+       {
+               if (hasher_algorithm_for_ikev2(algo))
+               {
+                       this->keymat->add_hash_algorithm(this->keymat, algo);
+                       added = TRUE;
+
+                       /* generate debug output */
+                       written = snprintf(pos, len, " %N", hash_algorithm_short_names,
+                                                          algo);
+                       if (written > 0 && written < len)
+                       {
+                               pos += written;
+                               len -= written;
+                       }
+               }
+       }
+       reader->destroy(reader);
+
+       *pos = '\0';
+       DBG2(DBG_CFG, "received supported signature hash algorithms:%s", buf);
+
+       if (added)
+       {
+               this->ike_sa->enable_extension(this->ike_sa, EXT_SIGNATURE_AUTH);
+       }
+}
+
+/**
+ * Check whether to send a USE_PPK notify
+ */
+static bool send_use_ppk(private_ike_init_t *this)
+{
+       peer_cfg_t *peer;
+       enumerator_t *keys;
+       shared_key_t *key;
+       bool use_ppk = FALSE;
+
+       if (this->initiator)
+       {
+               peer = this->ike_sa->get_peer_cfg(this->ike_sa);
+               if (peer->get_ppk_id(peer))
+               {
+                       use_ppk = TRUE;
+               }
+       }
+       else if (this->ike_sa->supports_extension(this->ike_sa, EXT_PPK))
+       {
+               /* check if we have at least one PPK available */
+               keys = lib->credmgr->create_shared_enumerator(lib->credmgr, SHARED_PPK,
+                                                                                                         NULL, NULL);
+               if (keys->enumerate(keys, &key, NULL, NULL))
+               {
+                       use_ppk = TRUE;
+               }
+               keys->destroy(keys);
+       }
+       return use_ppk;
+}
+
+/**
  * build the payloads for the message
  */
-static void build_payloads(private_ike_init_t *this, message_t *message)
+static bool build_payloads(private_ike_init_t *this, message_t *message)
 {
        sa_payload_t *sa_payload;
        ke_payload_t *ke_payload;
        nonce_payload_t *nonce_payload;
-       linked_list_t *proposal_list;
+       linked_list_t *proposal_list, *other_dh_groups;
        ike_sa_id_t *id;
        proposal_t *proposal;
        enumerator_t *enumerator;
+       ike_cfg_t *ike_cfg;
 
        id = this->ike_sa->get_id(this->ike_sa);
 
-       this->config = this->ike_sa->get_ike_cfg(this->ike_sa);
+       ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
 
        if (this->initiator)
        {
-               proposal_list = this->config->get_proposals(this->config);
-               if (this->old_sa)
+               proposal_list = ike_cfg->get_proposals(ike_cfg);
+               other_dh_groups = linked_list_create();
+               enumerator = proposal_list->create_enumerator(proposal_list);
+               while (enumerator->enumerate(enumerator, (void**)&proposal))
                {
                        /* include SPI of new IKE_SA when we are rekeying */
-                       enumerator = proposal_list->create_enumerator(proposal_list);
-                       while (enumerator->enumerate(enumerator, (void**)&proposal))
+                       if (this->old_sa)
                        {
                                proposal->set_spi(proposal, id->get_initiator_spi(id));
                        }
-                       enumerator->destroy(enumerator);
+                       /* move the selected DH group to the front of the proposal */
+                       if (!proposal->promote_dh_group(proposal, this->dh_group))
+                       {       /* 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);
+                       }
                }
+               enumerator->destroy(enumerator);
+               /* add proposals that don't contain the selected group */
+               enumerator = other_dh_groups->create_enumerator(other_dh_groups);
+               while (enumerator->enumerate(enumerator, (void**)&proposal))
+               {       /* no need to remove from the list as we destroy it anyway*/
+                       proposal_list->insert_last(proposal_list, proposal);
+               }
+               enumerator->destroy(enumerator);
+               other_dh_groups->destroy(other_dh_groups);
 
                sa_payload = sa_payload_create_from_proposals_v2(proposal_list);
                proposal_list->destroy_offset(proposal_list, offsetof(proposal_t, destroy));
@@ -147,9 +362,15 @@ static void build_payloads(private_ike_init_t *this, message_t *message)
        }
        message->add_payload(message, (payload_t*)sa_payload);
 
-       nonce_payload = nonce_payload_create(NONCE);
+       nonce_payload = nonce_payload_create(PLV2_NONCE);
        nonce_payload->set_nonce(nonce_payload, this->my_nonce);
-       ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE, this->dh);
+       ke_payload = ke_payload_create_from_diffie_hellman(PLV2_KEY_EXCHANGE,
+                                                                                                          this->dh);
+       if (!ke_payload)
+       {
+               DBG1(DBG_IKE, "creating KE payload failed");
+               return FALSE;
+       }
 
        if (this->old_sa)
        {       /* payload order differs if we are rekeying */
@@ -161,6 +382,120 @@ static void build_payloads(private_ike_init_t *this, message_t *message)
                message->add_payload(message, (payload_t*)ke_payload);
                message->add_payload(message, (payload_t*)nonce_payload);
        }
+
+       /* negotiate fragmentation if we are not rekeying */
+       if (!this->old_sa &&
+                ike_cfg->fragmentation(ike_cfg) != FRAGMENTATION_NO)
+       {
+               if (this->initiator ||
+                       this->ike_sa->supports_extension(this->ike_sa,
+                                                                                        EXT_IKE_FRAGMENTATION))
+               {
+                       message->add_notify(message, FALSE, FRAGMENTATION_SUPPORTED,
+                                                               chunk_empty);
+               }
+       }
+       /* submit supported hash algorithms for signature authentication */
+       if (!this->old_sa && this->signature_authentication)
+       {
+               if (this->initiator ||
+                       this->ike_sa->supports_extension(this->ike_sa,
+                                                                                        EXT_SIGNATURE_AUTH))
+               {
+                       send_supported_hash_algorithms(this, message);
+               }
+       }
+       /* notify other peer if we support redirection */
+       if (!this->old_sa && this->initiator && this->follow_redirects)
+       {
+               identification_t *gateway;
+               host_t *from;
+               chunk_t data;
+
+               from = this->ike_sa->get_redirected_from(this->ike_sa);
+               if (from)
+               {
+                       gateway = identification_create_from_sockaddr(
+                                                                                                       from->get_sockaddr(from));
+                       data = redirect_data_create(gateway, chunk_empty);
+                       message->add_notify(message, FALSE, REDIRECTED_FROM, data);
+                       chunk_free(&data);
+                       gateway->destroy(gateway);
+               }
+               else
+               {
+                       message->add_notify(message, FALSE, REDIRECT_SUPPORTED,
+                                                               chunk_empty);
+               }
+       }
+       /* notify the peer if we want to use/support PPK */
+       if (!this->old_sa && send_use_ppk(this))
+       {
+               message->add_notify(message, FALSE, USE_PPK, chunk_empty);
+       }
+       return TRUE;
+}
+
+/**
+ * Process the SA payload and select a proposal
+ */
+static void process_sa_payload(private_ike_init_t *this, message_t *message,
+                                                          sa_payload_t *sa_payload)
+{
+       ike_cfg_t *ike_cfg, *cfg, *alt_cfg = NULL;
+       enumerator_t *enumerator;
+       linked_list_t *proposal_list;
+       host_t *me, *other;
+       bool private, prefer_configured;
+
+       ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
+
+       proposal_list = sa_payload->get_proposals(sa_payload);
+       private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN);
+       prefer_configured = lib->settings->get_bool(lib->settings,
+                                                       "%s.prefer_configured_proposals", TRUE, lib->ns);
+
+       this->proposal = ike_cfg->select_proposal(ike_cfg, proposal_list, private,
+                                                                                         prefer_configured);
+       if (!this->proposal)
+       {
+               if (!this->initiator && !this->old_sa)
+               {
+                       me = message->get_destination(message);
+                       other = message->get_source(message);
+                       enumerator = charon->backends->create_ike_cfg_enumerator(
+                                                                                       charon->backends, me, other, IKEV2);
+                       while (enumerator->enumerate(enumerator, &cfg))
+                       {
+                               if (ike_cfg == cfg)
+                               {       /* already tried and failed */
+                                       continue;
+                               }
+                               DBG1(DBG_IKE, "no matching proposal found, trying alternative "
+                                        "config");
+                               this->proposal = cfg->select_proposal(cfg, proposal_list,
+                                                                                                       private, prefer_configured);
+                               if (this->proposal)
+                               {
+                                       alt_cfg = cfg->get_ref(cfg);
+                                       break;
+                               }
+                       }
+                       enumerator->destroy(enumerator);
+               }
+               if (alt_cfg)
+               {
+                       this->ike_sa->set_ike_cfg(this->ike_sa, alt_cfg);
+                       alt_cfg->destroy(alt_cfg);
+               }
+               else
+               {
+                       charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_IKE,
+                                                          proposal_list);
+               }
+       }
+       proposal_list->destroy_offset(proposal_list,
+                                                                 offsetof(proposal_t, destroy));
 }
 
 /**
@@ -170,64 +505,123 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
 {
        enumerator_t *enumerator;
        payload_t *payload;
+       ke_payload_t *ke_payload = NULL;
 
        enumerator = message->create_payload_enumerator(message);
        while (enumerator->enumerate(enumerator, &payload))
        {
                switch (payload->get_type(payload))
                {
-                       case SECURITY_ASSOCIATION:
+                       case PLV2_SECURITY_ASSOCIATION:
                        {
-                               sa_payload_t *sa_payload = (sa_payload_t*)payload;
-                               linked_list_t *proposal_list;
-                               bool private;
-
-                               proposal_list = sa_payload->get_proposals(sa_payload);
-                               private = this->ike_sa->supports_extension(this->ike_sa,
-                                                                                                                  EXT_STRONGSWAN);
-                               this->proposal = this->config->select_proposal(this->config,
-                                                                                                               proposal_list, private);
-                               proposal_list->destroy_offset(proposal_list,
-                                                                                         offsetof(proposal_t, destroy));
+                               process_sa_payload(this, message, (sa_payload_t*)payload);
                                break;
                        }
-                       case KEY_EXCHANGE:
+                       case PLV2_KEY_EXCHANGE:
                        {
-                               ke_payload_t *ke_payload = (ke_payload_t*)payload;
+                               ke_payload = (ke_payload_t*)payload;
 
                                this->dh_group = ke_payload->get_dh_group_number(ke_payload);
-                               if (!this->initiator)
-                               {
-                                       this->dh = this->keymat->keymat.create_dh(
-                                                                               &this->keymat->keymat, this->dh_group);
-                               }
-                               if (this->dh)
-                               {
-                                       this->dh->set_other_public_value(this->dh,
-                                                               ke_payload->get_key_exchange_data(ke_payload));
-                               }
                                break;
                        }
-                       case NONCE:
+                       case PLV2_NONCE:
                        {
                                nonce_payload_t *nonce_payload = (nonce_payload_t*)payload;
 
                                this->other_nonce = nonce_payload->get_nonce(nonce_payload);
                                break;
                        }
+                       case PLV2_NOTIFY:
+                       {
+                               notify_payload_t *notify = (notify_payload_t*)payload;
+
+                               switch (notify->get_notify_type(notify))
+                               {
+                                       case FRAGMENTATION_SUPPORTED:
+                                               this->ike_sa->enable_extension(this->ike_sa,
+                                                                                                          EXT_IKE_FRAGMENTATION);
+                                               break;
+                                       case SIGNATURE_HASH_ALGORITHMS:
+                                               if (this->signature_authentication)
+                                               {
+                                                       handle_supported_hash_algorithms(this, notify);
+                                               }
+                                               break;
+                                       case USE_PPK:
+                                               if (!this->old_sa)
+                                               {
+                                                       this->ike_sa->enable_extension(this->ike_sa,
+                                                                                                                  EXT_PPK);
+                                               }
+                                               break;
+                                       case REDIRECTED_FROM:
+                                       {
+                                               identification_t *gateway;
+                                               chunk_t data;
+
+                                               data = notify->get_notification_data(notify);
+                                               gateway = redirect_data_parse(data, NULL);
+                                               if (!gateway)
+                                               {
+                                                       DBG1(DBG_IKE, "received invalid REDIRECTED_FROM "
+                                                                "notify, ignored");
+                                                       break;
+                                               }
+                                               DBG1(DBG_IKE, "client got redirected from %Y", gateway);
+                                               gateway->destroy(gateway);
+                                               /* fall-through */
+                                       }
+                                       case REDIRECT_SUPPORTED:
+                                               if (!this->old_sa)
+                                               {
+                                                       this->ike_sa->enable_extension(this->ike_sa,
+                                                                                                                  EXT_IKE_REDIRECTION);
+                                               }
+                                               break;
+                                       default:
+                                               /* other notifies are handled elsewhere */
+                                               break;
+                               }
+
+                       }
                        default:
                                break;
                }
        }
        enumerator->destroy(enumerator);
+
+       if (this->proposal)
+       {
+               this->ike_sa->set_proposal(this->ike_sa, this->proposal);
+       }
+
+       if (ke_payload && this->proposal &&
+               this->proposal->has_dh_group(this->proposal, this->dh_group))
+       {
+               if (!this->initiator)
+               {
+                       this->dh = this->keymat->keymat.create_dh(
+                                                               &this->keymat->keymat, this->dh_group);
+               }
+               else if (this->dh)
+               {
+                       this->dh_failed = this->dh->get_dh_group(this->dh) != this->dh_group;
+               }
+               if (this->dh && !this->dh_failed)
+               {
+                       this->dh_failed = !this->dh->set_other_public_value(this->dh,
+                                                               ke_payload->get_key_exchange_data(ke_payload));
+               }
+       }
 }
 
 METHOD(task_t, build_i, status_t,
        private_ike_init_t *this, message_t *message)
 {
-       rng_t *rng;
+       ike_cfg_t *ike_cfg;
+
+       ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
 
-       this->config = this->ike_sa->get_ike_cfg(this->ike_sa);
        DBG0(DBG_IKE, "initiating IKE_SA %s[%d] to %H",
                 this->ike_sa->get_name(this->ike_sa),
                 this->ike_sa->get_unique_id(this->ike_sa),
@@ -240,10 +634,30 @@ METHOD(task_t, build_i, status_t,
                return FAILED;
        }
 
-       /* if the DH group is set via use_dh_group(), we already have a DH object */
+       /* if we are retrying after an INVALID_KE_PAYLOAD we already have one */
        if (!this->dh)
        {
-               this->dh_group = this->config->get_dh_group(this->config);
+               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;
+                       uint16_t dh_group;
+
+                       proposal = this->old_sa->get_proposal(this->old_sa);
+                       if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP,
+                                                                               &dh_group, NULL))
+                       {
+                               this->dh_group = dh_group;
+                       }
+                       else
+                       {       /* this shouldn't happen, but let's be safe */
+                               this->dh_group = ike_cfg->get_dh_group(ike_cfg);
+                       }
+               }
+               else
+               {
+                       this->dh_group = ike_cfg->get_dh_group(ike_cfg);
+               }
                this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat,
                                                                                                  this->dh_group);
                if (!this->dh)
@@ -253,18 +667,26 @@ METHOD(task_t, build_i, status_t,
                        return FAILED;
                }
        }
+       else if (this->dh->get_dh_group(this->dh) != this->dh_group)
+       {       /* reset DH instance if group changed (INVALID_KE_PAYLOAD) */
+               this->dh->destroy(this->dh);
+               this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat,
+                                                                                                 this->dh_group);
+               if (!this->dh)
+               {
+                       DBG1(DBG_IKE, "requested DH group %N not supported",
+                                diffie_hellman_group_names, this->dh_group);
+                       return FAILED;
+               }
+       }
 
        /* generate nonce only when we are trying the first time */
        if (this->my_nonce.ptr == NULL)
        {
-               rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
-               if (!rng)
+               if (!generate_nonce(this))
                {
-                       DBG1(DBG_IKE, "error generating nonce");
                        return FAILED;
                }
-               rng->allocate_bytes(rng, NONCE_SIZE, &this->my_nonce);
-               rng->destroy(rng);
        }
 
        if (this->cookie.ptr)
@@ -272,7 +694,10 @@ METHOD(task_t, build_i, status_t,
                message->add_notify(message, FALSE, COOKIE, this->cookie);
        }
 
-       build_payloads(this, message);
+       if (!build_payloads(this, message))
+       {
+               return FAILED;
+       }
 
 #ifdef ME
        {
@@ -290,20 +715,13 @@ METHOD(task_t, build_i, status_t,
 METHOD(task_t, process_r,  status_t,
        private_ike_init_t *this, message_t *message)
 {
-       rng_t *rng;
-
-       this->config = this->ike_sa->get_ike_cfg(this->ike_sa);
        DBG0(DBG_IKE, "%H is initiating an IKE_SA", message->get_source(message));
        this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING);
 
-       rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
-       if (!rng)
+       if (!generate_nonce(this))
        {
-               DBG1(DBG_IKE, "error generating nonce");
                return FAILED;
        }
-       rng->allocate_bytes(rng, NONCE_SIZE, &this->my_nonce);
-       rng->destroy(rng);
 
 #ifdef ME
        {
@@ -354,33 +772,50 @@ static bool derive_keys(private_ike_init_t *this,
        {
                return FALSE;
        }
-       charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh,
-                                                 nonce_i, nonce_r, this->old_sa);
+       charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, chunk_empty,
+                                                 nonce_i, nonce_r, this->old_sa, NULL);
        return TRUE;
 }
 
 METHOD(task_t, build_r, status_t,
        private_ike_init_t *this, message_t *message)
 {
+       identification_t *gateway;
+
        /* check if we have everything we need */
        if (this->proposal == NULL ||
                this->other_nonce.len == 0 || this->my_nonce.len == 0)
        {
-               DBG1(DBG_IKE, "received proposals inacceptable");
+               DBG1(DBG_IKE, "received proposals unacceptable");
                message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
                return FAILED;
        }
-       this->ike_sa->set_proposal(this->ike_sa, this->proposal);
+
+       /* check if we'd have to redirect the client */
+       if (!this->old_sa &&
+               this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_REDIRECTION) &&
+               charon->redirect->redirect_on_init(charon->redirect, this->ike_sa,
+                                                                                  &gateway))
+       {
+               chunk_t data;
+
+               DBG1(DBG_IKE, "redirecting peer to %Y", gateway);
+               data = redirect_data_create(gateway, this->other_nonce);
+               message->add_notify(message, TRUE, REDIRECT, data);
+               gateway->destroy(gateway);
+               chunk_free(&data);
+               return FAILED;
+       }
 
        if (this->dh == NULL ||
                !this->proposal->has_dh_group(this->proposal, this->dh_group))
        {
-               u_int16_t group;
+               uint16_t group;
 
                if (this->proposal->get_algorithm(this->proposal, DIFFIE_HELLMAN_GROUP,
                                                                                  &group, NULL))
                {
-                       DBG1(DBG_IKE, "DH group %N inacceptable, requesting %N",
+                       DBG1(DBG_IKE, "DH group %N unacceptable, requesting %N",
                                 diffie_hellman_group_names, this->dh_group,
                                 diffie_hellman_group_names, group);
                        this->dh_group = group;
@@ -391,17 +826,98 @@ METHOD(task_t, build_r, status_t,
                else
                {
                        DBG1(DBG_IKE, "no acceptable proposal found");
+                       message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
                }
                return FAILED;
        }
 
+       if (this->dh_failed)
+       {
+               DBG1(DBG_IKE, "applying DH public value failed");
+               message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               return FAILED;
+       }
+
        if (!derive_keys(this, this->other_nonce, this->my_nonce))
        {
                DBG1(DBG_IKE, "key derivation failed");
                message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
                return FAILED;
        }
-       build_payloads(this, message);
+       if (!build_payloads(this, message))
+       {
+               message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
+               return FAILED;
+       }
+       return SUCCESS;
+}
+
+/**
+ * Raise alerts for received notify errors
+ */
+static void raise_alerts(private_ike_init_t *this, notify_type_t type)
+{
+       ike_cfg_t *ike_cfg;
+       linked_list_t *list;
+
+       switch (type)
+       {
+               case NO_PROPOSAL_CHOSEN:
+                       ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
+                       list = ike_cfg->get_proposals(ike_cfg);
+                       charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_IKE, list);
+                       list->destroy_offset(list, offsetof(proposal_t, destroy));
+                       break;
+               default:
+                       break;
+       }
+}
+
+METHOD(task_t, pre_process_i, status_t,
+       private_ike_init_t *this, message_t *message)
+{
+       enumerator_t *enumerator;
+       payload_t *payload;
+
+       /* check for erroneous notifies */
+       enumerator = message->create_payload_enumerator(message);
+       while (enumerator->enumerate(enumerator, &payload))
+       {
+               if (payload->get_type(payload) == PLV2_NOTIFY)
+               {
+                       notify_payload_t *notify = (notify_payload_t*)payload;
+                       notify_type_t type = notify->get_notify_type(notify);
+
+                       switch (type)
+                       {
+                               case REDIRECT:
+                               {
+                                       identification_t *gateway;
+                                       chunk_t data, nonce = chunk_empty;
+                                       status_t status = SUCCESS;
+
+                                       if (this->old_sa)
+                                       {
+                                               break;
+                                       }
+                                       data = notify->get_notification_data(notify);
+                                       gateway = redirect_data_parse(data, &nonce);
+                                       if (!gateway || !chunk_equals(nonce, this->my_nonce))
+                                       {
+                                               DBG1(DBG_IKE, "received invalid REDIRECT notify");
+                                               status = FAILED;
+                                       }
+                                       DESTROY_IF(gateway);
+                                       chunk_free(&nonce);
+                                       enumerator->destroy(enumerator);
+                                       return status;
+                               }
+                               default:
+                                       break;
+                       }
+               }
+       }
+       enumerator->destroy(enumerator);
        return SUCCESS;
 }
 
@@ -411,11 +927,11 @@ METHOD(task_t, process_i, status_t,
        enumerator_t *enumerator;
        payload_t *payload;
 
-       /* check for erronous notifies */
+       /* check for erroneous notifies */
        enumerator = message->create_payload_enumerator(message);
        while (enumerator->enumerate(enumerator, &payload))
        {
-               if (payload->get_type(payload) == NOTIFY)
+               if (payload->get_type(payload) == PLV2_NOTIFY)
                {
                        notify_payload_t *notify = (notify_payload_t*)payload;
                        notify_type_t type = notify->get_notify_type(notify);
@@ -429,14 +945,14 @@ METHOD(task_t, process_i, status_t,
 
                                        bad_group = this->dh_group;
                                        data = notify->get_notification_data(notify);
-                                       this->dh_group = ntohs(*((u_int16_t*)data.ptr));
+                                       this->dh_group = ntohs(*((uint16_t*)data.ptr));
                                        DBG1(DBG_IKE, "peer didn't accept DH group %N, "
                                                 "it requested %N", diffie_hellman_group_names,
                                                 bad_group, diffie_hellman_group_names, this->dh_group);
 
                                        if (this->old_sa == NULL)
                                        {       /* reset the IKE_SA if we are not rekeying */
-                                               this->ike_sa->reset(this->ike_sa);
+                                               this->ike_sa->reset(this->ike_sa, FALSE);
                                        }
 
                                        enumerator->destroy(enumerator);
@@ -454,12 +970,35 @@ METHOD(task_t, process_i, status_t,
                                {
                                        chunk_free(&this->cookie);
                                        this->cookie = chunk_clone(notify->get_notification_data(notify));
-                                       this->ike_sa->reset(this->ike_sa);
+                                       this->ike_sa->reset(this->ike_sa, FALSE);
                                        enumerator->destroy(enumerator);
                                        DBG2(DBG_IKE, "received %N notify", notify_type_names, type);
                                        this->retry++;
                                        return NEED_MORE;
                                }
+                               case REDIRECT:
+                               {
+                                       identification_t *gateway;
+                                       chunk_t data, nonce = chunk_empty;
+                                       status_t status = FAILED;
+
+                                       if (this->old_sa)
+                                       {
+                                               DBG1(DBG_IKE, "received REDIRECT notify during rekeying"
+                                                    ", ignored");
+                                               break;
+                                       }
+                                       data = notify->get_notification_data(notify);
+                                       gateway = redirect_data_parse(data, &nonce);
+                                       if (this->ike_sa->handle_redirect(this->ike_sa, gateway))
+                                       {
+                                               status = NEED_MORE;
+                                       }
+                                       DESTROY_IF(gateway);
+                                       chunk_free(&nonce);
+                                       enumerator->destroy(enumerator);
+                                       return status;
+                               }
                                default:
                                {
                                        if (type <= 16383)
@@ -467,6 +1006,7 @@ METHOD(task_t, process_i, status_t,
                                                DBG1(DBG_IKE, "received %N notify error",
                                                         notify_type_names, type);
                                                enumerator->destroy(enumerator);
+                                               raise_alerts(this, type);
                                                return FAILED;
                                        }
                                        DBG2(DBG_IKE, "received %N notify",
@@ -487,7 +1027,6 @@ METHOD(task_t, process_i, status_t,
                DBG1(DBG_IKE, "peers proposal selection invalid");
                return FAILED;
        }
-       this->ike_sa->set_proposal(this->ike_sa, this->proposal);
 
        if (this->dh == NULL ||
                !this->proposal->has_dh_group(this->proposal, this->dh_group))
@@ -496,6 +1035,12 @@ METHOD(task_t, process_i, status_t,
                return FAILED;
        }
 
+       if (this->dh_failed)
+       {
+               DBG1(DBG_IKE, "applying DH public value failed");
+               return FAILED;
+       }
+
        if (!derive_keys(this, this->my_nonce, this->other_nonce))
        {
                DBG1(DBG_IKE, "key derivation failed");
@@ -519,9 +1064,7 @@ METHOD(task_t, migrate, void,
        this->ike_sa = ike_sa;
        this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
        this->proposal = NULL;
-       DESTROY_IF(this->dh);
-       this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat,
-                                                                                         this->dh_group);
+       this->dh_failed = FALSE;
 }
 
 METHOD(task_t, destroy, void,
@@ -529,6 +1072,7 @@ METHOD(task_t, destroy, void,
 {
        DESTROY_IF(this->dh);
        DESTROY_IF(this->proposal);
+       DESTROY_IF(this->nonceg);
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
        chunk_free(&this->cookie);
@@ -570,18 +1114,23 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa)
                .dh_group = MODP_NONE,
                .keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa),
                .old_sa = old_sa,
+               .signature_authentication = lib->settings->get_bool(lib->settings,
+                                                               "%s.signature_authentication", TRUE, lib->ns),
+               .follow_redirects = lib->settings->get_bool(lib->settings,
+                                                               "%s.follow_redirects", TRUE, lib->ns),
        );
+       this->nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat);
 
        if (initiator)
        {
                this->public.task.build = _build_i;
                this->public.task.process = _process_i;
+               this->public.task.pre_process = _pre_process_i;
        }
        else
        {
                this->public.task.build = _build_r;
                this->public.task.process = _process_r;
        }
-
        return &this->public;
 }