Authenticate and verify Phase 2 IKEv1 messages with appropriate hashes.
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
@@ -25,6 +27,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
|
||||
*/
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -110,6 +123,28 @@ static void iv_data_destroy(iv_data_t *this)
|
||||
free(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.
|
||||
*/
|
||||
@@ -616,6 +651,180 @@ METHOD(keymat_v1_t, get_hash, chunk_t,
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -88,6 +88,15 @@ struct keymat_v1_t {
|
||||
chunk_t dh, chunk_t dh_other, ike_sa_id_t *ike_sa_id,
|
||||
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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user