]> git.ipfire.org Git - thirdparty/strongswan.git/blobdiff - src/libcharon/sa/ikev2/tasks/ike_auth.c
ike: Optionally allow private algorithms for IKE/CHILD_SAs
[thirdparty/strongswan.git] / src / libcharon / sa / ikev2 / tasks / ike_auth.c
index 800dab07e7c7c1cf95521db402696bee6fd07195..6448d8baa9ce165be12491f46dadf80e9aa251a3 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
  * Copyright (C) 2005-2009 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
@@ -24,7 +24,9 @@
 #include <encoding/payloads/auth_payload.h>
 #include <encoding/payloads/eap_payload.h>
 #include <encoding/payloads/nonce_payload.h>
+#include <sa/ikev2/keymat_v2.h>
 #include <sa/ikev2/authenticators/eap_authenticator.h>
+#include <processing/jobs/delete_ike_sa_job.h>
 
 typedef struct private_ike_auth_t private_ike_auth_t;
 
@@ -58,6 +60,16 @@ struct private_ike_auth_t {
         */
        chunk_t other_nonce;
 
+       /**
+        * PPK_ID sent or received
+        */
+       identification_t *ppk_id;
+
+       /**
+        * Optional PPK to use
+        */
+       chunk_t ppk;
+
        /**
         * IKE_SA_INIT message sent by us
         */
@@ -112,6 +124,16 @@ struct private_ike_auth_t {
         * received an INITIAL_CONTACT?
         */
        bool initial_contact;
+
+       /**
+        * Is EAP acceptable, did we strictly authenticate peer?
+        */
+       bool eap_acceptable;
+
+       /**
+        * Gateway ID if redirected
+        */
+       identification_t *redirect_to;
 };
 
 /**
@@ -132,8 +154,8 @@ static status_t collect_my_init_data(private_ike_auth_t *this,
        nonce_payload_t *nonce;
 
        /* get the nonce that was generated in ike_init */
-       nonce = (nonce_payload_t*)message->get_payload(message, NONCE);
-       if (nonce == NULL)
+       nonce = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE);
+       if (!nonce)
        {
                return FAILED;
        }
@@ -158,8 +180,8 @@ static status_t collect_other_init_data(private_ike_auth_t *this,
        nonce_payload_t *nonce;
 
        /* get the nonce that was generated in ike_init */
-       nonce = (nonce_payload_t*)message->get_payload(message, NONCE);
-       if (nonce == NULL)
+       nonce = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE);
+       if (!nonce)
        {
                return FAILED;
        }
@@ -175,7 +197,7 @@ static status_t collect_other_init_data(private_ike_auth_t *this,
  */
 static void get_reserved_id_bytes(private_ike_auth_t *this, id_payload_t *id)
 {
-       u_int8_t *byte;
+       uint8_t *byte;
        int i;
 
        for (i = 0; i < countof(this->reserved); i++)
@@ -267,6 +289,29 @@ static bool do_another_auth(private_ike_auth_t *this)
        return do_another;
 }
 
+/**
+ * Check if this is the first authentication round
+ */
+static bool is_first_round(private_ike_auth_t *this, bool local)
+{
+       enumerator_t *done;
+       auth_cfg_t *cfg;
+
+       if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MULTIPLE_AUTH))
+       {
+               return TRUE;
+       }
+
+       done = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, local);
+       if (done->enumerate(done, &cfg))
+       {
+               done->destroy(done);
+               return FALSE;
+       }
+       done->destroy(done);
+       return TRUE;
+}
+
 /**
  * Get peer configuration candidates from backends
  */
@@ -274,13 +319,20 @@ static bool load_cfg_candidates(private_ike_auth_t *this)
 {
        enumerator_t *enumerator;
        peer_cfg_t *peer_cfg;
+       ike_cfg_t *ike_cfg;
        host_t *me, *other;
        identification_t *my_id, *other_id;
+       proposal_t *ike_proposal;
+       bool private;
 
        me = this->ike_sa->get_my_host(this->ike_sa);
        other = this->ike_sa->get_other_host(this->ike_sa);
        my_id = this->ike_sa->get_my_id(this->ike_sa);
        other_id = this->ike_sa->get_other_id(this->ike_sa);
+       ike_proposal = this->ike_sa->get_proposal(this->ike_sa);
+       private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN) ||
+                         lib->settings->get_bool(lib->settings, "%s.accept_private_algs",
+                                                                         FALSE, lib->ns);
 
        DBG1(DBG_CFG, "looking for peer configs matching %H[%Y]...%H[%Y]",
                 me, my_id, other, other_id);
