Authenticate and verify Phase 2 IKEv1 messages with appropriate hashes.
authorTobias Brunner <tobias@strongswan.org>
Wed, 23 Nov 2011 12:17:46 +0000 (13:17 +0100)
committerTobias Brunner <tobias@strongswan.org>
Tue, 20 Mar 2012 16:30:50 +0000 (17:30 +0100)
src/libcharon/encoding/message.c
src/libcharon/sa/keymat_v1.c
src/libcharon/sa/keymat_v1.h

index 032fc1b..f53be00 100644 (file)
@@ -29,6 +29,7 @@
 #include <encoding/parser.h>
 #include <encoding/payloads/encodings.h>
 #include <encoding/payloads/payload.h>
+#include <encoding/payloads/hash_payload.h>
 #include <encoding/payloads/encryption_payload.h>
 #include <encoding/payloads/unknown_payload.h>
 #include <encoding/payloads/cp_payload.h>
@@ -1408,7 +1409,19 @@ METHOD(message_t, generate, status_t,
                encrypted = this->rule->encrypted;
        }
        else
-       {       /* if at least one payload requires encryption, encrypt the message */
+       {
+               /* get a hash for this message, if any is required */
+               chunk_t hash = keymat_v1->get_hash_phase2(keymat_v1, &this->public);
+               if (hash.ptr)
+               {       /* insert a HASH payload as first payload */
+                       hash_payload_t *hash_payload = hash_payload_create();
+                       hash_payload->set_hash(hash_payload, hash);
+                       this->payloads->insert_first(this->payloads,
+                                                                                (payload_t*)hash_payload);
+                       chunk_free(&hash);
+               }
+
+               /* if at least one payload requires encryption, encrypt the message */
                /* TODO-IKEV1: set is_encrypted externally instead of this check? */
                enumerator = this->payloads->create_enumerator(this->payloads);
                while (enumerator->enumerate(enumerator, (void**)&payload))
@@ -1821,7 +1834,7 @@ static status_t verify(private_message_t *this)
 
        DBG2(DBG_ENC, "verifying message structure");
 
-       /* check for payloads with wrong count*/
+       /* check for payloads with wrong count */
        for (i = 0; i < this->rule->rule_count; i++)
        {
                enumerator_t *enumerator;
@@ -1906,6 +1919,35 @@ METHOD(message_t, parse_body, status_t,
 
        DBG1(DBG_ENC, "parsed %s", get_string(this, str, sizeof(str)));
 
+       if (this->major_version == IKEV1_MAJOR_VERSION)
+       {
+               keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat;
+               chunk_t hash;
+               hash = keymat_v1->get_hash_phase2(keymat_v1, &this->public);
+               if (hash.ptr)
+               {
+                       hash_payload_t *hash_payload;
+                       chunk_t other_hash;
+                       if (this->first_payload != HASH_V1)
+                       {
+                               DBG1(DBG_ENC, "expected HASH payload as first payload");
+                               chunk_free(&hash);
+                               return VERIFY_ERROR;
+                       }
+                       hash_payload = (hash_payload_t*)get_payload(this, HASH_V1);
+                       other_hash = hash_payload->get_hash(hash_payload);
+                       if (!chunk_equals(hash, other_hash))
+                       {
+                               DBG1(DBG_ENC, "our hash does not match received %B",
+                                        &other_hash);
+                               chunk_free(&hash);
+                               return VERIFY_ERROR;
+                       }
+                       DBG2(DBG_ENC, "verified IKEv1 message with hash %B", &hash);
+                       chunk_free(&hash);
+               }
+       }
+
        if (this->is_encrypted)
        {       /* TODO-IKEv1: this should be done later when we know this is no
                 * retransmit */
index 57b9675..8b9ac49 100644 (file)
@@ -16,6 +16,8 @@
 #include "keymat_v1.h"
 
 #include <daemon.h>
+#include <encoding/generator.h>
+#include <encoding/payloads/nonce_payload.h>
 #include <utils/linked_list.h>
 
 typedef struct private_keymat_v1_t private_keymat_v1_t;
@@ -26,6 +28,11 @@ typedef struct private_keymat_v1_t private_keymat_v1_t;
 #define MAX_IV 3
 
 /**
+ * Max. number of Quick Modes to track.
+ */
+#define MAX_QM 2
+
+/**
  * Data stored for IVs
  */
 typedef struct {
@@ -97,6 +104,12 @@ struct private_keymat_v1_t {
         * number of IVs in an MRU sort of way. Stores iv_data_t objects.
         */
        linked_list_t *ivs;
+
+       /**
+        * Keep track of Nonces during Quick Mode exchanges. Only a limited number
+        * of QMs are tracked at the same time. Stores qm_data_t objects.
+        */
+       linked_list_t *qms;
 };
 
 
@@ -111,6 +124,28 @@ static void iv_data_destroy(iv_data_t *this)
 }
 
 /**
+ * Data stored for Quick Mode exchanges
+ */
+typedef struct {
+       /** message ID */
+       u_int32_t mid;
+       /** Ni_b (Nonce from first message) */
+       chunk_t n_i;
+       /** Nr_b (Nonce from second message) */
+       chunk_t n_r;
+} qm_data_t;
+
+/**
+ * Destroy a qm_data_t object.
+ */
+static void qm_data_destroy(qm_data_t *this)
+{
+       chunk_free(&this->n_i);
+       chunk_free(&this->n_r);
+       free(this);
+}
+
+/**
  * Constants used in key derivation.
  */
 static const chunk_t octet_0 = chunk_from_chars(0x00);
@@ -617,6 +652,180 @@ METHOD(keymat_v1_t, get_hash, chunk_t,
 }
 
 /**
+ * Get the nonce value found in the given message.
+ * Returns FALSE if none is found.
+ */
+static bool get_nonce(message_t *message, chunk_t *n)
+{
+       nonce_payload_t *nonce;
+       nonce = (nonce_payload_t*)message->get_payload(message, NONCE_V1);
+       if (nonce)
+       {
+               *n = nonce->get_nonce(nonce);
+               return TRUE;
+       }
+       return FALSE;
+}
+
+/**
+ * Generate the message data in order to generate the hashes.
+ */
+static chunk_t get_message_data(message_t *message, generator_t *generator)
+{
+       payload_t *payload, *next;
+       enumerator_t *enumerator;
+       u_int32_t *lenpos;
+
+       if (message->is_encoded(message))
+       {       /* inbound, although the message is generated, we cannot access the
+                * cleartext message data, so generate it anyway */
+               enumerator = message->create_payload_enumerator(message);
+               while (enumerator->enumerate(enumerator, &payload))
+               {
+                       if (payload->get_type(payload) == HASH_V1)
+                       {
+                               continue;
+                       }
+                       generator->generate_payload(generator, payload);
+               }
+               enumerator->destroy(enumerator);
+       }
+       else
+       {
+               /* outbound, generate the payloads (there is no HASH payload yet) */
+               enumerator = message->create_payload_enumerator(message);
+               if (enumerator->enumerate(enumerator, &payload))
+               {
+                       while (enumerator->enumerate(enumerator, &next))
+                       {
+                               payload->set_next_type(payload, next->get_type(next));
+                               generator->generate_payload(generator, payload);
+                               payload = next;
+                       }
+                       payload->set_next_type(payload, NO_PAYLOAD);
+                       generator->generate_payload(generator, payload);
+               }
+               enumerator->destroy(enumerator);
+       }
+       return generator->get_chunk(generator, &lenpos);
+}
+
+/**
+ * Try to find data about a Quick Mode with the given message ID,
+ * if none is found, state is generated.
+ */
+static qm_data_t *lookup_quick_mode(private_keymat_v1_t *this, u_int32_t mid)
+{
+       enumerator_t *enumerator;
+       qm_data_t *qm, *found = NULL;
+
+       enumerator = this->qms->create_enumerator(this->qms);
+       while (enumerator->enumerate(enumerator, &qm))
+       {
+               if (qm->mid == mid)
+               {       /* state gets moved to the front of the list */
+                       this->qms->remove_at(this->qms, enumerator);
+                       found = qm;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       if (!found)
+       {
+               INIT(found,
+                       .mid = mid,
+               );
+       }
+       this->qms->insert_first(this->qms, found);
+       /* remove least recently used state if maximum reached */
+       if (this->qms->get_count(this->qms) > MAX_QM &&
+               this->qms->remove_last(this->qms, (void**)&qm) == SUCCESS)
+       {
+               qm_data_destroy(qm);
+       }
+       return found;
+}
+
+METHOD(keymat_v1_t, get_hash_phase2, chunk_t,
+       private_keymat_v1_t *this, message_t *message)
+{
+       u_int32_t mid = message->get_message_id(message), mid_n = htonl(mid);
+       chunk_t data = chunk_empty, hash = chunk_empty;
+       bool add_message = TRUE;
+       char *name = "Hash";
+
+       /* Hashes are simple for most exchanges in Phase 2:
+        *   Hash = prf(SKEYID_a, M-ID | Complete message after HASH payload)
+        * For Quick Mode there are three hashes:
+        *   Hash(1) = same as above
+        *   Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH payload)
+        *   Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b)
+        * So, for Quick Mode we keep track of the nonce values.
+        */
+       switch (message->get_exchange_type(message))
+       {
+               case QUICK_MODE:
+               {
+                       qm_data_t *qm = lookup_quick_mode(this, mid);
+                       if (!qm->n_i.ptr)
+                       {       /* Hash(1) = prf(SKEYID_a, M-ID | Message after HASH payload) */
+                               name = "Hash(1)";
+                               if (!get_nonce(message, &qm->n_i))
+                               {
+                                       return hash;
+                               }
+                               data = chunk_from_thing(mid_n);
+                       }
+                       else if (!qm->n_r.ptr)
+                       {       /* Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH) */
+                               name = "Hash(2)";
+                               if (!get_nonce(message, &qm->n_r))
+                               {
+                                       return hash;
+                               }
+                               data = chunk_cata("cc", chunk_from_thing(mid_n), qm->n_i);
+                       }
+                       else
+                       {       /* Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) */
+                               name = "Hash(3)";
+                               data = chunk_cata("cccc", octet_0, chunk_from_thing(mid_n),
+                                                                 qm->n_i, qm->n_r);
+                               add_message = FALSE;
+                               /* we don't need the state anymore */
+                               this->qms->remove(this->qms, qm, NULL);
+                               qm_data_destroy(qm);
+                       }
+                       break;
+               }
+               case TRANSACTION:
+               case INFORMATIONAL_V1:
+                       /* Hash = prf(SKEYID_a, M-ID | Message after HASH payload) */
+                       data = chunk_from_thing(mid_n);
+                       break;
+               default:
+                       break;
+       }
+       if (data.ptr)
+       {
+               this->prf->set_key(this->prf, this->skeyid_a);
+               if (add_message)
+               {
+                       generator_t *generator = generator_create();
+                       chunk_t msg = get_message_data(message, generator);
+                       this->prf->allocate_bytes(this->prf, data, NULL);
+                       this->prf->allocate_bytes(this->prf, msg, &hash);
+                       generator->destroy(generator);
+               }
+               else
+               {
+                       this->prf->allocate_bytes(this->prf, data, &hash);
+               }
+               DBG3(DBG_IKE, "%s %B", name, &hash);
+       }
+       return hash;
+}
+
+/**
  * Generate an IV
  */
 static void generate_iv(private_keymat_v1_t *this, iv_data_t *iv)
@@ -734,6 +943,7 @@ METHOD(keymat_t, destroy, void,
        chunk_free(&this->phase1_iv.iv);
        chunk_free(&this->phase1_iv.last_block);
        this->ivs->destroy_function(this->ivs, (void*)iv_data_destroy);
+       this->qms->destroy_function(this->qms, (void*)qm_data_destroy);
        free(this);
 }
 
@@ -754,11 +964,13 @@ keymat_v1_t *keymat_v1_create(bool initiator)
                        .derive_ike_keys = _derive_ike_keys,
                        .derive_child_keys = _derive_child_keys,
                        .get_hash = _get_hash,
+                       .get_hash_phase2 = _get_hash_phase2,
                        .get_iv = _get_iv,
                        .update_iv = _update_iv,
                        .confirm_iv = _confirm_iv,
                },
                .ivs = linked_list_create(),
+               .qms = linked_list_create(),
                .initiator = initiator,
                .prf_alg = PRF_UNDEFINED,
        );
index fb5a1b6..e2d5cf6 100644 (file)
@@ -89,6 +89,15 @@ struct keymat_v1_t {
                                                chunk_t sa_i, identification_t *id);
 
        /**
+        * Get HASH data for integrity/authentication in Phase 2 exchanges.
+        *
+        * @param message               message to generate the HASH data for
+        * @return                              allocated HASH data
+        */
+       chunk_t (*get_hash_phase2)(keymat_v1_t *this, message_t *message);
+
+
+       /**
         * Returns the IV for a message with the given message ID.
         *
         * @param mid                   message ID