ike-auth: Add basic PPK support
authorTobias Brunner <tobias@strongswan.org>
Thu, 26 Jul 2018 15:28:13 +0000 (17:28 +0200)
committerTobias Brunner <tobias@strongswan.org>
Mon, 10 Sep 2018 16:03:02 +0000 (18:03 +0200)
Some of the work will have to be done in the authenticators.

src/libcharon/sa/ikev2/tasks/ike_auth.c

index 4fb2665..8d9fa91 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012-2015 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
  * Copyright (C) 2005-2009 Martin Willi
  * Copyright (C) 2005 Jan Hutter
  * HSR Hochschule fuer Technik Rapperswil
@@ -24,6 +24,7 @@
 #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>
 
@@ -60,6 +61,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
         */
        packet_t *my_packet;
@@ -279,6 +290,29 @@ static bool do_another_auth(private_ike_auth_t *this)
 }
 
 /**
+ * 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
  */
 static bool load_cfg_candidates(private_ike_auth_t *this)
@@ -404,6 +438,151 @@ 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_OPAQUE:
+                       /* we currently don't support this */
+               default:
+                       return FALSE;
+               case PPK_ID_FIXED:
+                       data = chunk_skip(data, 1);
+                       break;
+       }
+       *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)
 {
@@ -433,6 +612,12 @@ METHOD(task_t, build_i, status_t,
                /* 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)
@@ -508,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:
@@ -522,6 +715,12 @@ 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))
        {
@@ -655,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:
@@ -700,6 +912,36 @@ 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);
+       }
+       clear_ppk(this);
+       return TRUE;
+}
+
 METHOD(task_t, build_r, status_t,
        private_ike_auth_t *this, message_t *message)
 {
@@ -806,6 +1048,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:
@@ -820,6 +1066,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))
        {
@@ -955,7 +1211,7 @@ METHOD(task_t, process_i, status_t,
        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)
        {
@@ -1011,6 +1267,9 @@ METHOD(task_t, process_i, status_t,
                                        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)
@@ -1072,6 +1331,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:
@@ -1107,6 +1371,14 @@ METHOD(task_t, process_i, status_t,
 
        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:
@@ -1122,11 +1394,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))
@@ -1175,6 +1465,10 @@ 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,
@@ -1186,6 +1480,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);
@@ -1212,6 +1507,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);