@@ -288,11 +340,18 @@ static bool load_cfg_candidates(private_ike_auth_t *this)
                                                                                        me, other, my_id, other_id, IKEV2);
        while (enumerator->enumerate(enumerator, &peer_cfg))
        {
+               /* ignore all configs that have no matching IKE proposal */
+               ike_cfg = peer_cfg->get_ike_cfg(peer_cfg);
+               if (!ike_cfg->has_proposal(ike_cfg, ike_proposal, private))
+               {
+                       DBG2(DBG_CFG, "ignore candidate '%s' without matching IKE proposal",
+                                peer_cfg->get_name(peer_cfg));
+                       continue;
+               }
                peer_cfg->get_ref(peer_cfg);
-               if (this->peer_cfg == NULL)
+               if (!this->peer_cfg)
                {       /* best match */
                        this->peer_cfg = peer_cfg;
-                       this->ike_sa->set_peer_cfg(this->ike_sa, peer_cfg);
                }
                else
                {
@@ -302,6 +361,7 @@ static bool load_cfg_candidates(private_ike_auth_t *this)
        enumerator->destroy(enumerator);
        if (this->peer_cfg)
        {
+               this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg);
                DBG1(DBG_CFG, "selected peer config '%s'",
                         this->peer_cfg->get_name(this->peer_cfg));
                return TRUE;
@@ -358,7 +418,7 @@ static bool update_cfg_candidates(private_ike_auth_t *this, bool strict)
                        {
                                break;
                        }
-                       DBG1(DBG_CFG, "selected peer config '%s' inacceptable: %s",
+                       DBG1(DBG_CFG, "selected peer config '%s' unacceptable: %s",
                                 this->peer_cfg->get_name(this->peer_cfg), comply_error);
                        this->peer_cfg->destroy(this->peer_cfg);
                }
@@ -380,6 +440,149 @@ static bool update_cfg_candidates(private_ike_auth_t *this, bool strict)
        return this->peer_cfg != NULL;
 }
 
+/**
+ * Currently defined PPK_ID types
+ */
+#define PPK_ID_OPAQUE 1
+#define PPK_ID_FIXED 2
+
+/**
+ * Parse the payload data of the given PPK_IDENTITY notify
+ */
+static bool parse_ppk_identity(notify_payload_t *notify, identification_t **id)
+{
+       chunk_t data;
+
+       data = notify->get_notification_data(notify);
+       if (data.len < 2)
+       {
+               return FALSE;
+       }
+       switch (data.ptr[0])
+       {
+               case PPK_ID_FIXED:
+                       data = chunk_skip(data, 1);
+                       break;
+               default:
+                       return FALSE;
+       }
+       *id = identification_create_from_data(data);
+       return TRUE;
+}
+
+/**
+ * Add a PPK_IDENTITY with the given PPK_ID to the given message
+ */
+static void add_ppk_identity(identification_t *id, message_t *msg)
+{
+       chunk_t data;
+       uint8_t type = PPK_ID_FIXED;
+
+       /* we currently only support one type */
+       data = chunk_cata("cc", chunk_from_thing(type), id->get_encoding(id));
+       msg->add_notify(msg, FALSE, PPK_IDENTITY, data);
+}
+
+/**
+ * Use the given PPK_ID to find a PPK and store it and the ID in the task
+ */
+static bool get_ppk(private_ike_auth_t *this, identification_t *ppk_id)
+{
+       shared_key_t *key;
+
+       key = lib->credmgr->get_shared(lib->credmgr, SHARED_PPK, ppk_id, NULL);
+       if (!key)
+       {
+               if (this->peer_cfg->ppk_required(this->peer_cfg))
+               {
+                       DBG1(DBG_CFG, "PPK required but no PPK found for '%Y'", ppk_id);
+                       return FALSE;
+               }
+               DBG1(DBG_CFG, "no PPK for '%Y' found, ignored because PPK is not "
+                        "required", ppk_id);
+               return TRUE;
+       }
+       this->ppk = chunk_clone(key->get_key(key));
+       this->ppk_id = ppk_id->clone(ppk_id);
+       key->destroy(key);
+       return TRUE;
+}
+
+/**
+ * Check if we have a PPK available and, if not, whether we require one as
+ * initiator
+ */
+static bool get_ppk_i(private_ike_auth_t *this)
+{
+       identification_t *ppk_id;
+
+       if (!this->ike_sa->supports_extension(this->ike_sa, EXT_PPK))
+       {
+               if (this->peer_cfg->ppk_required(this->peer_cfg))
+               {
+                       DBG1(DBG_CFG, "PPK required but peer does not support PPK");
+                       return FALSE;
+               }
+               return TRUE;
+       }
+
+       ppk_id = this->peer_cfg->get_ppk_id(this->peer_cfg);
+       if (!ppk_id)
+       {
+               if (this->peer_cfg->ppk_required(this->peer_cfg))
+               {
+                       DBG1(DBG_CFG, "PPK required but no PPK_ID configured");
+                       return FALSE;
+               }
+               return TRUE;
+       }
+       return get_ppk(this, ppk_id);
+}
+
+/**
+ * Check if we have a PPK available and if not whether we require one as
+ * responder
+ */
+static bool get_ppk_r(private_ike_auth_t *this, message_t *msg)
+{
+       notify_payload_t *notify;
+       identification_t *ppk_id, *ppk_id_cfg;
+       bool result;
+
+       if (!this->ike_sa->supports_extension(this->ike_sa, EXT_PPK))
+       {
+               if (this->peer_cfg->ppk_required(this->peer_cfg))
+               {
+                       DBG1(DBG_CFG, "PPK required but peer does not support PPK");
+                       return FALSE;
+               }
+               return TRUE;
+       }
+
+       notify = msg->get_notify(msg, PPK_IDENTITY);
+       if (!notify || !parse_ppk_identity(notify, &ppk_id))
+       {
+               if (this->peer_cfg->ppk_required(this->peer_cfg))
+               {
+                       DBG1(DBG_CFG, "PPK required but no PPK_IDENTITY received");
+                       return FALSE;
+               }
+               return TRUE;
+       }
+
+       ppk_id_cfg = this->peer_cfg->get_ppk_id(this->peer_cfg);
+       if (ppk_id_cfg && !ppk_id->matches(ppk_id, ppk_id_cfg))
+       {
+               DBG1(DBG_CFG, "received PPK_ID '%Y', but require '%Y'", ppk_id,
+                        ppk_id_cfg);
+               ppk_id->destroy(ppk_id);
+               return FALSE;
+       }
+       result = get_ppk(this, ppk_id);
+       ppk_id->destroy(ppk_id);
+       return result;
+}
+
 METHOD(task_t, build_i, status_t,
        private_ike_auth_t *this, message_t *message)
 {
@@ -390,7 +593,7 @@ METHOD(task_t, build_i, status_t,
                return collect_my_init_data(this, message);
        }
 
-       if (this->peer_cfg == NULL)
+       if (!this->peer_cfg)
        {
                this->peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
                this->peer_cfg->get_ref(this->peer_cfg);
@@ -406,6 +609,15 @@ METHOD(task_t, build_i, status_t,
                /* indicate support for EAP-only authentication */
                message->add_notify(message, FALSE, EAP_ONLY_AUTHENTICATION,
                                                        chunk_empty);
+               /* indicate support for RFC 6311 Message ID synchronization */
+               message->add_notify(message, FALSE, IKEV2_MESSAGE_ID_SYNC_SUPPORTED,
+                                                       chunk_empty);
+               /* only use a PPK in the first round */
+               if (!get_ppk_i(this))
+               {
+                       charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
+                       return FAILED;
+               }
        }
 
        if (!this->do_another_auth && !this->my_auth)
@@ -414,7 +626,7 @@ METHOD(task_t, build_i, status_t,
        }
 
        /* check if an authenticator is in progress */
-       if (this->my_auth == NULL)
+       if (!this->my_auth)
        {
                identification_t *idi, *idr = NULL;
                id_payload_t *id_payload;
@@ -433,7 +645,7 @@ METHOD(task_t, build_i, status_t,
                        {
                                this->ike_sa->set_other_id(this->ike_sa, idr->clone(idr));
                                id_payload = id_payload_create_from_identification(
-                                                                                                                       ID_RESPONDER, idr);
+                                                                                                                       PLV2_ID_RESPONDER, idr);
                                message->add_payload(message, (payload_t*)id_payload);
                        }
                }
@@ -451,12 +663,12 @@ METHOD(task_t, build_i, status_t,
                        cfg->add(cfg, AUTH_RULE_IDENTITY, idi);
                }
                this->ike_sa->set_my_id(this->ike_sa, idi->clone(idi));
-               id_payload = id_payload_create_from_identification(ID_INITIATOR, idi);
+               id_payload = id_payload_create_from_identification(PLV2_ID_INITIATOR, idi);
                get_reserved_id_bytes(this, id_payload);
                message->add_payload(message, (payload_t*)id_payload);
 
-               if (idr && message->get_message_id(message) == 1 &&
-                       this->peer_cfg->get_unique_policy(this->peer_cfg) != UNIQUE_NO &&
+               if (idr && !idr->contains_wildcards(idr) &&
+                       message->get_message_id(message) == 1 &&
                        this->peer_cfg->get_unique_policy(this->peer_cfg) != UNIQUE_NEVER)
                {
                        host_t *host;
@@ -481,6 +693,14 @@ METHOD(task_t, build_i, status_t,
                        return FAILED;
                }
        }
+       /* for authentication methods that return NEED_MORE, the PPK will be reset
+        * in process_i() for messages without PPK_ID notify, so we always set it
+        * during the first round (afterwards the PPK won't be available) */
+       if (this->ppk.ptr && this->my_auth->use_ppk)
+       {
+               this->my_auth->use_ppk(this->my_auth, this->ppk,
+                                                       !this->peer_cfg->ppk_required(this->peer_cfg));
+       }
        switch (this->my_auth->build(this->my_auth, message))
        {
                case SUCCESS:
@@ -495,10 +715,16 @@ METHOD(task_t, build_i, status_t,
                        return FAILED;
        }
 
+       /* add a PPK_IDENTITY notify to the message that contains AUTH */
+       if (this->ppk_id && message->get_payload(message, PLV2_AUTH))
+       {
+               add_ppk_identity(this->ppk_id, message);
+       }
+
        /* check for additional authentication rounds */
        if (do_another_auth(this))
        {
-               if (message->get_payload(message, AUTHENTICATION))
+               if (message->get_payload(message, PLV2_AUTH))
                {
                        message->add_notify(message, FALSE, ANOTHER_AUTH_FOLLOWS, chunk_empty);
                }
@@ -522,10 +748,10 @@ METHOD(task_t, process_r, status_t,
                return collect_other_init_data(this, message);
        }
 
-       if (this->my_auth == NULL && this->do_another_auth)
+       if (!this->my_auth && this->do_another_auth)
        {
                /* handle (optional) IDr payload, apply proposed identity */
-               id_payload = (id_payload_t*)message->get_payload(message, ID_RESPONDER);
+               id_payload = (id_payload_t*)message->get_payload(message, PLV2_ID_RESPONDER);
                if (id_payload)
                {
                        id = id_payload->get_identification(id_payload);
@@ -553,12 +779,16 @@ METHOD(task_t, process_r, status_t,
                        this->ike_sa->enable_extension(this->ike_sa,
                                                                                   EXT_EAP_ONLY_AUTHENTICATION);
                }
+               if (message->get_notify(message, INITIAL_CONTACT))
+               {
+                       this->initial_contact = TRUE;
+               }
        }
 
-       if (this->other_auth == NULL)
+       if (!this->other_auth)
        {
                /* handle IDi payload */
-               id_payload = (id_payload_t*)message->get_payload(message, ID_INITIATOR);
+               id_payload = (id_payload_t*)message->get_payload(message, PLV2_ID_INITIATOR);
                if (!id_payload)
                {
                        DBG1(DBG_IKE, "IDi payload missing");
@@ -570,7 +800,7 @@ METHOD(task_t, process_r, status_t,
                cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE);
                cfg->add(cfg, AUTH_RULE_IDENTITY, id->clone(id));
 
-               if (this->peer_cfg == NULL)
+               if (!this->peer_cfg)
                {
                        if (!load_cfg_candidates(this))
                        {
@@ -578,14 +808,14 @@ METHOD(task_t, process_r, status_t,
                                return NEED_MORE;
                        }
                }
-               if (message->get_payload(message, AUTHENTICATION) == NULL)
+               if (!message->get_payload(message, PLV2_AUTH))
                {       /* before authenticating with EAP, we need a EAP config */
                        cand = get_auth_cfg(this, FALSE);
                        while (!cand || (
                                        (uintptr_t)cand->get(cand, AUTH_RULE_EAP_TYPE) == EAP_NAK &&
                                        (uintptr_t)cand->get(cand, AUTH_RULE_EAP_VENDOR) == 0))
                        {       /* peer requested EAP, but current config does not match */
-                               DBG1(DBG_IKE, "peer requested EAP, config inacceptable");
+                               DBG1(DBG_IKE, "peer requested EAP, config unacceptable");
                                this->peer_cfg->destroy(this->peer_cfg);
                                this->peer_cfg = NULL;
                                if (!update_cfg_candidates(this, FALSE))
@@ -624,6 +854,19 @@ METHOD(task_t, process_r, status_t,
                        return NEED_MORE;
                }
        }
+       if (message->get_payload(message, PLV2_AUTH) &&
+               is_first_round(this, FALSE))
+       {
+               if (!get_ppk_r(this, message))
+               {
+                       this->authentication_failed = TRUE;
+                       return NEED_MORE;
+               }
+               else if (this->ppk.ptr && this->other_auth->use_ppk)
+               {
+                       this->other_auth->use_ppk(this->other_auth, this->ppk, FALSE);
+               }
+       }
        switch (this->other_auth->process(this->other_auth, message))
        {
                case SUCCESS:
@@ -631,7 +874,7 @@ METHOD(task_t, process_r, status_t,
                        this->other_auth = NULL;
                        break;
                case NEED_MORE:
-                       if (message->get_payload(message, AUTHENTICATION))
+                       if (message->get_payload(message, PLV2_AUTH))
                        {       /* AUTH verification successful, but another build() needed */
                                break;
                        }
@@ -641,14 +884,6 @@ METHOD(task_t, process_r, status_t,
                        return NEED_MORE;
        }
 
-       /* If authenticated (with non-EAP) and received INITIAL_CONTACT,
-        * delete any existing IKE_SAs with that peer. */
-       if (message->get_message_id(message) == 1 &&
-               message->get_notify(message, INITIAL_CONTACT))
-       {
-               this->initial_contact = TRUE;
-       }
-
        /* another auth round done, invoke authorize hook */
        if (!charon->bus->authorize(charon->bus, FALSE))
        {
@@ -665,7 +900,7 @@ METHOD(task_t, process_r, status_t,
                return NEED_MORE;
        }
 
-       if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL)
+       if (!message->get_notify(message, ANOTHER_AUTH_FOLLOWS))
        {
                this->expect_another_auth = FALSE;
                if (!update_cfg_candidates(this, TRUE))
@@ -677,9 +912,41 @@ METHOD(task_t, process_r, status_t,
        return NEED_MORE;
 }
 
+/**
+ * Clear the PPK and PPK_ID
+ */
+static void clear_ppk(private_ike_auth_t *this)
+{
+       DESTROY_IF(this->ppk_id);
+       this->ppk_id = NULL;
+       chunk_clear(&this->ppk);
+}
+
+/**
+ * Derive new keys and clear the PPK
+ */
+static bool apply_ppk(private_ike_auth_t *this)
+{
+       keymat_v2_t *keymat;
+
+       if (this->ppk.ptr)
+       {
+               keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa);
+               if (!keymat->derive_ike_keys_ppk(keymat, this->ppk))
+               {
+                       return FALSE;
+               }
+               DBG1(DBG_CFG, "using PPK for PPK_ID '%Y'", this->ppk_id);
+               this->ike_sa->set_condition(this->ike_sa, COND_PPK, TRUE);
+       }
+       clear_ppk(this);
+       return TRUE;
+}
+
 METHOD(task_t, build_r, status_t,
        private_ike_auth_t *this, message_t *message)
 {
+       identification_t *gateway;
        auth_cfg_t *cfg;
 
        if (message->get_exchange_type(message) == IKE_SA_INIT)
@@ -692,12 +959,12 @@ METHOD(task_t, build_r, status_t,
                return collect_my_init_data(this, message);
        }
 
-       if (this->authentication_failed || this->peer_cfg == NULL)
+       if (this->authentication_failed || !this->peer_cfg)
        {
                goto peer_auth_failed;
        }
 
-       if (this->my_auth == NULL && this->do_another_auth)
+       if (!this->my_auth && this->do_another_auth)
        {
                identification_t *id, *id_cfg;
                id_payload_t *id_payload;
@@ -733,17 +1000,10 @@ METHOD(task_t, build_r, status_t,
                        }
                }
 
-               id_payload = id_payload_create_from_identification(ID_RESPONDER, id);
+               id_payload = id_payload_create_from_identification(PLV2_ID_RESPONDER, id);
                get_reserved_id_bytes(this, id_payload);
                message->add_payload(message, (payload_t*)id_payload);
 
-               if (this->initial_contact)
-               {
-                       charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager,
-                                                                                                        this->ike_sa, TRUE);
-                       this->initial_contact = FALSE;
-               }
-
                if ((uintptr_t)cfg->get(cfg, AUTH_RULE_AUTH_CLASS) == AUTH_CLASS_EAP)
                {       /* EAP-only authentication */
                        if (!this->ike_sa->supports_extension(this->ike_sa,
@@ -780,7 +1040,7 @@ METHOD(task_t, build_r, status_t,
                        case NEED_MORE:
                                break;
                        default:
-                               if (message->get_payload(message, EXTENSIBLE_AUTHENTICATION))
+                               if (message->get_payload(message, PLV2_EAP))
                                {       /* skip AUTHENTICATION_FAILED if we have EAP_FAILURE */
                                        goto peer_auth_failed_no_notify;
                                }
@@ -789,6 +1049,10 @@ METHOD(task_t, build_r, status_t,
        }
        if (this->my_auth)
        {
+               if (this->ppk.ptr && this->my_auth->use_ppk)
+               {
+                       this->my_auth->use_ppk(this->my_auth, this->ppk, FALSE);
+               }
                switch (this->my_auth->build(this->my_auth, message))
                {
                        case SUCCESS:
@@ -803,6 +1067,16 @@ METHOD(task_t, build_r, status_t,
                }
        }
 
+       /* add a PPK_IDENTITY notify and derive new keys and clear the PPK */
+       if (this->ppk.ptr)
+       {
+               message->add_notify(message, FALSE, PPK_IDENTITY, chunk_empty);
+               if (!apply_ppk(this))
+               {
+                       goto local_auth_failed;
+               }
+       }
+
        /* check for additional authentication rounds */
        if (do_another_auth(this))
        {
@@ -812,34 +1086,56 @@ METHOD(task_t, build_r, status_t,
        {
                this->do_another_auth = FALSE;
        }
-       if (!this->do_another_auth && !this->expect_another_auth)
+       if (this->do_another_auth || this->expect_another_auth)
        {
-               if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager,
-                                                                                                        this->ike_sa, FALSE))
-               {
-                       DBG1(DBG_IKE, "cancelling IKE_SA setup due to uniqueness policy");
-                       charon->bus->alert(charon->bus, ALERT_UNIQUE_KEEP);
-                       message->add_notify(message, TRUE, AUTHENTICATION_FAILED,
-                                                               chunk_empty);
-                       return FAILED;
-               }
-               if (!charon->bus->authorize(charon->bus, TRUE))
-               {
-                       DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling");
-                       goto peer_auth_failed;
-               }
-               DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
-                        this->ike_sa->get_name(this->ike_sa),
-                        this->ike_sa->get_unique_id(this->ike_sa),
-                        this->ike_sa->get_my_host(this->ike_sa),
-                        this->ike_sa->get_my_id(this->ike_sa),
-                        this->ike_sa->get_other_host(this->ike_sa),
-                        this->ike_sa->get_other_id(this->ike_sa));
-               this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
-               charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
-               return SUCCESS;
+               return NEED_MORE;
        }
-       return NEED_MORE;
+
+       if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager,
+                                                                               this->ike_sa, this->initial_contact))
+       {
+               DBG1(DBG_IKE, "cancelling IKE_SA setup due to uniqueness policy");
+               charon->bus->alert(charon->bus, ALERT_UNIQUE_KEEP);
+               message->add_notify(message, TRUE, AUTHENTICATION_FAILED,
+                                                       chunk_empty);
+               return FAILED;
+       }
+       if (!charon->bus->authorize(charon->bus, TRUE))
+       {
+               DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling");
+               goto peer_auth_failed;
+       }
+       if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_REDIRECTION) &&
+               charon->redirect->redirect_on_auth(charon->redirect, this->ike_sa,
+                                                                                  &gateway))
+       {
+               delete_ike_sa_job_t *job;
+               chunk_t data;
+
+               DBG1(DBG_IKE, "redirecting peer to %Y", gateway);
+               data = redirect_data_create(gateway, chunk_empty);
+               message->add_notify(message, FALSE, REDIRECT, data);
+               gateway->destroy(gateway);
+               chunk_free(&data);
+               /* we use this condition to prevent the CHILD_SA from getting created */
+               this->ike_sa->set_condition(this->ike_sa, COND_REDIRECTED, TRUE);
+               /* if the peer does not delete the SA we do so after a while */
+               job = delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE);
+               lib->scheduler->schedule_job(lib->scheduler, (job_t*)job,
+                                               lib->settings->get_int(lib->settings,
+                                                       "%s.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT,
+                                                       lib->ns));
+       }
+       DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
+                this->ike_sa->get_name(this->ike_sa),
+                this->ike_sa->get_unique_id(this->ike_sa),
+                this->ike_sa->get_my_host(this->ike_sa),
+                this->ike_sa->get_my_id(this->ike_sa),
+                this->ike_sa->get_other_host(this->ike_sa),
+                this->ike_sa->get_other_id(this->ike_sa));
+       this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
+       charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
+       return SUCCESS;
 
 peer_auth_failed:
        message->add_notify(message, TRUE, AUTHENTICATION_FAILED, chunk_empty);
@@ -879,13 +1175,44 @@ static void send_auth_failed_informational(private_ike_auth_t *this,
        message->destroy(message);
 }
 
+/**
+ * Check if strict constraint fulfillment required to continue current auth
+ */
+static bool require_strict(private_ike_auth_t *this, bool mutual_eap)
+{
+       auth_cfg_t *cfg;
+
+       if (this->eap_acceptable)
+       {
+               return FALSE;
+       }
+
+       cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE);
+       switch ((uintptr_t)cfg->get(cfg, AUTH_RULE_AUTH_CLASS))
+       {
+               case AUTH_CLASS_EAP:
+                       if (mutual_eap && this->my_auth)
+                       {
+                               this->eap_acceptable = TRUE;
+                               return !this->my_auth->is_mutual(this->my_auth);
+                       }
+                       return TRUE;
+               case AUTH_CLASS_PSK:
+                       return TRUE;
+               case AUTH_CLASS_PUBKEY:
+               case AUTH_CLASS_ANY:
+               default:
+                       return FALSE;
+       }
+}
+
 METHOD(task_t, process_i, status_t,
        private_ike_auth_t *this, message_t *message)
 {
        enumerator_t *enumerator;
        payload_t *payload;
        auth_cfg_t *cfg;
-       bool mutual_eap = FALSE;
+       bool mutual_eap = FALSE, ppk_id_received = FALSE;
 
        if (message->get_exchange_type(message) == IKE_SA_INIT)
        {
@@ -900,7 +1227,7 @@ METHOD(task_t, process_i, status_t,
        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);
@@ -928,6 +1255,22 @@ METHOD(task_t, process_i, status_t,
                                case ME_ENDPOINT:
                                        /* handled in ike_me task */
                                        break;
+                               case REDIRECT:
+                                       DESTROY_IF(this->redirect_to);
+                                       this->redirect_to = redirect_data_parse(
+                                                               notify->get_notification_data(notify), NULL);
+                                       if (!this->redirect_to)
+                                       {
+                                               DBG1(DBG_IKE, "received invalid REDIRECT notify");
+                                       }
+                                       break;
+                               case IKEV2_MESSAGE_ID_SYNC_SUPPORTED:
+                                       this->ike_sa->enable_extension(this->ike_sa,
+                                                                                                  EXT_IKE_MESSAGE_ID_SYNC);
+                                       break;
+                               case PPK_IDENTITY:
+                                       ppk_id_received = TRUE;
+                                       break;
                                default:
                                {
                                        if (type <= 16383)
@@ -949,14 +1292,14 @@ METHOD(task_t, process_i, status_t,
 
        if (this->expect_another_auth)
        {
-               if (this->other_auth == NULL)
+               if (!this->other_auth)
                {
                        id_payload_t *id_payload;
                        identification_t *id;
 
                        /* handle IDr payload */
                        id_payload = (id_payload_t*)message->get_payload(message,
-                                                                                                                        ID_RESPONDER);
+                                                                                                                        PLV2_ID_RESPONDER);
                        if (!id_payload)
                        {
                                DBG1(DBG_IKE, "IDr payload missing");
@@ -968,7 +1311,7 @@ METHOD(task_t, process_i, status_t,
                        cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE);
                        cfg->add(cfg, AUTH_RULE_IDENTITY, id->clone(id));
 
-                       if (message->get_payload(message, AUTHENTICATION))
+                       if (message->get_payload(message, PLV2_AUTH))
                        {
                                /* verify authentication data */
                                this->other_auth = authenticator_create_verifier(this->ike_sa,
@@ -989,6 +1332,11 @@ METHOD(task_t, process_i, status_t,
                }
                if (this->other_auth)
                {
+                       if (ppk_id_received && is_first_round(this, FALSE) &&
+                               this->other_auth->use_ppk)
+                       {
+                               this->other_auth->use_ppk(this->other_auth, this->ppk, FALSE);
+                       }
                        switch (this->other_auth->process(this->other_auth, message))
                        {
                                case SUCCESS:
@@ -1014,8 +1362,24 @@ METHOD(task_t, process_i, status_t,
                }
        }
 
+       if (require_strict(this, mutual_eap))
+       {
+               if (!update_cfg_candidates(this, TRUE))
+               {
+                       goto peer_auth_failed;
+               }
+       }
+
        if (this->my_auth)
        {
+               /* while we already set the PPK in build_i(), we MUST not use it if
+                * the peer did not reply with a PPK_ID notify */
+               if (this->ppk.ptr && this->my_auth->use_ppk)
+               {
+                       this->my_auth->use_ppk(this->my_auth,
+                                                                  ppk_id_received ? this->ppk : chunk_empty,
+                                                                  FALSE);
+               }
                switch (this->my_auth->process(this->my_auth, message))
                {
                        case SUCCESS:
@@ -1031,11 +1395,29 @@ METHOD(task_t, process_i, status_t,
                        case NEED_MORE:
                                break;
                        default:
-                               charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
-                               send_auth_failed_informational(this, message);
-                               return FAILED;
+                               goto local_auth_failed;
+               }
+       }
+
+       /* change keys and clear PPK after we are done with our authentication, so
+        * we only explicitly use it for the first round, afterwards we just use the
+        * changed SK_p keys implicitly */
+       if (!this->my_auth && this->ppk_id)
+       {
+               if (ppk_id_received)
+               {
+                       if (!apply_ppk(this))
+                       {
+                               goto local_auth_failed;
+                       }
                }
+               else
+               {
+                       DBG1(DBG_CFG, "peer didn't use PPK for PPK_ID '%Y'", this->ppk_id);
+               }
+               clear_ppk(this);
        }
+
        if (mutual_eap)
        {
                if (!this->my_auth || !this->my_auth->is_mutual(this->my_auth))
@@ -1046,39 +1428,48 @@ METHOD(task_t, process_i, status_t,
                DBG1(DBG_IKE, "allow mutual EAP-only authentication");
        }
 
-       if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL)
+       if (!message->get_notify(message, ANOTHER_AUTH_FOLLOWS))
        {
                this->expect_another_auth = FALSE;
        }
-       if (!this->expect_another_auth && !this->do_another_auth && !this->my_auth)
+       if (this->expect_another_auth || this->do_another_auth || this->my_auth)
        {
-               if (!update_cfg_candidates(this, TRUE))
-               {
-                       goto peer_auth_failed;
-               }
-               if (!charon->bus->authorize(charon->bus, TRUE))
-               {
-                       DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, "
-                                             "cancelling");
-                       goto peer_auth_failed;
-               }
-               DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
-                        this->ike_sa->get_name(this->ike_sa),
-                        this->ike_sa->get_unique_id(this->ike_sa),
-                        this->ike_sa->get_my_host(this->ike_sa),
-                        this->ike_sa->get_my_id(this->ike_sa),
-                        this->ike_sa->get_other_host(this->ike_sa),
-                        this->ike_sa->get_other_id(this->ike_sa));
-               this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
-               charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
-               return SUCCESS;
+               return NEED_MORE;
        }
-       return NEED_MORE;
+       if (!update_cfg_candidates(this, TRUE))
+       {
+               goto peer_auth_failed;
+       }
+       if (!charon->bus->authorize(charon->bus, TRUE))
+       {
+               DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, "
+                                     "cancelling");
+               goto peer_auth_failed;
+       }
+       DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
+                this->ike_sa->get_name(this->ike_sa),
+                this->ike_sa->get_unique_id(this->ike_sa),
+                this->ike_sa->get_my_host(this->ike_sa),
+                this->ike_sa->get_my_id(this->ike_sa),
+                this->ike_sa->get_other_host(this->ike_sa),
+                this->ike_sa->get_other_id(this->ike_sa));
+       this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
+       charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
+
+       if (this->redirect_to)
+       {
+               this->ike_sa->handle_redirect(this->ike_sa, this->redirect_to);
+       }
+       return SUCCESS;
 
 peer_auth_failed:
        charon->bus->alert(charon->bus, ALERT_PEER_AUTH_FAILED);
        send_auth_failed_informational(this, message);
        return FAILED;
+local_auth_failed:
+       charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
+       send_auth_failed_informational(this, message);
+       return FAILED;
 }
 
 METHOD(task_t, get_type, task_type_t,
@@ -1090,6 +1481,7 @@ METHOD(task_t, get_type, task_type_t,
 METHOD(task_t, migrate, void,
        private_ike_auth_t *this, ike_sa_t *ike_sa)
 {
+       clear_ppk(this);
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
        DESTROY_IF(this->my_packet);
@@ -1097,6 +1489,7 @@ METHOD(task_t, migrate, void,
        DESTROY_IF(this->peer_cfg);
        DESTROY_IF(this->my_auth);
        DESTROY_IF(this->other_auth);
+       DESTROY_IF(this->redirect_to);
        this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy));
 
        this->my_packet = NULL;
@@ -1105,6 +1498,7 @@ METHOD(task_t, migrate, void,
        this->peer_cfg = NULL;
        this->my_auth = NULL;
        this->other_auth = NULL;
+       this->redirect_to = NULL;
        this->do_another_auth = TRUE;
        this->expect_another_auth = TRUE;
        this->authentication_failed = FALSE;
@@ -1114,6 +1508,7 @@ METHOD(task_t, migrate, void,
 METHOD(task_t, destroy, void,
        private_ike_auth_t *this)
 {
+       clear_ppk(this);
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
        DESTROY_IF(this->my_packet);
@@ -1121,6 +1516,7 @@ METHOD(task_t, destroy, void,
        DESTROY_IF(this->my_auth);
        DESTROY_IF(this->other_auth);
        DESTROY_IF(this->peer_cfg);
+       DESTROY_IF(this->redirect_to);
        this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy));
        free(this);
 }