diff options
author | Tobias Brunner <tobias@strongswan.org> | 2014-06-16 15:46:33 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2014-10-10 09:32:41 +0200 |
commit | c0f4936a23578625e7be7032d2a66db5d6a6c1a3 (patch) | |
tree | d485e09c82602cd0b7e4aa13223fce2bff773ec3 /src/libcharon/encoding | |
parent | e8ffb256b373de0094903fd227ccc8c5520caf9c (diff) | |
download | strongswan-c0f4936a23578625e7be7032d2a66db5d6a6c1a3.tar.bz2 strongswan-c0f4936a23578625e7be7032d2a66db5d6a6c1a3.tar.xz |
message: Fragment and reassemble IKEv2 messages
Diffstat (limited to 'src/libcharon/encoding')
-rw-r--r-- | src/libcharon/encoding/message.c | 494 | ||||
-rw-r--r-- | src/libcharon/encoding/message.h | 5 |
2 files changed, 366 insertions, 133 deletions
diff --git a/src/libcharon/encoding/message.c b/src/libcharon/encoding/message.c index 262f1050b..43f74238f 100644 --- a/src/libcharon/encoding/message.c +++ b/src/libcharon/encoding/message.c @@ -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)) - { - message->set_exchange_type(message, AGGRESSIVE); + if (this->major_version == IKEV1_MAJOR_VERSION) + { + /* 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,46 +2620,22 @@ 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_payload_t *payload; fragment_t *fragment; - bio_writer_t *writer; - host_t *src, *dst; - chunk_t data; - u_int8_t num; int i, insert_at = -1; - if (!this->frag) - { - return INVALID_STATE; - } - payload = (fragment_payload_t*)message->get_payload(message, PLV1_FRAGMENT); - if (!payload) - { - return INVALID_ARG; - } - 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); - } - - num = payload->get_number(payload); - if (!this->frag->last && payload->is_last(payload)) - { - this->frag->last = num; - } - 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 #%hhu", num); + DBG1(DBG_ENC, "received duplicate fragment #%hu", num); return NEED_MORE; } if (fragment->num > num) @@ -2548,12 +2644,11 @@ METHOD(message_t, add_fragment, status_t, break; } } - 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); + reset_defrag(this); return FAILED; } INIT(fragment, @@ -2561,35 +2656,93 @@ METHOD(message_t, add_fragment, status_t, .data = chunk_clone(data), ); array_insert(this->fragments, insert_at, fragment); + return SUCCESS; +} - if (this->frag->last < array_count(this->fragments)) - { - /* there are some fragments missing */ - DBG1(DBG_ENC, "received fragment #%hhu, waiting for complete IKE " - "message", num); - return NEED_MORE; - } +/** + * 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_t *fragment; + bio_writer_t *writer; + host_t *src, *dst; + chunk_t data; + int i; 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); + 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)); - 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); + + 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; + status_t status; + + if (!this->frag) + { + return INVALID_STATE; + } + payload = (fragment_payload_t*)message->get_payload(message, PLV1_FRAGMENT); + if (!payload) + { + return INVALID_ARG; + } + if (!this->fragments || this->message_id != payload->get_id(payload)) + { + 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; + } + status = add_fragment(this, num, data); + if (status != SUCCESS) + { + return status; + } + + if (array_count(this->fragments) != this->frag->last) + { + /* there are some fragments missing */ + DBG1(DBG_ENC, "received fragment #%hhu, waiting for complete IKE " + "message", num); + return NEED_MORE; + } + + DBG1(DBG_ENC, "received fragment #%hhu, reassembling fragmented IKE " + "message", num); + + 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), diff --git a/src/libcharon/encoding/message.h b/src/libcharon/encoding/message.h index 69a8e93b1..a03aa8e96 100644 --- a/src/libcharon/encoding/message.h +++ b/src/libcharon/encoding/message.h @@ -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 |