Hi, this diff adds support for IKEv2 Message Fragmentation as defined in RFC 7383 (https://tools.ietf.org/html/rfc7383) to iked(8).
Tobias Index: regress/sbin/iked/parser/common.c =================================================================== RCS file: /mount/openbsd/cvs/src/regress/sbin/iked/parser/common.c,v retrieving revision 1.1 diff -u -p -u -r1.1 common.c --- regress/sbin/iked/parser/common.c 29 May 2017 20:59:28 -0000 1.1 +++ regress/sbin/iked/parser/common.c 26 Feb 2019 12:37:35 -0000 @@ -166,6 +166,12 @@ config_add_proposal(struct iked_proposal return (NULL); } +void +config_free_fragments(struct iked_frag *frag) +{ + return; +} + int ikev2_send_informational(struct iked *env, struct iked_message *msg) { Index: regress/sbin/iked/parser/test_parser_fuzz.c =================================================================== RCS file: /mount/openbsd/cvs/src/regress/sbin/iked/parser/test_parser_fuzz.c,v retrieving revision 1.2 diff -u -p -u -r1.2 test_parser_fuzz.c --- regress/sbin/iked/parser/test_parser_fuzz.c 22 Mar 2018 21:11:49 -0000 1.2 +++ regress/sbin/iked/parser/test_parser_fuzz.c 26 Feb 2019 12:40:50 -0000 @@ -227,6 +227,50 @@ u_int8_t sk_pld[] = { 0x3d, 0xa1, 0xa5, 0x8f }; +u_int8_t skf_1of1_pld[] = { + 0x21, 0x00, 0x01, 0x98, 0x00, 0x01, 0x00, 0x01, 0x14, 0x77, + 0x25, 0x7b, 0x82, 0xc0, 0xdb, 0x0b, 0x24, 0x36, 0x36, 0x13, + 0x36, 0xe4, 0x99, 0xad, 0xf5, 0xaf, 0x26, 0x6f, 0x47, 0xd2, + 0x0d, 0x65, 0xe1, 0xa8, 0xcb, 0x35, 0x1e, 0x53, 0xce, 0x6d, + 0x8e, 0xf9, 0xe4, 0x51, 0xe3, 0x27, 0x10, 0x43, 0x38, 0x84, + 0x54, 0x1d, 0x7a, 0x1a, 0x89, 0x34, 0x06, 0xb3, 0x62, 0x86, + 0x98, 0x3b, 0x39, 0x91, 0x6e, 0xe8, 0x65, 0x3e, 0x31, 0xa8, + 0x08, 0xfe, 0x83, 0x56, 0x30, 0xd3, 0xe0, 0xfd, 0x73, 0x92, + 0x85, 0x2d, 0xae, 0x1d, 0x7d, 0xdb, 0x47, 0x05, 0x57, 0xe7, + 0x8e, 0xc5, 0xa5, 0x1b, 0x0e, 0x85, 0x1f, 0x12, 0x6d, 0xe6, + 0xdb, 0x3a, 0x3e, 0x99, 0xd1, 0x23, 0x41, 0xa4, 0x1c, 0x46, + 0x38, 0xd1, 0xa8, 0x84, 0x96, 0x13, 0xdb, 0x2a, 0x1d, 0x3b, + 0xb8, 0xd2, 0x04, 0xb3, 0x0d, 0xb4, 0x71, 0x90, 0xdb, 0xf6, + 0x2d, 0x60, 0x01, 0xc2, 0xb2, 0x89, 0xbd, 0xe9, 0x95, 0x7b, + 0x53, 0xa4, 0x94, 0x7e, 0x12, 0xe9, 0x5f, 0xfc, 0x51, 0x17, + 0x94, 0x3e, 0xba, 0xc2, 0xa5, 0x4d, 0x3a, 0x4d, 0x4b, 0x95, + 0x6d, 0x91, 0xc2, 0xb0, 0x2d, 0xb7, 0x24, 0xe8, 0x3b, 0xbd, + 0xe0, 0xcc, 0x09, 0x50, 0x11, 0x83, 0xc0, 0xcd, 0x29, 0x33, + 0xd5, 0x8f, 0x8a, 0xd1, 0xe3, 0xe8, 0x4f, 0x6a, 0x10, 0x4a, + 0x64, 0x97, 0x0f, 0x38, 0x58, 0x8d, 0x7f, 0x5d, 0xb4, 0x6b, + 0xa0, 0x42, 0x5e, 0x95, 0xe6, 0x08, 0x3e, 0x01, 0xf8, 0x82, + 0x90, 0x81, 0xd4, 0x70, 0xb5, 0xb2, 0x8c, 0x64, 0xa9, 0x56, + 0xdd, 0xc2, 0xda, 0xe1, 0xd3, 0xad, 0xf8, 0x5b, 0x99, 0x0b, + 0x19, 0x5e, 0x88, 0x0d, 0x81, 0x04, 0x4d, 0xc1, 0x43, 0x41, + 0xf1, 0xd3, 0x45, 0x65, 0x62, 0x70, 0x2f, 0xfa, 0x62, 0xbe, + 0x7d, 0xf4, 0x94, 0x91, 0xe0, 0xbb, 0xb1, 0xbc, 0xe5, 0x27, + 0xc8, 0x15, 0xd4, 0xcb, 0x82, 0x97, 0x15, 0x46, 0x82, 0xbb, + 0x48, 0xbb, 0x16, 0x25, 0xbe, 0x82, 0xe4, 0x27, 0x80, 0xf3, + 0xc2, 0x92, 0x3b, 0xd6, 0xc3, 0x65, 0x20, 0xec, 0x50, 0xdb, + 0x6a, 0xcb, 0x47, 0x73, 0xf7, 0x98, 0xf1, 0x66, 0x5e, 0xc4, + 0xe9, 0x87, 0xf8, 0xcb, 0x1e, 0x06, 0xa7, 0x67, 0xf5, 0xec, + 0x73, 0xe5, 0xc7, 0x4d, 0xc2, 0x90, 0xe4, 0xdf, 0x9d, 0x1f, + 0x05, 0x67, 0x99, 0xd6, 0xf0, 0xc4, 0x20, 0xbc, 0xf8, 0xf5, + 0x3e, 0x19, 0xe9, 0x3a, 0x12, 0xe1, 0xcc, 0x9f, 0x81, 0x55, + 0x1e, 0xad, 0xc8, 0xa3, 0xe5, 0x98, 0xbe, 0xe0, 0x4d, 0xb7, + 0x6b, 0xd5, 0xbe, 0x6a, 0x3d, 0x76, 0xb6, 0xe2, 0xa5, 0xa7, + 0x96, 0x68, 0xeb, 0x91, 0xee, 0x02, 0xfc, 0xe4, 0x01, 0xc3, + 0x24, 0xda, 0x4c, 0xff, 0x10, 0x27, 0x78, 0xb0, 0x0b, 0x55, + 0x5c, 0xce, 0x62, 0x7d, 0x33, 0x2b, 0x25, 0x99, 0xaa, 0x99, + 0xea, 0xa3, 0x1d, 0xd8, 0x2b, 0x57, 0xb5, 0xe4, 0x04, 0x21, + 0x75, 0xd9, 0xc4, 0xd0, 0x3d, 0xa1, 0xa5, 0x8f +}; + u_int8_t cp_pld[] = { 0x2f, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x00, /* REQUEST */ @@ -450,6 +494,25 @@ parser_fuzz_tests(void) FUZZ_TRUNCATE_START | FUZZ_TRUNCATE_END | FUZZ_BASE64, ibuf_data(data), ibuf_size(data)); + ibuf_free(data); + perform_test(fuzz); + TEST_DONE(); + + TEST_START("fuzz skf_1of1 payload"); + ASSERT_PTR_NE(data = ibuf_new(cookies, sizeof(cookies)), NULL); + ASSERT_INT_EQ(ibuf_add(data, genhdr, sizeof(genhdr)), 0); + ASSERT_INT_EQ(ibuf_add(data, skf_1of1_pld, sizeof(skf_1of1_pld)), 0); + set_length(ibuf_data(data), ibuf_size(data)); + set_nextpayload(ibuf_data(data), IKEV2_PAYLOAD_SA); + print_hex(ibuf_data(data), 0, ibuf_size(data)); + prepare_header(&hdr, data); + prepare_message(&msg, data); + ASSERT_INT_EQ(ikev2_pld_parse(NULL, &hdr, &msg, 0), 0); + fuzz = fuzz_begin(FUZZ_1_BIT_FLIP | FUZZ_2_BIT_FLIP | + FUZZ_1_BYTE_FLIP | FUZZ_2_BYTE_FLIP | + FUZZ_TRUNCATE_START | FUZZ_TRUNCATE_END | + FUZZ_BASE64, + ibuf_data(data), ibuf_size(data)); ibuf_free(data); perform_test(fuzz); TEST_DONE(); Index: sbin/iked/config.c =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/config.c,v retrieving revision 1.49 diff -u -p -u -r1.49 config.c --- sbin/iked/config.c 27 Nov 2017 18:39:35 -0000 1.49 +++ sbin/iked/config.c 26 Feb 2019 12:29:41 -0000 @@ -94,12 +94,30 @@ config_free_kex(struct iked_kex *kex) } void +config_free_fragments(struct iked_frag *frag) +{ + size_t i; + if (frag && frag->frag_arr) { + for (i = 0; i < frag->frag_count; i++) { + free(frag->frag_arr[i]->frag_data); + frag->frag_arr[i]->frag_data = NULL; + free(frag->frag_arr[i]); + frag->frag_arr[i] = NULL; + } + free(frag->frag_arr); + frag->frag_arr = NULL; + bzero(frag, sizeof(struct iked_frag)); + } +} + +void config_free_sa(struct iked *env, struct iked_sa *sa) { timer_del(env, &sa->sa_timer); timer_del(env, &sa->sa_keepalive); timer_del(env, &sa->sa_rekey); + config_free_fragments(&sa->sa_fragments); config_free_proposals(&sa->sa_proposals, 0); config_free_childsas(env, &sa->sa_childsas, NULL, NULL); sa_free_flows(env, &sa->sa_flows); @@ -925,6 +943,29 @@ config_setkeys(struct iked *env) EVP_PKEY_free(key); return (ret); +} + +int +config_setfragmentation(struct iked *env) +{ + unsigned int boolval; + + boolval = env->sc_frag; + proc_compose(&env->sc_ps, PROC_IKEV2, IMSG_CTL_FRAGMENTATION, + &boolval, sizeof(boolval)); + return (0); +} + +int +config_getfragmentation(struct iked *env, struct imsg *imsg) +{ + unsigned int boolval; + + IMSG_SIZE_CHECK(imsg, &boolval); + memcpy(&boolval, imsg->data, sizeof(boolval)); + env->sc_frag = boolval; + log_debug("%s: %sfragmentation", __func__, env->sc_frag ? "" : "no "); + return (0); } int Index: sbin/iked/iked.c =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/iked.c,v retrieving revision 1.36 diff -u -p -u -r1.36 iked.c --- sbin/iked/iked.c 27 Nov 2017 18:39:35 -0000 1.36 +++ sbin/iked/iked.c 26 Feb 2019 09:17:36 -0000 @@ -251,6 +251,7 @@ parent_configure(struct iked *env) fatal("pledge"); config_setmobike(env); + config_setfragmentation(env); config_setcoupled(env, env->sc_decoupled ? 0 : 1); config_setmode(env, env->sc_passive ? 1 : 0); config_setocsp(env); @@ -282,6 +283,7 @@ parent_reload(struct iked *env, int rese config_setcompile(env, PROC_IKEV2); config_setmobike(env); + config_setfragmentation(env); config_setcoupled(env, env->sc_decoupled ? 0 : 1); config_setmode(env, env->sc_passive ? 1 : 0); config_setocsp(env); Index: sbin/iked/iked.h =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/iked.h,v retrieving revision 1.119 diff -u -p -u -r1.119 iked.h --- sbin/iked/iked.h 6 Aug 2018 06:30:06 -0000 1.119 +++ sbin/iked/iked.h 26 Feb 2019 12:28:16 -0000 @@ -362,6 +362,21 @@ struct iked_kex { struct ibuf *kex_dhpeer; /* pointer to i or r */ }; +struct iked_frag_entry { + uint8_t *frag_data; + size_t frag_size; +}; + +struct iked_frag { + struct iked_frag_entry **frag_arr; /* list of fragment buffers */ + size_t frag_count; /* number of fragments received */ +#define IKED_FRAG_TOTAL_MAX 111 /* upper limit of frag_total (64kB / 576B) */ + size_t frag_total; /* total numbe of fragments */ + size_t frag_total_size; + uint8_t frag_nextpayload; + +}; + struct iked_sa { struct iked_sahdr sa_hdr; uint32_t sa_msgid; /* Last request rcvd */ @@ -377,6 +392,8 @@ struct iked_sa { struct iked_addr sa_local; int sa_fd; + struct iked_frag sa_fragments; + int sa_natt; /* for IKE messages */ int sa_udpencap; /* for pfkey */ int sa_usekeepalive;/* NAT-T keepalive */ @@ -445,6 +462,7 @@ struct iked_sa { uint16_t sa_cpi_in; /* IPcomp incoming*/ int sa_mobike; /* MOBIKE */ + int sa_frag; /* fragmentation */ struct iked_timer sa_timer; /* SA timeouts */ #define IKED_IKE_SA_EXCHANGE_TIMEOUT 300 /* 5 minutes */ @@ -602,6 +620,7 @@ struct iked { uint8_t sc_decoupled; uint8_t sc_mobike; /* MOBIKE */ + uint8_t sc_frag; /* fragmentation */ struct iked_policies sc_policies; struct iked_policy *sc_defaultcon; @@ -653,6 +672,7 @@ int control_listen(struct control_sock struct iked_policy * config_new_policy(struct iked *); void config_free_kex(struct iked_kex *); +void config_free_fragments(struct iked_frag *frag); void config_free_sa(struct iked *, struct iked_sa *); struct iked_sa * config_new_sa(struct iked *, int); @@ -701,6 +721,8 @@ int config_setkeys(struct iked *); int config_getkey(struct iked *, struct imsg *); int config_setmobike(struct iked *); int config_getmobike(struct iked *, struct imsg *); +int config_setfragmentation(struct iked *); +int config_getfragmentation(struct iked *, struct imsg *); /* policy.c */ void policy_init(struct iked *); @@ -859,6 +881,10 @@ void ikev2_msg_flushqueue(struct iked * struct iked_message * ikev2_msg_lookup(struct iked *, struct iked_msgqueue *, struct iked_message *, struct ike_header *); +void ikev2_msg_lookup_dispose_all(struct iked *env, struct iked_msgqueue *queue, + struct iked_message *msg, struct ike_header *hdr); +int ikev2_msg_lookup_retransmit_all(struct iked *env, struct iked_msgqueue *queue, + struct iked_message *msg, struct ike_header *hdr, struct iked_sa *sa); /* ikev2_pld.c */ int ikev2_pld_parse(struct iked *, struct ike_header *, Index: sbin/iked/ikev2.c =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/ikev2.c,v retrieving revision 1.166 diff -u -p -u -r1.166 ikev2.c --- sbin/iked/ikev2.c 5 Mar 2018 14:30:30 -0000 1.166 +++ sbin/iked/ikev2.c 26 Feb 2019 12:27:21 -0000 @@ -144,6 +144,8 @@ ssize_t ikev2_add_sighashnotify(struct i ssize_t); ssize_t ikev2_add_nat_detection(struct iked *, struct ibuf *, struct ikev2_payload **, struct iked_message *, ssize_t); +ssize_t ikev2_add_fragmentation(struct iked *, struct ibuf *, + struct ikev2_payload **, struct iked_message *, ssize_t); ssize_t ikev2_add_mobike(struct iked *, struct ibuf *, struct ikev2_payload **, ssize_t, struct iked_sa *); @@ -202,6 +204,8 @@ ikev2_dispatch_parent(int fd, struct pri return (0); case IMSG_CTL_MOBIKE: return (config_getmobike(env, imsg)); + case IMSG_CTL_FRAGMENTATION: + return (config_getfragmentation(env, imsg)); case IMSG_UDP_SOCKET: return (config_getsocket(env, imsg, ikev2_msg_cb)); case IMSG_PFKEY_SOCKET: @@ -399,9 +403,9 @@ void ikev2_recv(struct iked *env, struct iked_message *msg) { struct ike_header *hdr; - struct iked_message *m; struct iked_sa *sa; unsigned int initiator, flag = 0; + int r; hdr = ibuf_seek(msg->msg_data, msg->msg_offset, sizeof(*hdr)); @@ -442,7 +446,8 @@ ikev2_recv(struct iked *env, struct iked if (msg->msg_msgid > sa->sa_reqid) return; if (hdr->ike_exchange != IKEV2_EXCHANGE_INFORMATIONAL && - !ikev2_msg_lookup(env, &sa->sa_requests, msg, hdr)) + !ikev2_msg_lookup(env, &sa->sa_requests, msg, hdr) && + sa->sa_fragments.frag_count == 0) return; if (flag) { if ((sa->sa_stateflags & flag) == 0) @@ -454,10 +459,9 @@ ikev2_recv(struct iked *env, struct iked initiator = 1; } /* - * There's no need to keep the request around anymore + * There's no need to keep the request (fragments) around anymore */ - if ((m = ikev2_msg_lookup(env, &sa->sa_requests, msg, hdr))) - ikev2_msg_dispose(env, &sa->sa_requests, m); + ikev2_msg_lookup_dispose_all(env, &sa->sa_requests, msg, hdr); } else { /* * IKE_SA_INIT is special since it always uses the message id 0. @@ -483,14 +487,15 @@ ikev2_recv(struct iked *env, struct iked /* * See if we have responded to this request before */ - if ((m = ikev2_msg_lookup(env, &sa->sa_responses, msg, hdr))) { - if (ikev2_msg_retransmit_response(env, sa, m)) { + if ((r = ikev2_msg_lookup_retransmit_all(env, &sa->sa_responses, msg, hdr, sa)) != 0) { + if (r == -1) { log_warn("%s: failed to retransmit a " "response", __func__); sa_free(env, sa); } return; - } else if (sa->sa_msgid_set && msg->msg_msgid == sa->sa_msgid) { + } else if (sa->sa_msgid_set && msg->msg_msgid == sa->sa_msgid && + !(sa->sa_fragments.frag_count)) { /* * Response is being worked on, most likely we're * waiting for the CA process to get back to us @@ -803,6 +808,9 @@ ikev2_init_recv(struct iked *env, struct return; } + if (sa->sa_fragments.frag_count != 0) + return; + if (!ikev2_msg_frompeer(msg)) return; @@ -1022,6 +1030,13 @@ ikev2_init_ike_sa_peer(struct iked *env, goto done; len = ibuf_size(sa->sa_inonce); + /* Fragmentation Notify */ + if (env->sc_frag) { + if ((len = ikev2_add_fragmentation(env, buf, &pld, &req, len)) + == -1) + goto done; + } + if ((env->sc_opts & IKED_OPT_NONATT) == 0) { if (ntohs(port) == IKED_NATT_PORT) { /* Enforce NAT-T on the initiator side */ @@ -1973,6 +1988,29 @@ ikev2_add_cp(struct iked *env, struct ik } ssize_t +ikev2_add_fragmentation(struct iked *env, struct ibuf *buf, + struct ikev2_payload **pld, struct iked_message *msg, ssize_t len) +{ + struct ikev2_notify *n; + uint8_t *ptr; + + if (*pld != NULL) + if (ikev2_next_payload(*pld, len, IKEV2_PAYLOAD_NOTIFY) == -1) + return (-1); + if ((*pld = ikev2_add_payload(buf)) == NULL) + return (-1); + len = sizeof(*n); + if ((ptr = ibuf_advance(buf, len)) == NULL) + return (-1); + n = (struct ikev2_notify *) ptr; + n->n_protoid = 0; + n->n_spisize = 0; + n->n_type = htobe16(IKEV2_N_FRAGMENTATION_SUPPORTED); + + return (len); +} + +ssize_t ikev2_add_proposals(struct iked *env, struct iked_sa *sa, struct ibuf *buf, struct iked_proposals *proposals, uint8_t protoid, int initiator, int sendikespi, int skipdh) @@ -2283,6 +2321,9 @@ ikev2_resp_recv(struct iked *env, struct if ((sa = msg->msg_sa) == NULL) return; + if (sa->sa_fragments.frag_count !=0) + return; + if (msg->msg_natt && sa->sa_natt == 0) { log_debug("%s: NAT-T message received, updated SA", __func__); sa->sa_natt = 1; @@ -2407,6 +2448,13 @@ ikev2_resp_ike_sa_init(struct iked *env, goto done; len = ibuf_size(sa->sa_rnonce); + /* Fragmentation Notify*/ + if (sa->sa_frag) { + if ((len = ikev2_add_fragmentation(env, buf, &pld, &resp, len)) + == -1) + goto done; + } + if ((env->sc_opts & IKED_OPT_NONATT) == 0 && msg->msg_local.ss_family != AF_UNSPEC) { if ((len = ikev2_add_nat_detection(env, buf, &pld, &resp, len)) @@ -2485,6 +2533,7 @@ ikev2_send_auth_failed(struct iked *env, timer_del(env, &sa->sa_timer); timer_set(env, &sa->sa_timer, ikev2_ike_sa_timeout, sa); timer_add(env, &sa->sa_timer, IKED_IKE_SA_DELETE_TIMEOUT); + config_free_fragments(&sa->sa_fragments); return (ret); } Index: sbin/iked/ikev2.h =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/ikev2.h,v retrieving revision 1.27 diff -u -p -u -r1.27 ikev2.h --- sbin/iked/ikev2.h 3 Dec 2017 21:02:44 -0000 1.27 +++ sbin/iked/ikev2.h 26 Feb 2019 09:15:04 -0000 @@ -78,6 +78,11 @@ struct ikev2_payload { uint16_t pld_length; /* Payload length with header */ } __packed; +struct ikev2_frag_payload { + uint16_t frag_num; /* current fragment message number */ + uint16_t frag_total; /* total number of fragment messages */ +} __packed; + #define IKEV2_CRITICAL_PAYLOAD 0x01 /* First bit in the reserved field */ /* IKEv2 payload types */ @@ -99,6 +104,7 @@ struct ikev2_payload { #define IKEV2_PAYLOAD_CP 47 /* Configuration Payload */ #define IKEV2_PAYLOAD_EAP 48 /* Extensible Authentication */ #define IKEV2_PAYLOAD_GSPM 49 /* RFC6467 Generic Secure Password */ +#define IKEV2_PAYLOAD_SKF 53 /* RFC7383 Encrypted Fragment Payload */ extern struct iked_constmap ikev2_payload_map[]; @@ -243,6 +249,11 @@ extern struct iked_constmap ikev2_xforma #define IKEV2_XFORMDH_X_CURVE25519 1034 /* draft-ietf-ipsecme-safecurves-00 */ extern struct iked_constmap ikev2_xformdh_map[]; + +#define IKEV2_IPV4_OVERHEAD (20 + 8 + 28) /* IPv4 + UDP + IKE_HDR*/ +#define IKEV2_MAXLEN_IPV4_FRAG (576 - IKEV2_IPV4_OVERHEAD) +#define IKEV2_IPV6_OVERHEAD (40 + 8 + 28) /* IPv6 + UDP + IKE_HDR*/ +#define IKEV2_MAXLEN_IPV6_FRAG (1280 - IKEV2_IPV6_OVERHEAD) #define IKEV2_XFORMESN_NONE 0 /* No ESN */ #define IKEV2_XFORMESN_ESN 1 /* ESN */ Index: sbin/iked/ikev2_msg.c =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/ikev2_msg.c,v retrieving revision 1.53 diff -u -p -u -r1.53 ikev2_msg.c --- sbin/iked/ikev2_msg.c 27 Nov 2017 18:39:35 -0000 1.53 +++ sbin/iked/ikev2_msg.c 26 Feb 2019 09:15:04 -0000 @@ -46,6 +46,9 @@ void ikev1_recv(struct iked *, struct iked_message *); void ikev2_msg_response_timeout(struct iked *, void *); void ikev2_msg_retransmit_timeout(struct iked *, void *); +int ikev2_check_frag_oversize(struct iked_sa *sa, struct ibuf *buf); +int ikev2_send_encrypted_fragments(struct iked *env, struct iked_sa *sa, + struct ibuf *in,uint8_t exchange, uint8_t firstpayload, int response); void ikev2_msg_cb(int fd, short event, void *arg) @@ -616,7 +619,8 @@ ikev2_msg_decrypt(struct iked *env, stru __func__, outlen, encrlen, pad); print_hex(ibuf_data(out), 0, ibuf_size(out)); - if (ibuf_setsize(out, outlen) != 0) + /* Strip padding and padding length */ + if (ibuf_setsize(out, outlen - pad - 1) != 0) goto done; ibuf_release(src); @@ -629,6 +633,25 @@ ikev2_msg_decrypt(struct iked *env, stru } int +ikev2_check_frag_oversize(struct iked_sa *sa, struct ibuf *buf) { + size_t len = ibuf_length(buf); + sa_family_t sa_fam; + size_t max; + size_t ivlen, integrlen, blocklen; + + sa_fam = ((struct sockaddr *)&sa->sa_local.addr)->sa_family; + + max = sa_fam == AF_INET ? IKEV2_MAXLEN_IPV4_FRAG + : IKEV2_MAXLEN_IPV6_FRAG; + + blocklen = cipher_length(sa->sa_encr); + ivlen = cipher_ivlength(sa->sa_encr); + integrlen = hash_length(sa->sa_integr); + + return ((len + ivlen + blocklen + integrlen) >= max) && sa->sa_frag; +} + +int ikev2_msg_send_encrypt(struct iked *env, struct iked_sa *sa, struct ibuf **ep, uint8_t exchange, uint8_t firstpayload, int response) { @@ -638,6 +661,12 @@ ikev2_msg_send_encrypt(struct iked *env, struct ibuf *buf, *e = *ep; int ret = -1; + /* Check if msg needs to be fragmented */ + if (ikev2_check_frag_oversize(sa, e)) { + return ikev2_send_encrypted_fragments(env, sa, e, exchange, + firstpayload, response); + } + if ((buf = ikev2_msg_init(env, &resp, &sa->sa_peer.addr, sa->sa_peer.addr.ss_len, &sa->sa_local.addr, sa->sa_local.addr.ss_len, response)) == NULL) @@ -689,6 +718,123 @@ ikev2_msg_send_encrypt(struct iked *env, return (ret); } +int +ikev2_send_encrypted_fragments(struct iked *env, struct iked_sa *sa, + struct ibuf *in, uint8_t exchange, uint8_t firstpayload, int response) { + struct iked_message resp; + struct ibuf *buf, *e; + struct ike_header *hdr; + struct ikev2_payload *pld; + struct ikev2_frag_payload *frag; + sa_family_t sa_fam; + size_t ivlen, integrlen, blocklen; + size_t max_len, left, offset=0;; + size_t frag_num = 1, frag_total; + uint8_t *data; + uint32_t msgid; + int ret = -1; + + sa_fam = ((struct sockaddr *)&sa->sa_local.addr)->sa_family; + + left = ibuf_length(in); + + /* Calculate max allowed size of a fragments payload */ + blocklen = cipher_length(sa->sa_encr); + ivlen = cipher_ivlength(sa->sa_encr); + integrlen = hash_length(sa->sa_integr); + max_len = (sa_fam == AF_INET ? IKEV2_MAXLEN_IPV4_FRAG + : IKEV2_MAXLEN_IPV6_FRAG) + - ivlen - blocklen - integrlen; + + /* Total number of fragments to send */ + frag_total = (left / max_len) + 1; + + msgid = response ? sa->sa_msgid : ikev2_msg_id(env, sa); + + while (frag_num <= frag_total) { + if ((buf = ikev2_msg_init(env, &resp, &sa->sa_peer.addr, + sa->sa_peer.addr.ss_len, &sa->sa_local.addr, + sa->sa_local.addr.ss_len, response)) == NULL) + goto done; + + resp.msg_msgid = msgid; + + /* IKE header */ + if ((hdr = ikev2_add_header(buf, sa, resp.msg_msgid, + IKEV2_PAYLOAD_SKF, exchange, response ? IKEV2_FLAG_RESPONSE + : 0)) == NULL) + goto done; + + /* Payload header */ + if ((pld = ikev2_add_payload(buf)) == NULL) + goto done; + + /* Fragment header */ + if ((frag = ibuf_advance(buf, sizeof(*frag))) == NULL) { + log_debug("%s: failed to add SKF fragment header", + __func__); + goto done; + } + frag->frag_num = htobe16(frag_num); + frag->frag_total = htobe16(frag_total); + + /* Encrypt message and add as an E payload */ + data = ibuf_seek(in, offset, 0); + if((e=ibuf_new(data, MIN(left, max_len))) == NULL) { + goto done; + } + if ((e = ikev2_msg_encrypt(env, sa, e)) == NULL) { + log_debug("%s: encryption failed", __func__); + goto done; + } + if (ibuf_cat(buf, e) != 0) + goto done; + + if (ikev2_next_payload(pld, ibuf_size(e) + sizeof(*frag), + firstpayload) == -1) + goto done; + + if (ikev2_set_header(hdr, ibuf_size(buf) - sizeof(*hdr)) == -1) + goto done; + + /* Add integrity checksum (HMAC) */ + if (ikev2_msg_integr(env, sa, buf) != 0) { + log_debug("%s: integrity checksum failed", __func__); + goto done; + } + + log_debug("%s: Fragment %zu of %zu has size of %zu bytes.", + __func__, frag_num, frag_total, + ibuf_size(buf) - sizeof(*hdr)); + print_hex(ibuf_data(buf), 0, ibuf_size(buf)); + + resp.msg_data = buf; + resp.msg_sa = sa; + resp.msg_fd = sa->sa_fd; + TAILQ_INIT(&resp.msg_proposals); + + if (ikev2_msg_send(env, &resp) == -1) + goto done; + + offset += MIN(left, max_len); + left -= MIN(left, max_len); + frag_num++; + + /* MUST be zero after first fragment */ + firstpayload = 0; + + ikev2_msg_cleanup(env, &resp); + ibuf_release(e); + e = NULL; + } + + return 0; +done: + ikev2_msg_cleanup(env, &resp); + ibuf_release(e); + return ret; +} + struct ibuf * ikev2_msg_auth(struct iked *env, struct iked_sa *sa, int response) { @@ -990,6 +1136,42 @@ ikev2_msg_lookup(struct iked *env, struc } return (m); +} + +void +ikev2_msg_lookup_dispose_all(struct iked *env, struct iked_msgqueue *queue, + struct iked_message *msg, struct ike_header *hdr) +{ + struct iked_message *m = NULL, *tmp = NULL; + + TAILQ_FOREACH_SAFE(m, queue, msg_entry, tmp) { + if (m->msg_msgid == msg->msg_msgid && + m->msg_exchange == hdr->ike_exchange) { + TAILQ_REMOVE(queue, m, msg_entry); + timer_del(env, &m->msg_timer); + ikev2_msg_cleanup(env, m); + free(m); + } + } +} + +int +ikev2_msg_lookup_retransmit_all(struct iked *env, struct iked_msgqueue *queue, + struct iked_message *msg, struct ike_header *hdr, struct iked_sa *sa) +{ + struct iked_message *m = NULL, *tmp = NULL; + int count = 0; + + TAILQ_FOREACH_SAFE(m, queue, msg_entry, tmp) { + if (m->msg_msgid == msg->msg_msgid && + m->msg_exchange == hdr->ike_exchange) { + if (ikev2_msg_retransmit_response(env, sa, msg)) { + return -1; + } + count++; + } + } + return count; } int Index: sbin/iked/ikev2_pld.c =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/ikev2_pld.c,v retrieving revision 1.70 diff -u -p -u -r1.70 ikev2_pld.c --- sbin/iked/ikev2_pld.c 22 Mar 2018 21:11:49 -0000 1.70 +++ sbin/iked/ikev2_pld.c 26 Feb 2019 12:29:10 -0000 @@ -95,6 +95,10 @@ int ikev2_pld_auth(struct iked *, struc struct iked_message *, size_t, size_t); int ikev2_pld_e(struct iked *, struct ikev2_payload *, struct iked_message *, size_t, size_t); +int ikev2_pld_ef(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, size_t offset, size_t left); +int ikev2_frags_reassemble(struct iked *env, + struct ikev2_payload *pld, struct iked_message *msg); int ikev2_validate_cp(struct iked_message *, size_t, size_t, struct ikev2_cp *); int ikev2_pld_cp(struct iked *, struct ikev2_payload *, @@ -249,6 +253,9 @@ ikev2_pld_payloads(struct iked *env, str case IKEV2_PAYLOAD_SK: ret = ikev2_pld_e(env, &pld, msg, offset, left); break; + case IKEV2_PAYLOAD_SKF: + ret = ikev2_pld_ef(env, &pld, msg, offset, left); + break; case IKEV2_PAYLOAD_CP | IKED_E: ret = ikev2_pld_cp(env, &pld, msg, offset, left); break; @@ -266,8 +273,8 @@ ikev2_pld_payloads(struct iked *env, str return (-1); } - /* Encrypted payload must appear last */ - if (payload == IKEV2_PAYLOAD_SK) + /* Encrypted payloads must appear last */ + if ((payload == IKEV2_PAYLOAD_SK) || (payload == IKEV2_PAYLOAD_SKF)) return (0); payload = pld.pld_nextpayload; @@ -1251,6 +1258,23 @@ ikev2_pld_notify(struct iked *env, struc } msg->msg_parent->msg_cookie = msg->msg_cookie; break; + case IKEV2_N_FRAGMENTATION_SUPPORTED: + if (msg->msg_e) { + log_debug("%s: N_FRAGMENTATION_SUPPORTED encrypted", + __func__); + return (-1); + } + if (len != 0) { + log_debug("%s: ignoring malformed fragmentation" + " notification: %zu", __func__, len); + return (0); + } + if (!env->sc_frag) { + log_debug("%s: fragmentation disabled", __func__); + return (0); + } + msg->msg_sa->sa_frag = 1; + break; case IKEV2_N_SIGNATURE_HASH_ALGORITHMS: if (msg->msg_e) { log_debug("%s: SIGNATURE_HASH_ALGORITHMS: encrypted", @@ -1585,6 +1609,164 @@ ikev2_pld_ts(struct iked *env, struct ik } int +ikev2_pld_ef(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg, size_t offset, size_t left) +{ + struct iked_sa *sa = msg->msg_sa; + struct iked_frag *sa_frag = &sa->sa_fragments; + struct iked_frag_entry *el = NULL; + struct ikev2_frag_payload frag; + uint8_t *msgbuf = ibuf_data(msg->msg_data); + uint8_t *buf; + struct ibuf *e = NULL; + size_t frag_num, frag_total; + size_t len; + int ret = -1; + ssize_t elen; + + buf = msgbuf + offset; + memcpy(&frag, buf, sizeof(frag)); + frag_num = betoh16(frag.frag_num); + frag_total = betoh16(frag.frag_total); + + offset += sizeof(frag); + buf = msgbuf + offset; + len = left - sizeof(frag); + + /* Limit number of total fragments to avoid DOS */ + if (frag_total > IKED_FRAG_TOTAL_MAX ) { + log_debug("%s: Total Fragments too big %zu", + __func__, frag_total); + goto dropall; + } + + /* Check sanity of fragment header */ + if (frag_num == 0 || frag_total == 0) { + log_debug("%s: Malformed fragment received: %zu of %zu", + __func__, frag_num, frag_total); + goto done; + } + log_debug("%s: Received fragment: %zu of %zu", + __func__, frag_num, frag_total); + + /* Check new fragmented message */ + if (sa_frag->frag_arr == NULL) { + sa_frag->frag_arr = reallocarray(NULL, frag_total, sizeof(uint8_t*)); + bzero(sa_frag->frag_arr, frag_total * sizeof(uint8_t*)); + sa_frag->frag_total = frag_total; + sa_frag->frag_nextpayload = pld->pld_nextpayload; + } + + /* Drop all fragments if frag_num or frag_total don't match */ + if (frag_num > sa_frag->frag_total || frag_total > sa_frag->frag_total) + goto dropall; + + /* Silent drop if fragment already stored */ + if (sa_frag->frag_arr[frag_num-1] != NULL) + goto done; + + /* Decrypt fragment */ + if ((e = ibuf_new(buf, len)) == NULL) + goto done; + + if ((e = ikev2_msg_decrypt(env, msg->msg_sa, msg->msg_data, e)) + == NULL ) { + log_debug("%s: Failed to decrypt fragment: %zu of %zu", + __func__, frag_num, frag_total); + goto done; + } + elen = ibuf_length(e); + + /* Insert new list element */ + el = malloc(sizeof(struct iked_frag_entry)); + if (el == NULL) { + log_debug("%s: Failed allocating new fragment: %zu of %zu", + __func__, frag_num, frag_total); + goto done; + } + bzero(el, sizeof(*el)); + + sa_frag->frag_arr[frag_num-1] = el; + el->frag_size = elen; + el->frag_data = malloc(elen); + if (el->frag_data == NULL) { + log_debug("%s: Failed allocating new fragment data: %zu of %zu", + __func__, frag_num, frag_total); + goto done; + } + bzero(el->frag_data, sizeof(elen)); + + /* Copy plaintext to fragment */ + memcpy(el->frag_data, ibuf_seek(e, 0, 0), elen); + sa_frag->frag_total_size += elen; + sa_frag->frag_count++; + + /* If all frags are received start reassembly */ + if (sa_frag->frag_count == sa_frag->frag_total) { + log_debug("%s: All fragments received: %zu of %zu", + __func__, frag_num, frag_total); + ret = ikev2_frags_reassemble(env, pld, msg); + } else { + ret = 0; + } +done: + ibuf_release(e); + return (ret); +dropall: + config_free_fragments(sa_frag); + ibuf_release(e); + return -1; +} + +int +ikev2_frags_reassemble(struct iked *env, struct ikev2_payload *pld, + struct iked_message *msg) +{ + struct iked_frag *sa_frag = &msg->msg_sa->sa_fragments; + struct ibuf *e = NULL; + struct iked_frag_entry *el; + size_t offset = 0; + size_t i; + struct iked_message emsg; + int ret = -1; + + /* Reassemble fragments to single buffer */ + if ((e = ibuf_new(NULL, sa_frag->frag_total_size)) == NULL) { + log_debug("%s: Failed allocating SK buffer.", __func__); + goto done; + } + + /* Empty queue to new buffer */ + for (i = 0; i < sa_frag->frag_total; i++) { + el = sa_frag->frag_arr[i]; + memcpy(ibuf_seek(e, offset, 0), el->frag_data, el->frag_size); + offset += el->frag_size; + } + + log_debug("%s: Defragmented length %zd", __func__, + sa_frag->frag_total_size); + print_hex(ibuf_data(e), 0, sa_frag->frag_total_size); + + /* + * Parse decrypted payload + */ + bzero(&emsg, sizeof(emsg)); + memcpy(&emsg, msg, sizeof(*msg)); + emsg.msg_data = e; + emsg.msg_e = 1; + emsg.msg_parent = msg; + TAILQ_INIT(&emsg.msg_proposals); + + ret = ikev2_pld_payloads(env, &emsg, 0, ibuf_size(e), + sa_frag->frag_nextpayload); +done: + config_free_fragments(sa_frag); + ibuf_release(e); + + return (ret); +} + +int ikev2_pld_e(struct iked *env, struct ikev2_payload *pld, struct iked_message *msg, size_t offset, size_t left) { @@ -1595,6 +1777,13 @@ ikev2_pld_e(struct iked *env, struct ike uint8_t *buf; size_t len; int ret = -1; + + if (sa->sa_fragments.frag_arr != NULL) { + log_warn("%s: Received SK payload when SKFs are in queue.", + __func__); + config_free_fragments(&sa->sa_fragments); + return (ret); + } buf = msgbuf + offset; len = left; Index: sbin/iked/parse.y =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/parse.y,v retrieving revision 1.78 diff -u -p -u -r1.78 parse.y --- sbin/iked/parse.y 13 Feb 2019 22:57:07 -0000 1.78 +++ sbin/iked/parse.y 26 Feb 2019 09:30:23 -0000 @@ -105,6 +105,7 @@ static int rules = 0; static int passive = 0; static int decouple = 0; static int mobike = 1; +static int fragmentation = 0; static char *ocsp_url = NULL; struct ipsec_xf { @@ -395,6 +396,7 @@ typedef struct { %token IKEV1 FLOW SA TCPMD5 TUNNEL TRANSPORT COUPLE DECOUPLE SET %token INCLUDE LIFETIME BYTES INET INET6 QUICK SKIP DEFAULT %token IPCOMP OCSP IKELIFETIME MOBIKE NOMOBIKE +%token FRAGMENTATION NOFRAGMENTATION %token <v.string> STRING %token <v.number> NUMBER %type <v.string> string @@ -455,6 +457,8 @@ set : SET ACTIVE { passive = 0; } | SET PASSIVE { passive = 1; } | SET COUPLE { decouple = 0; } | SET DECOUPLE { decouple = 1; } + | SET FRAGMENTATION { fragmentation = 1; } + | SET NOFRAGMENTATION { fragmentation = 0; } | SET MOBIKE { mobike = 1; } | SET NOMOBIKE { mobike = 0; } | SET OCSP STRING { @@ -1167,6 +1171,7 @@ lookup(char *s) { "esp", ESP }, { "file", FILENAME }, { "flow", FLOW }, + { "fragmentation", FRAGMENTATION }, { "from", FROM }, { "group", GROUP }, { "ike", IKEV1 }, @@ -1181,6 +1186,7 @@ lookup(char *s) { "local", LOCAL }, { "mobike", MOBIKE }, { "name", NAME }, + { "nofragmentation", NOFRAGMENTATION }, { "nomobike", NOMOBIKE }, { "ocsp", OCSP }, { "passive", PASSIVE }, @@ -1579,6 +1585,7 @@ parse_config(const char *filename, struc free(ocsp_url); mobike = 1; + fragmentation = 0; decouple = passive = 0; ocsp_url = NULL; @@ -1592,6 +1599,7 @@ parse_config(const char *filename, struc env->sc_passive = passive ? 1 : 0; env->sc_decoupled = decouple ? 1 : 0; env->sc_mobike = mobike; + env->sc_frag = fragmentation; env->sc_ocsp_url = ocsp_url; if (!rules) Index: sbin/iked/types.h =================================================================== RCS file: /mount/openbsd/cvs/src/sbin/iked/types.h,v retrieving revision 1.29 diff -u -p -u -r1.29 types.h --- sbin/iked/types.h 27 Nov 2017 18:39:35 -0000 1.29 +++ sbin/iked/types.h 26 Feb 2019 09:25:33 -0000 @@ -103,6 +103,7 @@ enum imsg_type { IMSG_CTL_ACTIVE, IMSG_CTL_PASSIVE, IMSG_CTL_MOBIKE, + IMSG_CTL_FRAGMENTATION, IMSG_COMPILE, IMSG_UDP_SOCKET, IMSG_PFKEY_SOCKET,