[Swan-dev] pluto: Add RFC7383 fragmentation support
Herbert Xu
herbert at gondor.apana.org.au
Thu Apr 30 15:34:57 EEST 2015
This patch adds RFC7383 IKEv2 fragmentation support to pluto.
Signed-off-by: Herbert Xu <herbert at gondor.apana.org.au>
diff --git a/include/ietf_constants.h b/include/ietf_constants.h
index e56683d..95fb27a 100644
--- a/include/ietf_constants.h
+++ b/include/ietf_constants.h
@@ -529,6 +529,11 @@ enum next_payload_types_ikev2 {
ISAKMP_NEXT_v2SK = 46, /* Encrypted payload */
ISAKMP_NEXT_v2CP = 47, /* Configuration Payload (MODECFG) */
ISAKMP_NEXT_v2EAP = 48, /* Extensible Authentication Payload */
+ ISAKMP_NEXT_v2GSPM = 49,
+ ISAKMP_NEXT_v2IDG = 50,
+ ISAKMP_NEXT_v2GSA = 51,
+ ISAKMP_NEXT_v2KD = 52,
+ ISAKMP_NEXT_v2SKF = 53, /* Encrypted fragment */
/* 128 - 255 Private Use */
/* Cisco/Microsoft proprietary IKE fragmentation - private use for libreswan */
ISAKMP_NEXT_v2IKE_FRAGMENTATION = 132,
@@ -1415,8 +1420,14 @@ typedef enum {
v2N_IKEV2_MESSAGE_ID_SYNC = 16422, /* RFC-6311 */
v2N_IPSEC_REPLAY_COUNTER_SYNC = 16423, /* RFC-6311 */
v2N_SECURE_PASSWORD_METHODS = 16424, /* RFC-6467 */
-
- /* 16425 - 40969 Unassigned */
+ v2N_PSK_PERSIST = 16425,
+ v2N_PSK_CONFIRM = 16426,
+ v2N_ERX_SUPPORTED = 16427,
+ v2N_IFOM_CAPABILITY = 16428,
+ v2N_SENDER_REQUEST_ID = 16429,
+ v2N_FRAGMENTATION_SUPPORTED = 16430, /* RFC-7383 */
+
+ /* 16431 - 40969 Unassigned */
/* 40960 - 65535 Private Use */
} v2_notification_t;
diff --git a/lib/libswan/constants.c b/lib/libswan/constants.c
index 33d2143..d8d4c2a 100644
--- a/lib/libswan/constants.c
+++ b/lib/libswan/constants.c
@@ -286,6 +286,11 @@ const char *const payload_name_ikev2_main[] = {
"ISAKMP_NEXT_v2SK",
"ISAKMP_NEXT_v2CP",
"ISAKMP_NEXT_v2EAP",
+ "ISAKMP_NEXT_v2GSPM",
+ "ISAKMP_NEXT_v2IDG",
+ "ISAKMP_NEXT_v2GSA",
+ "ISAKMP_NEXT_v2KD",
+ "ISAKMP_NEXT_v2SKF",
NULL /* termination for bitnamesof() */
};
@@ -306,7 +311,7 @@ static enum_names payload_names_ikev2_private_use = {
static enum_names payload_names_ikev2_main = {
ISAKMP_NEXT_v2SA,
- ISAKMP_NEXT_v2EAP,
+ ISAKMP_NEXT_v2SKF,
payload_name_ikev2_main,
&payload_names_ikev2_private_use
};
@@ -321,7 +326,7 @@ enum_names ikev2_payload_names = {
/* either V1 or V2 payload kind */
static enum_names payload_names_ikev2copy_main = {
ISAKMP_NEXT_v2SA,
- ISAKMP_NEXT_v2EAP,
+ ISAKMP_NEXT_v2SKF,
payload_name_ikev2_main,
&payload_names_ikev1_private_use
};
@@ -1603,12 +1608,18 @@ static const char *const ikev2_notify_name_16384[] = {
"v2N_IPSEC_REPLAY_COUNTER_SYNC_SUPPORTED",
"v2N_IKEV2_MESSAGE_ID_SYNC",
"v2N_IPSEC_REPLAY_COUNTER_SYNC",
- "v2N_SECURE_PASSWORD_METHODS", /* 16423 */
+ "v2N_SECURE_PASSWORD_METHODS",
+ "v2N_PSK_PERSIST",
+ "v2N_PSK_CONFIRM",
+ "v2N_ERX_SUPPORTED",
+ "v2N_IFOM_CAPABILITY",
+ "v2N_SENDER_REQUEST_ID",
+ "v2N_FRAGMENTATION_SUPPORTED", /* 16430 */
};
static enum_names ikev2_notify_names_16384 = {
v2N_INITIAL_CONTACT,
- v2N_SECURE_PASSWORD_METHODS,
+ v2N_FRAGMENTATION_SUPPORTED,
ikev2_notify_name_16384,
NULL
};
diff --git a/programs/pluto/ikev2.c b/programs/pluto/ikev2.c
index fcd094f..d1f4c68 100644
--- a/programs/pluto/ikev2.c
+++ b/programs/pluto/ikev2.c
@@ -566,6 +566,7 @@ struct ikev2_payloads_summary ikev2_decode_payloads(struct msg_digest *md,
switch (np) {
case ISAKMP_NEXT_v2SK:
+ case ISAKMP_NEXT_v2SKF:
/* RFC 5996 2.14 "Encrypted Payload":
*
* Next Payload - The payload type of the
@@ -635,6 +636,108 @@ void ikev2_log_payload_errors(struct ikev2_payload_errors errors)
}
}
+static bool ikev2_check_fragment(struct msg_digest *md)
+{
+ struct state *st = md->st;
+ struct ikev2_skf *skf = &md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf;
+ struct ikev2_frag *i;
+
+ if (!(st->st_connection->policy & POLICY_IKE_FRAG_ALLOW)) {
+ libreswan_log("discarding IKE encrypted fragment - fragmentation not allowed by local policy (ike_frag=no)");
+ return TRUE;
+ }
+
+ DBG(DBG_CONTROL,
+ DBG_log("received IKE encrypted fragment number '%u', total number '%u', next payload '%u'",
+ skf->isaskf_number, skf->isaskf_total, skf->isaskf_np));
+
+ if (!skf->isaskf_number || !skf->isaskf_total ||
+ skf->isaskf_number > skf->isaskf_total ||
+ (skf->isaskf_number == 1 ? !skf->isaskf_np : skf->isaskf_np)) {
+ libreswan_log("Ignoring invalid IKE encrypted fragment");
+ return TRUE;
+ }
+
+ for (i = st->ikev2_frags; i; i = i->next) {
+ if (i->index != skf->isaskf_number)
+ continue;
+
+ if (i->total == skf->isaskf_total) {
+ libreswan_log("Ignoring duplicate IKE encrypted fragment");
+ return TRUE;
+ }
+
+ if (i->total > skf->isaskf_total) {
+ libreswan_log("Ignoring odd IKE encrypted fragment");
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static bool ikev2_collect_fragment(struct msg_digest *md)
+{
+ struct state *st = md->st;
+ struct ikev2_skf *skf = &md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf;
+ pb_stream *e_pbs = &md->chain[ISAKMP_NEXT_v2SKF]->pbs;
+ struct ikev2_frag *frag, **i;
+ int num_frags;
+
+ if (ikev2_check_fragment(md))
+ return TRUE;
+
+ frag = alloc_thing(struct ikev2_frag, "ikev2_frag");
+ frag->np = skf->isaskf_np;
+ frag->index = skf->isaskf_number;
+ frag->total = skf->isaskf_total;
+ frag->iv = e_pbs->cur - md->packet_pbs.start;
+ clonetochunk(frag->cipher, md->packet_pbs.start,
+ e_pbs->roof - md->packet_pbs.start,
+ "IKEv2 encrypted fragment");
+
+ /* Add the fragment to the state */
+ i = &st->ikev2_frags;
+ if (*i && (*i)->total < skf->isaskf_total)
+ do {
+ struct ikev2_frag *old = *i;
+
+ (*i) = old->next;
+ freeanychunk(old->cipher);
+ pfree(old);
+ } while (*i);
+
+ num_frags = 0;
+ for (;;) {
+ if (frag) {
+ /* Still looking for a place to insert frag */
+ if (!*i || (*i)->index > frag->index) {
+ frag->next = *i;
+ *i = frag;
+ frag = NULL;
+ }
+ }
+
+ if (!*i)
+ break;
+
+ num_frags++;
+
+ i = &(*i)->next;
+ }
+
+ if (num_frags < skf->isaskf_total)
+ return TRUE;
+
+ /* optimize: if receiving fragments, immediately respond with fragments too */
+ st->st_seen_fragments = TRUE;
+
+ DBG(DBG_CONTROL,
+ DBG_log(" updated IKE fragment state to respond using fragments without waiting for re-transmits"));
+
+ return FALSE;
+}
+
/*
* process an input packet, possibly generating a reply.
*
@@ -822,8 +925,6 @@ void process_v2_packet(struct msg_digest **mdp)
struct ikev2_payloads_summary clear_payload_summary = { .status = STF_IGNORE };
struct ikev2_payload_errors clear_payload_status = { .status = STF_OK };
- struct ikev2_payloads_summary enc_payload_summary = { .status = STF_IGNORE };
- struct ikev2_payload_errors enc_payload_status = { .status = STF_OK };
for (svm = v2_state_microcode_table; svm->state != STATE_IKEv2_ROOF;
svm++) {
@@ -858,6 +959,17 @@ void process_v2_packet(struct msg_digest **mdp)
complete_v2_state_transition(mdp, clear_payload_summary.status);
return;
}
+
+ if (clear_payload_summary.seen &
+ LELEM(PINDEX(ISAKMP_NEXT_v2SKF)))
+ clear_payload_summary.seen ^=
+ LELEM(PINDEX(ISAKMP_NEXT_v2SK)) |
+ LELEM(PINDEX(ISAKMP_NEXT_v2SKF));
+ if (clear_payload_summary.repeated &
+ LELEM(PINDEX(ISAKMP_NEXT_v2SKF)))
+ clear_payload_summary.repeated ^=
+ LELEM(PINDEX(ISAKMP_NEXT_v2SK)) |
+ LELEM(PINDEX(ISAKMP_NEXT_v2SKF));
}
struct ikev2_payload_errors clear_payload_errors
= ikev2_verify_payloads(clear_payload_summary, svm, FALSE);
@@ -866,40 +978,6 @@ void process_v2_packet(struct msg_digest **mdp)
clear_payload_status = clear_payload_errors;
continue;
}
- /*
- * Continue checking by unpacking the SK payload but
- * only its ok and there's one to unpack. Otherwise
- * assume its a correct match.
- */
- if (!(svm->flags & SMF2_UNPACK_SK)) {
- break;
- }
- if (!(svm->req_clear_payloads & LELEM(PINDEX(ISAKMP_NEXT_v2SK)))) {
- break;
- }
- if (enc_payload_summary.status != STF_OK) {
- DBG(DBG_CONTROL,
- DBG_log("Unpacking encrypted payload for svm: %s",
- svm->story));
- stf_status status = ikev2_verify_and_decrypt_sk_payload(md);
- if (status != STF_OK) {
- complete_v2_state_transition(mdp, status);
- return;
- }
- unsigned np = md->chain[ISAKMP_NEXT_v2SK]->payload.generic.isag_np;
- enc_payload_summary = ikev2_decode_payloads(md, &md->clr_pbs, np);
- if (enc_payload_summary.status != STF_OK) {
- complete_v2_state_transition(mdp, enc_payload_summary.status);
- return;
- }
- }
- struct ikev2_payload_errors enc_payload_errors
- = ikev2_verify_payloads(enc_payload_summary, svm, TRUE);
- if (enc_payload_errors.status != STF_OK) {
- /* Save this failure for later logging. */
- enc_payload_status = enc_payload_errors;
- continue;
- }
/* must be the right state machine entry */
break;
}
@@ -917,9 +995,6 @@ void process_v2_packet(struct msg_digest **mdp)
if (clear_payload_status.status != STF_OK) {
ikev2_log_payload_errors(clear_payload_status);
complete_v2_state_transition(mdp, clear_payload_status.status);
- } else if (enc_payload_status.status != STF_OK) {
- ikev2_log_payload_errors(enc_payload_status);
- complete_v2_state_transition(mdp, enc_payload_status.status);
} else if (!(md->hdr.isa_flags & ISAKMP_FLAGS_v2_MSG_R)) {
/*
* We are the responder to this message so
@@ -936,6 +1011,9 @@ void process_v2_packet(struct msg_digest **mdp)
if (state_busy(st))
return;
+ if (md->chain[ISAKMP_NEXT_v2SKF] && ikev2_collect_fragment(md))
+ return;
+
DBG(DBG_PARSING, {
if (pbs_left(&md->message_pbs) != 0)
DBG_log("removing %d bytes of padding",
@@ -1368,9 +1446,6 @@ static void success_v2_state_transition(struct msg_digest *md)
/* if requested, send the new reply packet */
if (svm->flags & SMF2_REPLY) {
- /* free previously transmitted packet */
- freeanychunk(st->st_tpacket);
-
if (nat_traversal_enabled && from_state != STATE_PARENT_I1) {
/* adjust our destination port if necessary */
nat_traversal_change_port_lookup(md, st);
@@ -1384,11 +1459,6 @@ static void success_v2_state_transition(struct msg_digest *md)
st->st_interface->port);
});
- close_output_pbs(&reply_stream); /* good form, but actually a no-op */
-
- clonetochunk(st->st_tpacket, reply_stream.start,
- pbs_offset(&reply_stream), "reply packet");
-
/* actually send the packet
* Note: this is a great place to implement "impairments"
* for testing purposes. Suppress or duplicate the
diff --git a/programs/pluto/ikev2.h b/programs/pluto/ikev2.h
index 0b9957e..79eff7a 100644
--- a/programs/pluto/ikev2.h
+++ b/programs/pluto/ikev2.h
@@ -198,8 +198,6 @@ void send_v2_notification_invalid_ke_from_state(struct state *st);
bool modp_in_propset(oakley_group_t received, struct alg_info_ike *ai_list);
oakley_group_t first_modp_from_propset(struct alg_info_ike *ai_list);
-stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md);
-
struct ikev2_payloads_summary {
stf_status status;
lset_t seen;
diff --git a/programs/pluto/ikev2_parent.c b/programs/pluto/ikev2_parent.c
index 95803a3..a5c6e7c 100644
--- a/programs/pluto/ikev2_parent.c
+++ b/programs/pluto/ikev2_parent.c
@@ -495,6 +495,17 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
close_output_pbs(&pb);
}
+ /* Send fragmentation support notification */
+ if (c->policy & POLICY_IKE_FRAG_ALLOW) {
+ int np = ISAKMP_NEXT_v2N;
+
+ if (!ship_v2N(np, ISAKMP_PAYLOAD_NONCRITICAL,
+ PROTO_v2_RESERVED, &empty_chunk,
+ v2N_FRAGMENTATION_SUPPORTED, &empty_chunk,
+ &md->rbody))
+ return STF_INTERNAL_ERROR;
+ }
+
/* Send NAT-T Notify payloads */
{
int np = c->send_vendorid ? ISAKMP_NEXT_v2V : ISAKMP_NEXT_v2NONE;
@@ -525,6 +536,7 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
clonetochunk(st->st_tpacket, reply_stream.start,
pbs_offset(&reply_stream),
"reply packet for ikev2_parent_outI1_tail");
+ release_v2fragments(&st->st_tfrags);
/* save packet for later signing */
freeanychunk(st->st_firstpacket_me);
@@ -538,6 +550,26 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
return STF_OK;
}
+static void ikev2_check_frag_support(struct msg_digest *md)
+{
+ struct payload_digest *p;
+ struct state *st = md->st;
+
+ passert(st);
+
+ for (p = md->chain[ISAKMP_NEXT_v2N]; p != NULL; p = p->next) {
+ switch (p->payload.v2n.isan_type) {
+ case v2N_FRAGMENTATION_SUPPORTED:
+ st->st_seen_fragvid = TRUE;
+ break;
+ default:
+ continue;
+ }
+
+ break;
+ }
+}
+
/*
*
***************************************************************
@@ -788,8 +820,10 @@ stf_status ikev2parent_inI1outR1(struct msg_digest *md)
* check v2N_NAT_DETECTION_DESTINATION_IP or/and
* v2N_NAT_DETECTION_SOURCE_IP
*/
- if (md->chain[ISAKMP_NEXT_v2N] != NULL)
+ if (md->chain[ISAKMP_NEXT_v2N] != NULL) {
ikev2_natd_lookup(md, zero_cookie);
+ ikev2_check_frag_support(md);
+ }
/* calculate the nonce and the KE */
{
@@ -1008,6 +1042,17 @@ static stf_status ikev2_parent_inI1outR1_tail(
!has_preloaded_public_key(st);
}
+ /* Send fragmentation support notification */
+ if (c->policy & POLICY_IKE_FRAG_ALLOW) {
+ int np = ISAKMP_NEXT_v2N;
+
+ if (!ship_v2N(np, ISAKMP_PAYLOAD_NONCRITICAL,
+ PROTO_v2_RESERVED, &empty_chunk,
+ v2N_FRAGMENTATION_SUPPORTED, &empty_chunk,
+ &md->rbody))
+ return STF_INTERNAL_ERROR;
+ }
+
/* Send NAT-T Notify payloads */
{
struct ikev2_generic in;
@@ -1046,6 +1091,7 @@ static stf_status ikev2_parent_inI1outR1_tail(
clonetochunk(st->st_tpacket, reply_stream.start,
pbs_offset(&reply_stream),
"reply packet for ikev2_parent_inI1outR1_tail");
+ release_v2fragments(&st->st_tfrags);
/* save packet for later signing */
freeanychunk(st->st_firstpacket_me);
@@ -1208,6 +1254,7 @@ stf_status ikev2parent_inR1outI2(struct msg_digest *md)
case v2N_USE_TRANSPORT_MODE:
case v2N_NAT_DETECTION_SOURCE_IP:
case v2N_NAT_DETECTION_DESTINATION_IP:
+ case v2N_FRAGMENTATION_SUPPORTED:
/* we do handle these further down */
break;
default:
@@ -1255,8 +1302,10 @@ stf_status ikev2parent_inR1outI2(struct msg_digest *md)
/* check v2N_NAT_DETECTION_DESTINATION_IP or/and
* v2N_NAT_DETECTION_SOURCE_IP
*/
- if (md->chain[ISAKMP_NEXT_v2N] != NULL)
+ if (md->chain[ISAKMP_NEXT_v2N] != NULL) {
ikev2_natd_lookup(md, st->st_rcookie);
+ ikev2_check_frag_support(md);
+ }
/* initiate calculation of g^xy */
return start_dh_v2(md, "ikev2_inR1outI2 KE", ORIGINAL_INITIATOR,
@@ -1540,15 +1589,15 @@ static stf_status ikev2_encrypt_msg(struct state *st,
* the actual starting-variable (a.k.a. IV).
*/
-stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
+static stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md,
+ struct chunk *chunk,
+ unsigned int iv)
{
/* caller should be passing in the original (parent) state. */
struct state *st = md->st;
struct state *pst = IS_CHILD_SA(st) ?
state_with_serialno(st->st_clonedfrom) : st;
- pb_stream *e_pbs = &md->chain[ISAKMP_NEXT_v2SK]->pbs;
-
if (st != NULL && !st->hidden_variables.st_skeyid_calculated)
{
DBG(DBG_CRYPT | DBG_CONTROL, {
@@ -1563,7 +1612,7 @@ stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
return STF_FAIL;
}
- u_char *wire_iv_start = e_pbs->cur;
+ u_char *wire_iv_start = chunk->ptr + iv;
size_t wire_iv_size = pst->st_oakley.encrypter->wire_iv_size;
size_t integ_size = (ike_alg_enc_requires_integ(pst->st_oakley.encrypter)
? pst->st_oakley.integ_hasher->hash_integ_len
@@ -1576,14 +1625,14 @@ stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
* - at least one padding-length byte
* - truncated integrity digest / tag
*/
- u_char *payload_end = e_pbs->roof;
+ u_char *payload_end = chunk->ptr + chunk->len;
if (payload_end < (wire_iv_start + wire_iv_size + 1 + integ_size)) {
libreswan_log("encrypted payload impossibly short (%zu)",
payload_end - wire_iv_start);
return STF_FAIL;
}
- u_char *auth_start = md->packet_pbs.start;
+ u_char *auth_start = chunk->ptr;
u_char *enc_start = wire_iv_start + wire_iv_size;
u_char *integ_start = payload_end - integ_size;
size_t enc_size = integ_start - enc_start;
@@ -1705,7 +1754,57 @@ stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
/* don't bother to check any other pad octets */
DBG(DBG_CRYPT, DBG_log("striping %u bytes as pad", padlen));
- init_pbs(&md->clr_pbs, enc_start, enc_size - padlen, "cleartext");
+ setchunk(*chunk, enc_start, enc_size - padlen);
+ return STF_OK;
+}
+
+static stf_status ikev2_reassemble_fragments(struct msg_digest *md,
+ struct chunk *chunk)
+{
+ struct state *st = md->st;
+ struct ikev2_frag *frag;
+ stf_status status;
+ unsigned int size;
+ unsigned int offset;
+
+ size = 0;
+ for (frag = st->ikev2_frags; frag; frag = frag->next) {
+ setchunk(frag->plain, frag->cipher.ptr, frag->cipher.len);
+
+ status = ikev2_verify_and_decrypt_sk_payload(
+ md, &frag->plain, frag->iv);
+ if (status != STF_OK) {
+ release_v2fragments(&st->ikev2_frags);
+ return status;
+ }
+
+ size += frag->plain.len;
+ }
+
+ /* We have all the fragments */
+ md->raw_packet.ptr = alloc_bytes(size, "IKE fragments buffer");
+
+ /* Reassemble fragments in buffer */
+ frag = st->ikev2_frags;
+ md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf.isaskf_np = frag->np;
+ offset = 0;
+ do {
+ struct ikev2_frag *old = frag;
+
+ passert(offset + frag->plain.len <= size);
+ memcpy(md->raw_packet.ptr + offset, frag->plain.ptr,
+ frag->plain.len);
+ offset += frag->plain.len;
+ frag = frag->next;
+
+ freeanychunk(old->cipher);
+ pfree(old);
+ } while (frag);
+
+ st->ikev2_frags = NULL;
+
+ setchunk(*chunk, md->raw_packet.ptr, size);
+
return STF_OK;
}
@@ -1713,11 +1812,29 @@ static
stf_status ikev2_decrypt_msg(struct msg_digest *md)
{
stf_status status;
- status = ikev2_verify_and_decrypt_sk_payload(md);
+ struct chunk chunk;
+
+ if (md->chain[ISAKMP_NEXT_v2SKF]) {
+ status = ikev2_reassemble_fragments(md, &chunk);
+ } else {
+ pb_stream *e_pbs = &md->chain[ISAKMP_NEXT_v2SK]->pbs;
+
+ setchunk(chunk, md->packet_pbs.start,
+ e_pbs->roof - md->packet_pbs.start);
+
+ status = ikev2_verify_and_decrypt_sk_payload(
+ md, &chunk, e_pbs->cur - md->packet_pbs.start);
+ }
+
if (status != STF_OK) {
return status;
}
- unsigned np = md->chain[ISAKMP_NEXT_v2SK]->payload.generic.isag_np;
+
+ init_pbs(&md->clr_pbs, chunk.ptr, chunk.len, "cleartext");
+
+ unsigned np = md->chain[ISAKMP_NEXT_v2SK] ?
+ md->chain[ISAKMP_NEXT_v2SK]->payload.generic.isag_np :
+ md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf.isaskf_np;
struct ikev2_payloads_summary summary = ikev2_decode_payloads(md, &md->clr_pbs, np);
if (summary.status != STF_OK) {
return status;
@@ -1849,6 +1966,158 @@ static stf_status ikev2_send_auth(struct connection *c,
return STF_OK;
}
+static stf_status ikev2_send_fragment(struct msg_digest *md,
+ struct isakmp_hdr *hdr,
+ struct ikev2_generic *oe,
+ struct ikev2_frag **fragp,
+ struct chunk *payload,
+ unsigned int count, unsigned int total,
+ const char *desc)
+{
+ struct state *st = md->st;
+ struct ikev2_skf e;
+ unsigned char *encstart;
+ pb_stream e_pbs, e_pbs_cipher;
+ unsigned char *iv;
+ unsigned char *authstart;
+
+ /* make sure HDR is at start of a clean buffer */
+ zero(&reply_buffer);
+ init_pbs(&reply_stream, reply_buffer, sizeof(reply_buffer),
+ "reply packet");
+
+ /* beginning of data going out */
+ authstart = reply_stream.cur;
+
+ /* HDR out */
+ {
+ hdr->isa_np = ISAKMP_NEXT_v2SKF;
+
+ if (!out_struct(hdr, &isakmp_hdr_desc, &reply_stream,
+ &md->rbody))
+ return STF_INTERNAL_ERROR;
+ }
+
+ /* insert an Encryption payload header */
+ e.isaskf_np = count == 1 ? oe->isag_np : 0;
+ e.isaskf_critical = oe->isag_critical;
+ e.isaskf_number = count;
+ e.isaskf_total = total;
+
+ if (!out_struct(&e, &ikev2_skf_desc, &md->rbody, &e_pbs))
+ return STF_INTERNAL_ERROR;
+
+ /* insert IV */
+ iv = e_pbs.cur;
+ if (!emit_wire_iv(st, &e_pbs))
+ return STF_INTERNAL_ERROR;
+
+ /* note where cleartext starts */
+ init_pbs(&e_pbs_cipher, e_pbs.cur, e_pbs.roof - e_pbs.cur,
+ "cleartext");
+ e_pbs_cipher.container = &e_pbs;
+ e_pbs_cipher.desc = NULL;
+ e_pbs_cipher.cur = e_pbs.cur;
+ encstart = e_pbs_cipher.cur;
+
+ if (!out_raw(payload->ptr, payload->len, &e_pbs_cipher,
+ "cleartext fragment"))
+ return STF_INTERNAL_ERROR;
+
+ /*
+ * need to extend the packet so that we will know how big it is
+ * since the length is under the integrity check
+ */
+ if (!ikev2_padup_pre_encrypt(st, &e_pbs_cipher))
+ return STF_INTERNAL_ERROR;
+
+ close_output_pbs(&e_pbs_cipher);
+
+ {
+ unsigned char *authloc = ikev2_authloc(st, &e_pbs);
+ int ret;
+
+ if (authloc == NULL)
+ return STF_INTERNAL_ERROR;
+
+ close_output_pbs(&e_pbs);
+ close_output_pbs(&md->rbody);
+ close_output_pbs(&reply_stream);
+
+ ret = ikev2_encrypt_msg(st, authstart,
+ iv, encstart, authloc,
+ &e_pbs, &e_pbs_cipher);
+ if (ret != STF_OK)
+ return ret;
+ }
+
+ *fragp = alloc_thing(struct ikev2_frag, "ikev2_frag");
+ clonetochunk((*fragp)->cipher, reply_stream.start,
+ pbs_offset(&reply_stream), desc);
+
+ return STF_OK;
+}
+
+static stf_status ikev2_send_fragments(struct msg_digest *md,
+ struct isakmp_hdr *hdr,
+ struct ikev2_generic *e,
+ struct chunk *payload,
+ const char *desc)
+{
+ struct state *st = md->st;
+ unsigned int len;
+ unsigned int offset;
+ unsigned int total;
+ unsigned int count;
+ struct ikev2_frag **fragp;
+ int ret;
+
+ len = (st->st_connection->addr_family == AF_INET) ?
+ ISAKMP_FRAG_MAXLEN_IPv4 : ISAKMP_FRAG_MAXLEN_IPv6;
+
+ if (st->st_interface && st->st_interface->ike_float)
+ len -= NON_ESP_MARKER_SIZE;
+
+ len -= NSIZEOF_isakmp_hdr + NSIZEOF_isakmp_ikefrag;
+ len -= ike_alg_enc_requires_integ(st->st_oakley.encrypter) ?
+ st->st_oakley.integ_hasher->hash_integ_len :
+ st->st_oakley.encrypter->aead_tag_size;
+
+ if (st->st_oakley.encrypter->pad_to_blocksize)
+ len &= ~(st->st_oakley.encrypter->enc_blocksize - 1);
+
+ len -= 2;
+
+ total = (payload->len - 1) / len + 1;
+ if (total >= 128) {
+ loglog(RC_LOG_SERIOUS, "Too many frags %u %u",
+ (unsigned int)payload->len, len);
+ return STF_INTERNAL_ERROR;
+ }
+
+ count = 1;
+ offset = 0;
+ fragp = &st->st_tfrags;
+
+ for (;;) {
+ struct chunk cipher;
+
+ if (offset + len > payload->len)
+ len = payload->len - offset;
+ setchunk(cipher, payload->ptr + offset, len);
+ offset += len;
+ ret = ikev2_send_fragment(md, hdr, e, fragp, &cipher,
+ count++, total, desc);
+
+ if (ret != STF_OK || offset >= payload->len)
+ break;
+
+ fragp = &(*fragp)->next;
+ }
+
+ return ret;
+}
+
static stf_status ikev2_parent_inR1outI2_tail(
struct pluto_crypto_req_cont *dh,
struct pluto_crypto_req *r)
@@ -1865,6 +2134,9 @@ static stf_status ikev2_parent_inR1outI2_tail(
unsigned char *authstart;
struct state *pst = st;
bool send_cert = FALSE;
+ struct isakmp_hdr hdr = md->hdr;
+ unsigned char *authloc;
+ unsigned int len;
finish_dh_v2(st, r);
@@ -1906,8 +2178,6 @@ static stf_status ikev2_parent_inR1outI2_tail(
/* HDR out */
{
- struct isakmp_hdr hdr = md->hdr;
-
/* clear all flags, set original initiator */
hdr.isa_flags = ISAKMP_FLAGS_v2_IKE_I;
hdr.isa_version = build_ikev2_version();
@@ -2073,6 +2343,8 @@ static stf_status ikev2_parent_inR1outI2_tail(
}
}
+ len = pbs_offset(&e_pbs_cipher);
+
/*
* need to extend the packet so that we will know how big it is
* since the length is under the integrity check
@@ -2082,15 +2354,29 @@ static stf_status ikev2_parent_inR1outI2_tail(
close_output_pbs(&e_pbs_cipher);
- {
- unsigned char *authloc = ikev2_authloc(st, &e_pbs);
+ authloc = ikev2_authloc(st, &e_pbs);
- if (authloc == NULL)
- return STF_INTERNAL_ERROR;
+ if (authloc == NULL)
+ return STF_INTERNAL_ERROR;
- close_output_pbs(&e_pbs);
- close_output_pbs(&md->rbody);
- close_output_pbs(&reply_stream);
+ close_output_pbs(&e_pbs);
+ close_output_pbs(&md->rbody);
+ close_output_pbs(&reply_stream);
+
+ if (should_fragment_ike_msg(st, pbs_offset(&reply_stream), TRUE)) {
+ struct chunk payload;
+
+ clonetochunk(payload, e_pbs_cipher.start, len,
+ "cleartext for ikev2_parent_outR1_I2");
+
+ ret = ikev2_send_fragments(md, &hdr, &e, &payload,
+ "reply fragment for ikev2_parent_outR1_I2");
+
+ freeanychunk(payload);
+ return ret;
+ }
+
+ {
ret = ikev2_encrypt_msg(st, authstart,
iv, encstart, authloc,
@@ -2106,6 +2392,7 @@ static stf_status ikev2_parent_inR1outI2_tail(
clonetochunk(pst->st_tpacket, reply_stream.start,
pbs_offset(&reply_stream),
"reply packet for ikev2_parent_outI1");
+ release_v2fragments(&pst->st_tfrags);
return STF_OK;
}
@@ -2588,10 +2875,13 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
{
unsigned char *encstart;
unsigned char *iv;
+ unsigned char *authloc;
struct ikev2_generic e;
pb_stream e_pbs, e_pbs_cipher;
stf_status ret;
bool send_cert = FALSE;
+ unsigned int len;
+ struct isakmp_hdr hdr;
/* make sure HDR is at start of a clean buffer */
zero(&reply_buffer);
@@ -2600,7 +2890,7 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
/* HDR out */
{
- struct isakmp_hdr hdr = md->hdr; /* grab cookies */
+ hdr = md->hdr; /* grab cookies */
/* set msg responder flag - clear others */
hdr.isa_flags = ISAKMP_FLAGS_v2_MSG_R;
@@ -2749,22 +3039,37 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
}
}
+ len = pbs_offset(&e_pbs_cipher);
+
if (!ikev2_padup_pre_encrypt(st, &e_pbs_cipher))
return STF_INTERNAL_ERROR;
close_output_pbs(&e_pbs_cipher);
- {
- unsigned char *authloc = ikev2_authloc(st, &e_pbs);
+ authloc = ikev2_authloc(st, &e_pbs);
- if (authloc == NULL)
- return STF_INTERNAL_ERROR;
+ if (authloc == NULL)
+ return STF_INTERNAL_ERROR;
- close_output_pbs(&e_pbs);
+ close_output_pbs(&e_pbs);
+ close_output_pbs(&md->rbody);
+ close_output_pbs(&reply_stream);
- close_output_pbs(&md->rbody);
- close_output_pbs(&reply_stream);
+ if (should_fragment_ike_msg(st, pbs_offset(&reply_stream),
+ TRUE)) {
+ struct chunk payload;
+
+ clonetochunk(payload, e_pbs_cipher.start, len,
+ "cleartext for ikev2_parent_inI2outR2_tail");
+ ret = ikev2_send_fragments(md, &hdr, &e, &payload,
+ "reply fragment for ikev2_parent_inI2outR2_tail");
+
+ freeanychunk(payload);
+ return ret;
+ }
+
+ {
ret = ikev2_encrypt_msg(st, authstart,
iv, encstart, authloc,
&e_pbs, &e_pbs_cipher);
@@ -2778,6 +3083,7 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
clonetochunk(st->st_tpacket, reply_stream.start,
pbs_offset(&reply_stream),
"reply packet for ikev2_parent_inI2outR2_tail");
+ release_v2fragments(&st->st_tfrags);
/* note: retransmission is driven by initiator */
@@ -3311,6 +3617,7 @@ void send_v2_notification(struct state *p1st,
clonetochunk(p1st->st_tpacket, reply_stream.start, pbs_offset(&reply_stream),
"notification packet");
+ release_v2fragments(&p1st->st_tfrags);
send_ike_msg(p1st, __FUNCTION__);
}
@@ -3599,6 +3906,7 @@ static stf_status ikev2_child_inIoutR_tail(struct pluto_crypto_req_cont *qke,
freeanychunk(pst->st_tpacket);
clonetochunk(pst->st_tpacket, reply_stream.start, pbs_offset(&reply_stream), "reply packet for CREATE_CHILD_SA exchange");
+ release_v2fragments(&pst->st_tfrags);
send_ike_msg(pst, __FUNCTION__);
@@ -3942,6 +4250,7 @@ stf_status process_encrypted_informational_ikev2(struct msg_digest *md)
clonetochunk(st->st_tpacket, reply_stream.start,
pbs_offset(&reply_stream),
"reply packet for informational exchange");
+ release_v2fragments(&st->st_tfrags);
send_ike_msg(st, __FUNCTION__);
}
@@ -4189,6 +4498,7 @@ stf_status ikev2_send_informational(struct state *st)
clonetochunk(pst->st_tpacket, reply_stream.start,
pbs_offset(&reply_stream),
"reply packet for informational exchange");
+ release_v2fragments(&st->st_tfrags);
pst->st_pend_liveness = TRUE; /* we should only do this when dpd/liveness is active? */
send_ike_msg(pst, __FUNCTION__);
}
@@ -4345,6 +4655,7 @@ static bool ikev2_delete_out_guts(struct state *const st, struct state *const ps
clonetochunk(pst->st_tpacket, reply_stream.start,
pbs_offset(&reply_stream),
"request packet for informational exchange");
+ release_v2fragments(&pst->st_tfrags);
send_ike_msg(pst, __FUNCTION__);
diff --git a/programs/pluto/packet.c b/programs/pluto/packet.c
index 896f14f..b6d4a2d 100644
--- a/programs/pluto/packet.c
+++ b/programs/pluto/packet.c
@@ -1194,6 +1194,41 @@ struct_desc ikev2_sk_desc = { "IKEv2 Encryption Payload",
ikev2generic_fields,
sizeof(struct ikev2_generic) };
+/*
+ * 2.5. Fragmenting Message
+ *
+ * 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Next Payload |C| RESERVED | Payload Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Fragment Number | Total Fragments |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Initialization Vector |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * ~ Encrypted content ~
+ * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | | Padding (0-255 octets) |
+ * +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
+ * | | Pad Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * ~ Integrity Checksum Data ~
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Encrypted Fragment Payload
+ */
+static field_desc ikev2skf_fields[] = {
+ { ft_enum, 8 / BITS_PER_BYTE, "next payload type", &ikev2_payload_names },
+ { ft_set, 8 / BITS_PER_BYTE, "flags", critical_names },
+ { ft_len, 16 / BITS_PER_BYTE, "length", NULL },
+ { ft_nat, 16 / BITS_PER_BYTE, "fragment number", NULL },
+ { ft_nat, 16 / BITS_PER_BYTE, "total fragments", NULL },
+ { ft_end, 0, NULL, NULL }
+};
+
+struct_desc ikev2_skf_desc = { "IKEv2 Encrypted Fragment",
+ ikev2skf_fields, sizeof(struct ikev2_skf) };
+
/* descriptor for each V1 payload type
*
* There is a slight problem in that some payloads differ, depending
@@ -1242,7 +1277,13 @@ static struct_desc *const v2_payload_descs[] = {
&ikev2_ts_desc, /* 44 ISAKMP_NEXT_v2TSi */
&ikev2_ts_desc, /* 45 ISAKMP_NEXT_v2TSr */
&ikev2_sk_desc, /* 46 ISAKMP_NEXT_v2SK */
- &ikev2_cp_desc, /* 57 ISAKMP_NEXT_v2CP */
+ &ikev2_cp_desc, /* 47 ISAKMP_NEXT_v2CP */
+ NULL, /* 48 */
+ NULL, /* 49 */
+ NULL, /* 50 */
+ NULL, /* 51 */
+ NULL, /* 52 */
+ &ikev2_skf_desc, /* 53 ISAKMP_NEXT_v2SKF */
};
static field_desc suggested_group_fields[] = {
diff --git a/programs/pluto/packet.h b/programs/pluto/packet.h
index 15af444..1e0e6ce 100644
--- a/programs/pluto/packet.h
+++ b/programs/pluto/packet.h
@@ -902,6 +902,19 @@ struct ikev2_cp_attribute {
extern struct_desc ikev2_cp_attribute_desc;
+/*
+ * Fragment Message. RFC 7383 section 2.5
+ */
+struct ikev2_skf {
+ u_int8_t isaskf_np;
+ u_int8_t isaskf_critical;
+ u_int16_t isaskf_length;
+ u_int16_t isaskf_number;
+ u_int16_t isaskf_total;
+};
+
+extern struct_desc ikev2_skf_desc;
+
/* union of all payloads */
union payload {
@@ -931,6 +944,7 @@ union payload {
struct ikev2_delete v2delete;
struct ikev2_cp v2cp;
struct ikev2_cp_attribute v2cp_attribute;
+ struct ikev2_skf v2skf;
};
struct suggested_group {
diff --git a/programs/pluto/server.c b/programs/pluto/server.c
index ed287c2..7e9693c 100644
--- a/programs/pluto/server.c
+++ b/programs/pluto/server.c
@@ -1215,6 +1215,32 @@ static bool send_frags(struct state *st, const char *where)
return TRUE;
}
+bool should_fragment_ike_msg(struct state *st, size_t len, bool resending)
+{
+ if (st->st_interface && st->st_interface->ike_float)
+ len += NON_ESP_MARKER_SIZE;
+
+ return len >= (st->st_connection->addr_family == AF_INET ?
+ ISAKMP_FRAG_MAXLEN_IPv4 : ISAKMP_FRAG_MAXLEN_IPv6) &&
+ (resending ?
+ (st->st_connection->policy & POLICY_IKE_FRAG_ALLOW) &&
+ st->st_seen_fragvid :
+ (st->st_connection->policy & POLICY_IKE_FRAG_FORCE) ||
+ st->st_seen_fragments);
+}
+
+static bool send_ikev2_frags(struct state *st, const char *where)
+{
+ struct ikev2_frag *frag;
+
+ for (frag = st->st_tfrags; frag; frag = frag->next)
+ if (!send_packet(st, where, FALSE,
+ frag->cipher.ptr, frag->cipher.len, NULL, 0))
+ return FALSE;
+
+ return TRUE;
+}
+
static bool send_or_resend_ike_msg(struct state *st, const char *where,
bool resending)
{
@@ -1234,15 +1260,10 @@ static bool send_or_resend_ike_msg(struct state *st, const char *where,
/* decide of whether we're to fragment - IKEv1 only, draft-smyslov-ipsecme-ikev2-fragmentation not implemented yet */
if (!st->st_ikev2 &&
st->st_state != STATE_MAIN_I1 &&
- len + natt_bonus >=
- (st->st_connection->addr_family == AF_INET ?
- ISAKMP_FRAG_MAXLEN_IPv4 : ISAKMP_FRAG_MAXLEN_IPv6) &&
- ((resending &&
- (st->st_connection->policy & POLICY_IKE_FRAG_ALLOW) &&
- st->st_seen_fragvid) ||
- ((st->st_connection->policy & POLICY_IKE_FRAG_FORCE) ||
- st->st_seen_fragments))) {
+ should_fragment_ike_msg(st, len + natt_bonus, resending)) {
return send_frags(st, where);
+ } else if (st->st_ikev2 && st->st_tfrags) {
+ return send_ikev2_frags(st, where);
} else {
return send_packet(st, where, FALSE, st->st_tpacket.ptr,
st->st_tpacket.len, NULL, 0);
diff --git a/programs/pluto/server.h b/programs/pluto/server.h
index ddec566..09ac047 100644
--- a/programs/pluto/server.h
+++ b/programs/pluto/server.h
@@ -85,5 +85,7 @@ extern struct event *pluto_event_new(evutil_socket_t ft, short events,
bool ev_before(struct pluto_event *pev, deltatime_t delay);
extern void set_pluto_busy(bool busy);
extern void set_whack_pluto_ddos(enum ddos_mode mode);
+extern bool should_fragment_ike_msg(struct state *st, size_t len,
+ bool resending);
#endif /* _SERVER_H */
diff --git a/programs/pluto/state.c b/programs/pluto/state.c
index a720845..d77907c 100644
--- a/programs/pluto/state.c
+++ b/programs/pluto/state.c
@@ -454,10 +454,31 @@ void release_whack(struct state *st)
close_any(st->st_whack_sock);
}
+void release_v2fragments(struct ikev2_frag **fragp)
+{
+ struct ikev2_frag *frag = *fragp;
+
+ while (frag != NULL) {
+ struct ikev2_frag *this = frag;
+
+ frag = this->next;
+ freeanychunk(this->cipher);
+
+ pfree(this);
+ }
+
+ *fragp = NULL;
+}
+
void release_fragments(struct state *st)
{
struct ike_frag *frag = st->ike_frags;
+ if (st->st_ikev2) {
+ release_v2fragments(&st->ikev2_frags);
+ return;
+ }
+
while (frag != NULL) {
struct ike_frag *this = frag;
@@ -664,6 +685,7 @@ void delete_state(struct state *st)
freeanychunk(st->st_firstpacket_me);
freeanychunk(st->st_firstpacket_him);
freeanychunk(st->st_tpacket);
+ release_v2fragments(&st->st_tfrags);
freeanychunk(st->st_rpacket);
freeanychunk(st->st_p1isa);
freeanychunk(st->st_gi);
diff --git a/programs/pluto/state.h b/programs/pluto/state.h
index 64a7955..9cb3c97 100644
--- a/programs/pluto/state.h
+++ b/programs/pluto/state.h
@@ -149,6 +149,16 @@ struct ike_frag {
size_t size;
};
+struct ikev2_frag {
+ struct ikev2_frag *next;
+ int np;
+ int index;
+ int total;
+ unsigned int iv;
+ struct chunk cipher;
+ struct chunk plain;
+};
+
/*
* internal state that
* should get copied by god... to the child SA state.
@@ -230,7 +240,11 @@ struct state {
const char *st_suspended_md_func;
int st_suspended_md_line;
- struct ike_frag *ike_frags; /* collected ike fragments */
+ /* collected ike fragments */
+ union {
+ struct ike_frag *ike_frags;
+ struct ikev2_frag *ikev2_frags;
+ };
struct trans_attrs st_oakley;
@@ -315,6 +329,7 @@ struct state {
/* my stuff */
chunk_t st_tpacket; /* Transmitted packet */
+ struct ikev2_frag *st_tfrags; /* Transmitted fragments */
#ifdef HAVE_LABELED_IPSEC
struct xfrm_user_sec_ctx_ike *sec_ctx;
@@ -545,6 +560,7 @@ extern void fmt_state(struct state *st, const monotime_t n,
extern void delete_states_by_peer(const ip_address *peer);
extern void replace_states_by_peer(const ip_address *peer);
extern void release_fragments(struct state *st);
+extern void release_v2fragments(struct ikev2_frag **fragp);
extern void v1_delete_state_by_xauth_name(struct state *st, void *name);
extern void delete_state_by_id_name(struct state *st, void *name);
--
Email: Herbert Xu <herbert at gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
More information about the Swan-dev
mailing list