message: Fragment and reassemble IKEv2 messages

This commit is contained in:
Tobias Brunner 2014-06-16 15:46:33 +02:00
parent e8ffb256b3
commit c0f4936a23
2 changed files with 368 additions and 135 deletions

View File

@ -33,6 +33,7 @@
#include <encoding/payloads/payload.h>
#include <encoding/payloads/hash_payload.h>
#include <encoding/payloads/encrypted_payload.h>
#include <encoding/payloads/encrypted_fragment_payload.h>
#include <encoding/payloads/unknown_payload.h>
#include <encoding/payloads/cp_payload.h>
#include <encoding/payloads/fragment_payload.h>
@ -819,8 +820,9 @@ typedef struct {
* For IKEv1 the number of the last fragment (in case we receive them out
* of order), since the first one starts with 1 this defines the number of
* fragments we expect.
* For IKEv2 we store the total number of fragment we received last.
*/
u_int8_t last;
u_int16_t last;
/**
* Length of all currently received fragments.
@ -908,7 +910,7 @@ struct private_message_t {
/**
* Array of generated fragments (if any), as packet_t*.
* If defragmenting (frag != NULL) this contains fragment_t*
* If defragmenting (i.e. frag != NULL) this contains fragment_t*
*/
array_t *fragments;
@ -952,11 +954,10 @@ static void fragment_destroy(fragment_t *this)
free(this);
}
static void reset_defrag(private_message_t *this, u_int16_t id)
static void reset_defrag(private_message_t *this)
{
array_destroy_function(this->fragments, (void*)fragment_destroy, NULL);
this->fragments = NULL;
this->message_id = id;
this->frag->last = 0;
this->frag->len = 0;
}
@ -1822,35 +1823,64 @@ static message_t *clone_message(private_message_t *this)
message->set_source(message, src->clone(src));
message->set_destination(message, dst->clone(dst));
message->set_exchange_type(message, this->exchange_type);
memcpy(((private_message_t*)message)->reserved, this->reserved,
sizeof(this->reserved));
return message;
}
/**
* Create a single fragment with the given data
*/
static message_t *create_fragment(private_message_t *this, u_int8_t num,
bool last, chunk_t data)
static message_t *create_fragment(private_message_t *this, payload_type_t next,
u_int16_t num, u_int16_t count, chunk_t data)
{
fragment_payload_t *fragment;
enumerator_t *enumerator;
payload_t *fragment, *payload;
message_t *message;
peer_cfg_t *peer_cfg;
ike_sa_t *ike_sa;
fragment = fragment_payload_create_from_data(num, last, data);
message = clone_message(this);
/* other implementations seem to just use 0 as message ID, so here we go */
message->set_message_id(message, 0);
/* always use the initial message type for fragments, even for quick mode
* or transaction messages. */
ike_sa = charon->bus->get_sa(charon->bus);
if (ike_sa && (peer_cfg = ike_sa->get_peer_cfg(ike_sa)) &&
peer_cfg->use_aggressive(peer_cfg))
if (this->major_version == IKEV1_MAJOR_VERSION)
{
message->set_exchange_type(message, AGGRESSIVE);
/* other implementations seem to just use 0 as message ID, so here we go */
message->set_message_id(message, 0);
/* always use the initial message type for fragments, even for quick mode
* or transaction messages. */
ike_sa = charon->bus->get_sa(charon->bus);
if (ike_sa && (peer_cfg = ike_sa->get_peer_cfg(ike_sa)) &&
peer_cfg->use_aggressive(peer_cfg))
{
message->set_exchange_type(message, AGGRESSIVE);
}
else
{
message->set_exchange_type(message, ID_PROT);
}
fragment = (payload_t*)fragment_payload_create_from_data(
num, num == count, data);
}
else
{
message->set_exchange_type(message, ID_PROT);
fragment = (payload_t*)encrypted_fragment_payload_create_from_data(
num, count, data);
if (num == 1)
{
/* only in the first fragment is this set to the type of the first
* payload in the encrypted payload */
fragment->set_next_type(fragment, next);
/* move unencrypted payloads to the first fragment */
enumerator = this->payloads->create_enumerator(this->payloads);
while (enumerator->enumerate(enumerator, &payload))
{
if (payload->get_type(payload) != PLV2_ENCRYPTED)
{
this->payloads->remove_at(this->payloads, enumerator);
message->add_payload(message, payload);
}
}
enumerator->destroy(enumerator);
}
}
message->add_payload(message, (payload_t*)fragment);
return message;
@ -1869,29 +1899,18 @@ METHOD(message_t, fragment, status_t,
private_message_t *this, keymat_t *keymat, size_t frag_len,
enumerator_t **fragments)
{
encrypted_payload_t *encrypted = NULL;
generator_t *generator = NULL;
message_t *fragment;
packet_t *packet;
u_int8_t num, count;
payload_type_t next = PL_NONE;
u_int16_t num, count;
host_t *src, *dst;
chunk_t data;
status_t status;
u_int32_t *lenpos;
size_t len;
if (this->major_version == IKEV2_MAJOR_VERSION)
{
return INVALID_STATE;
}
clear_fragments(this);
if (!is_encoded(this))
{
status = generate(this, keymat, NULL);
if (status != SUCCESS)
{
return status;
}
}
src = this->packet->get_source(this->packet);
dst = this->packet->get_destination(this->packet);
if (!frag_len)
@ -1901,31 +1920,112 @@ METHOD(message_t, fragment, status_t,
/* frag_len is the complete IP datagram length, account for overhead (we
* assume no IP options/extension headers are used) */
frag_len -= (src->get_family(src) == AF_INET) ? 20 : 40;
/* 8 (UDP header) + 28 (IKE header) */
frag_len -= 36;
/* 8 (UDP header) */
frag_len -= 8;
if (dst->get_port(dst) != IKEV2_UDP_PORT &&
src->get_port(src) != IKEV2_UDP_PORT)
{ /* reduce length due to non-ESP marker */
frag_len -= 4;
}
data = this->packet->get_data(this->packet);
if (data.len <= frag_len)
if (is_encoded(this))
{
if (this->major_version == IKEV2_MAJOR_VERSION)
{
encrypted = (encrypted_payload_t*)get_payload(this, PLV2_ENCRYPTED);
}
data = this->packet->get_data(this->packet);
len = data.len;
}
else
{
status = generate_message(this, keymat, &generator, &encrypted);
if (status != SUCCESS)
{
DESTROY_IF(generator);
return status;
}
data = generator->get_chunk(generator, &lenpos);
len = data.len + (encrypted ? encrypted->get_length(encrypted) : 0);
}
/* check if we actually need to fragment the message and if we have an
* encrypted payload for IKEv2 */
if (len <= frag_len ||
(this->major_version == IKEV2_MAJOR_VERSION && !encrypted))
{
if (generator)
{
status = finalize_message(this, keymat, generator, encrypted);
if (status != SUCCESS)
{
return status;
}
}
*fragments = enumerator_create_single(this->packet, NULL);
return SUCCESS;
}
/* overhead for the fragmentation payload header */
frag_len -= 8;
/* frag_len denoted the maximum IKE message size so far, later on it will
* denote the maximum content size of a fragment payload, therefore,
* account for IKE header */
frag_len -= 28;
if (this->major_version == IKEV1_MAJOR_VERSION)
{
if (generator)
{
status = finalize_message(this, keymat, generator, encrypted);
if (status != SUCCESS)
{
return status;
}
data = this->packet->get_data(this->packet);
generator = NULL;
}
/* overhead for the fragmentation payload header */
frag_len -= 8;
}
else
{
aead_t *aead;
if (generator)
{
generator->destroy(generator);
generator = generator_create();
}
else
{ /* do not log again if it was generated previously */
generator = generator_create_no_dbg();
}
next = encrypted->payload_interface.get_next_type((payload_t*)encrypted);
encrypted->generate_payloads(encrypted, generator);
data = generator->get_chunk(generator, &lenpos);
if (!is_encoded(this))
{
encrypted->destroy(encrypted);
}
aead = keymat->get_aead(keymat, FALSE);
/* overhead for the encrypted fragment payload */
frag_len -= aead->get_iv_size(aead) + aead->get_icv_size(aead);
frag_len -= 8 /* header */;
/* padding and padding length */
frag_len = round_down(frag_len, aead->get_block_size(aead)) - 1;
/* TODO-FRAG: if there are unencrypted payloads, should we account for
* their length in the first fragment? we still would have to add
* an encrypted fragment payload (albeit empty), even so we couldn't
* prevent IP fragmentation in every case */
}
count = data.len / frag_len + (data.len % frag_len ? 1 : 0);
this->fragments = array_create(0, count);
DBG2(DBG_ENC, "splitting IKE message with length of %zu bytes into "
"%hhu fragments", data.len, count);
DBG1(DBG_ENC, "splitting IKE message with length of %zu bytes into "
"%hu fragments", len, count);
for (num = 1; num <= count; num++)
{
len = min(data.len, frag_len);
fragment = create_fragment(this, num, num == count,
fragment = create_fragment(this, next, num, count,
chunk_create(data.ptr, len));
status = fragment->generate(fragment, keymat, &packet);
fragment->destroy(fragment);
@ -1933,12 +2033,14 @@ METHOD(message_t, fragment, status_t,
{
DBG1(DBG_ENC, "failed to generate IKE fragment");
clear_fragments(this);
DESTROY_IF(generator);
return FAILED;
}
array_insert(this->fragments, ARRAY_TAIL, packet);
data = chunk_skip(data, len);
}
*fragments = array_create_enumerator(this->fragments);
DESTROY_IF(generator);
return SUCCESS;
}
@ -1970,6 +2072,10 @@ METHOD(message_t, parse_header, status_t,
DBG2(DBG_ENC, "parsing header of message");
if (!this->parser)
{ /* reassembled IKEv2 message, header is inherited from fragments */
return SUCCESS;
}
this->parser->reset_context(this->parser);
status = this->parser->parse_payload(this->parser, PL_HEADER,
(payload_t**)&ike_header);
@ -2149,43 +2255,52 @@ static status_t decrypt_and_extract(private_message_t *this, keymat_t *keymat,
DBG1(DBG_ENC, "found encrypted payload, but no transform set");
return INVALID_ARG;
}
bs = aead->get_block_size(aead);
encryption->set_transform(encryption, aead);
chunk = this->packet->get_data(this->packet);
if (chunk.len < encryption->get_length(encryption) ||
chunk.len < bs)
if (!this->parser)
{
DBG1(DBG_ENC, "invalid payload length");
return VERIFY_ERROR;
/* reassembled IKEv2 messages are already decrypted, we still call
* decrypt() to parse the contained payloads */
status = encryption->decrypt(encryption, chunk_empty);
}
if (keymat->get_version(keymat) == IKEV1)
{ /* instead of associated data we provide the IV, we also update
* the IV with the last encrypted block */
keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat;
chunk_t iv;
if (keymat_v1->get_iv(keymat_v1, this->message_id, &iv))
else
{
bs = aead->get_block_size(aead);
encryption->set_transform(encryption, aead);
chunk = this->packet->get_data(this->packet);
if (chunk.len < encryption->get_length(encryption) ||
chunk.len < bs)
{
status = encryption->decrypt(encryption, iv);
if (status == SUCCESS)
DBG1(DBG_ENC, "invalid payload length");
return VERIFY_ERROR;
}
if (keymat->get_version(keymat) == IKEV1)
{ /* instead of associated data we provide the IV, we also update
* the IV with the last encrypted block */
keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat;
chunk_t iv;
if (keymat_v1->get_iv(keymat_v1, this->message_id, &iv))
{
if (!keymat_v1->update_iv(keymat_v1, this->message_id,
chunk_create(chunk.ptr + chunk.len - bs, bs)))
status = encryption->decrypt(encryption, iv);
if (status == SUCCESS)
{
status = FAILED;
if (!keymat_v1->update_iv(keymat_v1, this->message_id,
chunk_create(chunk.ptr + chunk.len - bs, bs)))
{
status = FAILED;
}
}
}
else
{
status = FAILED;
}
}
else
{
status = FAILED;
chunk.len -= encryption->get_length(encryption);
status = encryption->decrypt(encryption, chunk);
}
}
else
{
chunk.len -= encryption->get_length(encryption);
status = encryption->decrypt(encryption, chunk);
}
if (status != SUCCESS)
{
return status;
@ -2432,10 +2547,15 @@ METHOD(message_t, parse_body, status_t,
return NOT_SUPPORTED;
}
status = parse_payloads(this);
if (status != SUCCESS)
{ /* error is already logged */
return status;
/* reassembled IKEv2 messages are already parsed (except for the payloads
* contained in the encrypted payload, which are handled below) */
if (this->parser)
{
status = parse_payloads(this);
if (status != SUCCESS)
{ /* error is already logged */
return status;
}
}
status = decrypt_payloads(this, keymat);
@ -2500,16 +2620,85 @@ METHOD(message_t, parse_body, status_t,
return SUCCESS;
}
METHOD(message_t, add_fragment, status_t,
private_message_t *this, message_t *message)
/**
* Store the fragment data for the fragment with the given fragment number.
*/
static status_t add_fragment(private_message_t *this, u_int16_t num,
chunk_t data)
{
fragment_t *fragment;
int i, insert_at = -1;
for (i = 0; i < array_count(this->fragments); i++)
{
array_get(this->fragments, i, &fragment);
if (fragment->num == num)
{
/* ignore a duplicate fragment */
DBG1(DBG_ENC, "received duplicate fragment #%hu", num);
return NEED_MORE;
}
if (fragment->num > num)
{
insert_at = i;
break;
}
}
this->frag->len += data.len;
if (this->frag->len > this->frag->max_packet)
{
DBG1(DBG_ENC, "fragmented IKE message is too large");
reset_defrag(this);
return FAILED;
}
INIT(fragment,
.num = num,
.data = chunk_clone(data),
);
array_insert(this->fragments, insert_at, fragment);
return SUCCESS;
}
/**
* Merge the cached fragment data and resets the defragmentation state.
* Also updates the IP addresses to those of the last received fragment.
*/
static chunk_t merge_fragments(private_message_t *this, message_t *last)
{
fragment_payload_t *payload;
fragment_t *fragment;
bio_writer_t *writer;
host_t *src, *dst;
chunk_t data;
int i;
writer = bio_writer_create(this->frag->len);
for (i = 0; i < array_count(this->fragments); i++)
{
array_get(this->fragments, i, &fragment);
writer->write_data(writer, fragment->data);
}
data = writer->extract_buf(writer);
writer->destroy(writer);
/* set addresses to those of the last fragment we received */
src = last->get_source(last);
dst = last->get_destination(last);
this->packet->set_source(this->packet, src->clone(src));
this->packet->set_destination(this->packet, dst->clone(dst));
reset_defrag(this);
free(this->frag);
this->frag = NULL;
return data;
}
METHOD(message_t, add_fragment_v1, status_t,
private_message_t *this, message_t *message)
{
fragment_payload_t *payload;
chunk_t data;
u_int8_t num;
int i, insert_at = -1;
status_t status;
if (!this->frag)
{
@ -2522,47 +2711,25 @@ METHOD(message_t, add_fragment, status_t,
}
if (!this->fragments || this->message_id != payload->get_id(payload))
{
reset_defrag(this, payload->get_id(payload));
/* we don't know the total number of fragments */
this->fragments = array_create(0, 0);
reset_defrag(this);
this->message_id = payload->get_id(payload);
/* we don't know the total number of fragments, assume something */
this->fragments = array_create(0, 4);
}
num = payload->get_number(payload);
data = payload->get_data(payload);
if (!this->frag->last && payload->is_last(payload))
{
this->frag->last = num;
}
for (i = 0; i < array_count(this->fragments); i++)
status = add_fragment(this, num, data);
if (status != SUCCESS)
{
array_get(this->fragments, i, &fragment);
if (fragment->num == num)
{
/* ignore a duplicate fragment */
DBG1(DBG_ENC, "received duplicate fragment #%hhu", num);
return NEED_MORE;
}
if (fragment->num > num)
{
insert_at = i;
break;
}
return status;
}
data = payload->get_data(payload);
this->frag->len += data.len;
if (this->frag->len > this->frag->max_packet)
{
DBG1(DBG_ENC, "fragmented IKE message is too large");
reset_defrag(this, 0);
return FAILED;
}
INIT(fragment,
.num = num,
.data = chunk_clone(data),
);
array_insert(this->fragments, insert_at, fragment);
if (this->frag->last < array_count(this->fragments))
if (array_count(this->fragments) != this->frag->last)
{
/* there are some fragments missing */
DBG1(DBG_ENC, "received fragment #%hhu, waiting for complete IKE "
@ -2570,26 +2737,12 @@ METHOD(message_t, add_fragment, status_t,
return NEED_MORE;
}
writer = bio_writer_create(this->frag->len);
DBG1(DBG_ENC, "received fragment #%hhu, reassembling fragmented IKE "
"message", num);
for (i = 0; i < array_count(this->fragments); i++)
{
array_get(this->fragments, i, &fragment);
writer->write_data(writer, fragment->data);
}
src = message->get_source(message);
dst = message->get_destination(message);
this->packet->set_source(this->packet, src->clone(src));
this->packet->set_destination(this->packet, dst->clone(dst));
this->packet->set_data(this->packet, writer->extract_buf(writer));
writer->destroy(writer);
this->parser->destroy(this->parser);
this->parser = parser_create(this->packet->get_data(this->packet));
reset_defrag(this, 0);
free(this->frag);
this->frag = NULL;
data = merge_fragments(this, message);
this->packet->set_data(this->packet, data);
this->parser = parser_create(data);
if (parse_header(this) != SUCCESS)
{
@ -2599,16 +2752,91 @@ METHOD(message_t, add_fragment, status_t,
return SUCCESS;
}
METHOD(message_t, add_fragment_v2, status_t,
private_message_t *this, message_t *message)
{
encrypted_fragment_payload_t *encrypted_fragment;
encrypted_payload_t *encrypted;
payload_t *payload;
enumerator_t *enumerator;
chunk_t data;
u_int16_t total, num;
status_t status;
if (!this->frag)
{
return INVALID_STATE;
}
payload = message->get_payload(message, PLV2_FRAGMENT);
if (!payload || this->message_id != message->get_message_id(message))
{
return INVALID_ARG;
}
encrypted_fragment = (encrypted_fragment_payload_t*)payload;
total = encrypted_fragment->get_total_fragments(encrypted_fragment);
if (!this->fragments || total > this->frag->last)
{
reset_defrag(this);
this->frag->last = total;
this->fragments = array_create(0, total);
}
num = encrypted_fragment->get_fragment_number(encrypted_fragment);
data = encrypted_fragment->get_content(encrypted_fragment);
status = add_fragment(this, num, data);
if (status != SUCCESS)
{
return status;
}
if (num == 1)
{
/* the first fragment denotes the payload type of the first payload in
* the original encrypted payload, cache that */
this->first_payload = payload->get_next_type(payload);
/* move all unencrypted payloads contained in the first fragment */
enumerator = message->create_payload_enumerator(message);
while (enumerator->enumerate(enumerator, &payload))
{
if (payload->get_type(payload) != PLV2_FRAGMENT)
{
message->remove_payload_at(message, enumerator);
this->payloads->insert_last(this->payloads, payload);
}
}
enumerator->destroy(enumerator);
}
if (array_count(this->fragments) != total)
{
/* there are some fragments missing */
DBG1(DBG_ENC, "received fragment #%hu of %hu, waiting for complete IKE "
"message", num, total);
return NEED_MORE;
}
DBG1(DBG_ENC, "received fragment #%hu of %hu, reassembling fragmented IKE "
"message", num, total);
data = merge_fragments(this, message);
encrypted = encrypted_payload_create_from_plain(this->first_payload, data);
this->payloads->insert_last(this->payloads, encrypted);
/* update next payload type (could be an unencrypted payload) */
this->payloads->get_first(this->payloads, (void**)&payload);
this->first_payload = payload->get_type(payload);
return SUCCESS;
}
METHOD(message_t, destroy, void,
private_message_t *this)
{
DESTROY_IF(this->ike_sa_id);
DESTROY_IF(this->parser);
this->payloads->destroy_offset(this->payloads, offsetof(payload_t, destroy));
this->packet->destroy(this->packet);
this->parser->destroy(this->parser);
if (this->frag)
{
reset_defrag(this, 0);
reset_defrag(this);
free(this->frag);
}
else
@ -2652,7 +2880,7 @@ message_t *message_create_from_packet(packet_t *packet)
.is_encoded = _is_encoded,
.is_fragmented = _is_fragmented,
.fragment = _fragment,
.add_fragment = _add_fragment,
.add_fragment = _add_fragment_v2,
.set_source = _set_source,
.get_source = _get_source,
.set_destination = _set_destination,
@ -2699,13 +2927,23 @@ message_t *message_create_defrag(message_t *fragment)
{
private_message_t *this;
if (!fragment->get_payload(fragment, PLV1_FRAGMENT))
if (!fragment->get_payload(fragment, PLV1_FRAGMENT) &&
!fragment->get_payload(fragment, PLV2_FRAGMENT))
{
return NULL;
}
this = (private_message_t*)message_create(
fragment->get_major_version(fragment),
fragment->get_minor_version(fragment));
this = (private_message_t*)clone_message((private_message_t*)fragment);
/* we don't need a parser for IKEv2, the one for IKEv1 is created after
* reassembling the original message */
this->parser->destroy(this->parser);
this->parser = NULL;
if (fragment->get_major_version(fragment) == IKEV1_MAJOR_VERSION)
{
/* we store the fragment ID in the message ID field, which should be
* zero for fragments, but make sure */
this->message_id = 0;
this->public.add_fragment = _add_fragment_v1;
}
INIT(this->frag,
.max_packet = lib->settings->get_int(lib->settings,
"%s.max_packet", MAX_PACKET, lib->ns),

View File

@ -268,8 +268,6 @@ struct message_t {
* Generates the message split into fragments of the given size (total IP
* datagram length).
*
* @note Only supported for IKEv1 at the moment.
*
* @param keymat keymat to encrypt/sign message(s)
* @param frag_len fragment length (maximum total IP datagram length), 0
* for default value depending on address family
@ -277,7 +275,6 @@ struct message_t {
* which are owned by the enumerator
* @return
* - SUCCESS if message could be fragmented
* - INVALID_STATE if message is IKEv2
* - FAILED if fragmentation failed
* - and the possible return values of generate()
*/
@ -303,8 +300,6 @@ struct message_t {
* Once the message is completed it should be processed like any other
* inbound message.
*
* @note Only supported for IKEv1 at the moment.
*
* @param fragment fragment to add
* @return
* - SUCCESS if message was reassembled