Added IV generation to keymat_v1_t.
authorTobias Brunner <tobias@strongswan.org>
Mon, 21 Nov 2011 12:11:16 +0000 (13:11 +0100)
committerTobias Brunner <tobias@strongswan.org>
Tue, 20 Mar 2012 16:30:46 +0000 (17:30 +0100)
src/libcharon/sa/keymat_v1.c
src/libcharon/sa/keymat_v1.h

index 3592a3e..891f6a2 100644 (file)
 #include "keymat_v1.h"
 
 #include <daemon.h>
+#include <utils/linked_list.h>
 
 typedef struct private_keymat_v1_t private_keymat_v1_t;
 
 /**
+ * Max. number of IVs to track.
+ */
+#define MAX_IV 3
+
+/**
+ * Data stored for IVs
+ */
+typedef struct {
+       /** message ID */
+       u_int32_t mid;
+       /** current IV */
+       chunk_t iv;
+       /** last block of encrypted message */
+       chunk_t last_block;
+} iv_data_t;
+
+/**
  * Private data of an keymat_t object.
  */
 struct private_keymat_v1_t {
@@ -50,6 +68,11 @@ struct private_keymat_v1_t {
        aead_t *aead;
 
        /**
+        * Hasher used for IV generation
+        */
+       hasher_t *hasher;
+
+       /**
         * Key used for authentication during main mode
         */
        chunk_t skeyid;
@@ -63,8 +86,30 @@ struct private_keymat_v1_t {
         * Key used for authentication after main mode
         */
        chunk_t skeyid_a;
+
+       /**
+        * Phase 1 IV
+        */
+       iv_data_t phase1_iv;
+
+       /**
+        * Keep track of IVs for exchanges after phase 1. We store only a limited
+        * number of IVs in an MRU sort of way. Stores iv_data_t objects.
+        */
+       linked_list_t *ivs;
 };
 
+
+/**
+ * Destroy an iv_data_t object.
+ */
+static void iv_data_destroy(iv_data_t *this)
+{
+       chunk_free(&this->last_block);
+       chunk_free(&this->iv);
+       free(this);
+}
+
 /**
  * Constants used in key derivation.
  */
@@ -240,6 +285,28 @@ static u_int16_t auth_to_prf(u_int16_t alg)
 }
 
 /**
+ * Converts integrity algorithm to hash algorithm
+ */
+static u_int16_t auth_to_hash(u_int16_t alg)
+{
+       switch (alg)
+       {
+               case AUTH_HMAC_SHA1_96:
+                       return HASH_SHA1;
+               case AUTH_HMAC_SHA2_256_128:
+                       return HASH_SHA256;
+               case AUTH_HMAC_SHA2_384_192:
+                       return HASH_SHA384;
+               case AUTH_HMAC_SHA2_512_256:
+                       return HASH_SHA512;
+               case AUTH_HMAC_MD5_96:
+                       return HASH_MD5;
+               default:
+                       return HASH_UNKNOWN;
+       }
+}
+
+/**
  * Adjust the key length for PRF algorithms that expect a fixed key length.
  */
 static void adjust_keylen(u_int16_t alg, chunk_t *key)
@@ -367,9 +434,132 @@ METHOD(keymat_v1_t, derive_ike_keys, bool,
                return FALSE;
        }
 
+       if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL) ||
+               (alg = auth_to_hash(alg)) == HASH_UNKNOWN)
+       {
+               DBG1(DBG_IKE, "no %N selected", transform_type_names, HASH_ALGORITHM);
+               return FALSE;
+       }
+       this->hasher = lib->crypto->create_hasher(lib->crypto, alg);
+       if (!this->hasher)
+       {
+               DBG1(DBG_IKE, "%N %N not supported!",
+                        transform_type_names, HASH_ALGORITHM,
+                        hash_algorithm_names, alg);
+               return FALSE;
+       }
+
+       dh->get_my_public_value(dh, &dh_me);
+       g_xi = this->initiator ? dh_me : dh_other;
+       g_xr = this->initiator ? dh_other : dh_me;
+
+       /* initial IV = hash(g^xi | g^xr) */
+       data = chunk_cata("cc", g_xi, g_xr);
+       this->hasher->allocate_hash(this->hasher, data, &this->phase1_iv.iv);
+       if (this->phase1_iv.iv.len > this->aead->get_block_size(this->aead))
+       {
+               this->phase1_iv.iv.len = this->aead->get_block_size(this->aead);
+       }
+       chunk_free(&dh_me);
+       DBG4(DBG_IKE, "initial IV %B", &this->phase1_iv.iv);
+
        return TRUE;
 }
 
+/**
+ * Generate an IV
+ */
+static void generate_iv(private_keymat_v1_t *this, iv_data_t *iv)
+{
+       if (iv->mid == 0 || iv->iv.ptr)
+       {       /* use last block of previous encrypted message */
+               chunk_free(&iv->iv);
+               iv->iv = iv->last_block;
+               iv->last_block = chunk_empty;
+       }
+       else
+       {
+               /* initial phase 2 IV = hash(last_phase1_block | mid) */
+               u_int32_t net = htonl(iv->mid);
+               chunk_t data = chunk_cata("cc", this->phase1_iv.iv,
+                                                                 chunk_from_thing(net));
+               this->hasher->allocate_hash(this->hasher, data, &iv->iv);
+               if (iv->iv.len > this->aead->get_block_size(this->aead))
+               {
+                       iv->iv.len = this->aead->get_block_size(this->aead);
+               }
+       }
+       DBG4(DBG_IKE, "next IV for MID %u %B", iv->mid, &iv->iv);
+}
+
+/**
+ * Try to find an IV for the given message ID, if not found, generate it.
+ */
+static iv_data_t *lookup_iv(private_keymat_v1_t *this, u_int32_t mid)
+{
+       enumerator_t *enumerator;
+       iv_data_t *iv, *found = NULL;
+
+       if (mid == 0)
+       {
+               return &this->phase1_iv;
+       }
+
+       enumerator = this->ivs->create_enumerator(this->ivs);
+       while (enumerator->enumerate(enumerator, &iv))
+       {
+               if (iv->mid == mid)
+               {       /* IV gets moved to the front of the list */
+                       this->ivs->remove_at(this->ivs, enumerator);
+                       found = iv;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       if (!found)
+       {
+               INIT(found,
+                       .mid = mid,
+               );
+               generate_iv(this, found);
+       }
+       this->ivs->insert_first(this->ivs, found);
+       /* remove least recently used IV if maximum reached */
+       if (this->ivs->get_count(this->ivs) > MAX_IV &&
+               this->ivs->remove_last(this->ivs, (void**)&iv) == SUCCESS)
+       {
+               iv_data_destroy(iv);
+       }
+       return found;
+}
+
+METHOD(keymat_v1_t, get_iv, chunk_t,
+       private_keymat_v1_t *this, u_int32_t mid)
+{
+       return chunk_clone(lookup_iv(this, mid)->iv);
+}
+
+METHOD(keymat_v1_t, update_iv, void,
+       private_keymat_v1_t *this, u_int32_t mid, chunk_t last_block)
+{
+       iv_data_t *iv = lookup_iv(this, mid);
+       if (iv)
+       {       /* update last block */
+               chunk_free(&iv->last_block);
+               iv->last_block = chunk_clone(last_block);
+       }
+}
+
+METHOD(keymat_v1_t, confirm_iv, void,
+       private_keymat_v1_t *this, u_int32_t mid)
+{
+       iv_data_t *iv = lookup_iv(this, mid);
+       if (iv)
+       {
+               generate_iv(this, iv);
+       }
+}
+
 METHOD(keymat_t, create_dh, diffie_hellman_t*,
        private_keymat_v1_t *this, diffie_hellman_group_t group)
 {
@@ -387,9 +577,13 @@ METHOD(keymat_t, destroy, void,
 {
        DESTROY_IF(this->prf);
        DESTROY_IF(this->aead);
+       DESTROY_IF(this->hasher);
        chunk_clear(&this->skeyid);
        chunk_clear(&this->skeyid_d);
        chunk_clear(&this->skeyid_a);
+       chunk_free(&this->phase1_iv.iv);
+       chunk_free(&this->phase1_iv.last_block);
+       this->ivs->destroy_function(this->ivs, (void*)iv_data_destroy);
        free(this);
 }
 
@@ -408,7 +602,11 @@ keymat_v1_t *keymat_v1_create(bool initiator)
                                .destroy = _destroy,
                        },
                        .derive_ike_keys = _derive_ike_keys,
+                       .get_iv = _get_iv,
+                       .update_iv = _update_iv,
+                       .confirm_iv = _confirm_iv,
                },
+               .ivs = linked_list_create(),
                .initiator = initiator,
                .prf_alg = PRF_UNDEFINED,
        );
index e85d239..54d4d43 100644 (file)
@@ -56,6 +56,37 @@ struct keymat_v1_t {
                                                        chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id,
                                                        auth_class_t auth, shared_key_t *shared_key);
 
+       /**
+        * Returns the IV for a message with the given message ID.
+        *
+        * @param mid                   message ID
+        * @return                              IV (needs to be freed)
+        */
+       chunk_t (*get_iv)(keymat_v1_t *this, u_int32_t mid);
+
+       /**
+        * Updates the IV for the next message with the given message ID.
+        *
+        * A call of confirm_iv() is required in order to actually make the IV
+        * available.  This is needed for the inbound case where we store the last
+        * block of the encrypted message but want to update the IV only after
+        * verification of the decrypted message.
+        *
+        * @param mid                   message ID
+        * @param last_block    last block of encrypted message (gets cloned)
+        */
+       void (*update_iv)(keymat_v1_t *this, u_int32_t mid, chunk_t last_block);
+
+       /**
+        * Confirms the updated IV for the given message ID.
+        *
+        * To actually make the new IV available via get_iv this method has to
+        * be called after update_iv.
+        *
+        * @param mid                   message ID
+        */
+       void (*confirm_iv)(keymat_v1_t *this, u_int32_t mid);
+
 };
 
 /**