From: Tobias Brunner Date: Tue, 21 Mar 2023 14:04:12 +0000 (+0100) Subject: child-create: Add support for optimized rekeying X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Foptimized-rekeying;p=thirdparty%2Fstrongswan.git child-create: Add support for optimized rekeying --- diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index d8514c4463..ab6b09a039 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2020 Tobias Brunner + * Copyright (C) 2008-2023 Tobias Brunner * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2005 Jan Hutter * @@ -218,17 +218,22 @@ struct private_child_create_t { child_sa_t *child_sa; /** - * successfully established the CHILD? + * Successfully established the CHILD? */ bool established; /** - * whether the CHILD_SA rekeys an existing one + * Whether the CHILD_SA rekeys an existing one */ bool rekey; /** - * whether we are retrying with another DH group + * Whether to use optimized rekeying + */ + bool optimized_rekeying; + + /** + * Whether we are retrying with another DH group */ bool retry; }; @@ -316,20 +321,22 @@ static bool allocate_spi(private_child_create_t *this) { proposal_t *proposal; - if (this->initiator) + if (!this->proto) { - this->proto = PROTO_ESP; - /* we just get a SPI for the first protocol. TODO: If we ever support - * proposal lists with mixed protocols, we'd need multiple SPIs */ - if (this->proposals->get_first(this->proposals, - (void**)&proposal) == SUCCESS) + if (this->initiator) { - this->proto = proposal->get_protocol(proposal); + this->proto = PROTO_ESP; + /* we just get a SPI for the first protocol */ + if (this->proposals->get_first(this->proposals, + (void**)&proposal) == SUCCESS) + { + this->proto = proposal->get_protocol(proposal); + } + } + else + { + this->proto = this->proposal->get_protocol(this->proposal); } - } - else - { - this->proto = this->proposal->get_protocol(this->proposal); } this->my_spi = this->child_sa->alloc_spi(this->child_sa, this->proto); if (!this->my_spi) @@ -880,8 +887,9 @@ static bool build_payloads_multi_ke(private_child_create_t *this, */ static bool build_payloads(private_child_create_t *this, message_t *message) { - sa_payload_t *sa_payload; + notify_payload_t *notify; nonce_payload_t *nonce_payload; + sa_payload_t *sa_payload; ts_payload_t *ts_payload; kernel_feature_t features; @@ -890,16 +898,6 @@ static bool build_payloads(private_child_create_t *this, message_t *message) return build_payloads_multi_ke(this, message); } - if (this->initiator) - { - sa_payload = sa_payload_create_from_proposals_v2(this->proposals); - } - else - { - sa_payload = sa_payload_create_from_proposal_v2(this->proposal); - } - message->add_payload(message, (payload_t*)sa_payload); - /* add nonce payload if not in IKE_AUTH */ if (message->get_exchange_type(message) == CREATE_CHILD_SA) { @@ -918,15 +916,7 @@ static bool build_payloads(private_child_create_t *this, message_t *message) return FALSE; } - /* add TSi/TSr payloads */ - ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi, - this->child.label); - message->add_payload(message, (payload_t*)ts_payload); - ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr, - this->child.label); - message->add_payload(message, (payload_t*)ts_payload); - - /* add a notify if we are not in tunnel mode */ + /* add a notify if we are not using tunnel mode */ switch (this->mode) { case MODE_TRANSPORT: @@ -945,6 +935,36 @@ static bool build_payloads(private_child_create_t *this, message_t *message) message->add_notify(message, FALSE, ESP_TFC_PADDING_NOT_SUPPORTED, chunk_empty); } + + if (this->optimized_rekeying) + { + /* omit SA and TS payloads when using optimized rekeying, but send + * an appropriate notify with our SPI */ + notify = notify_payload_create_from_protocol_and_type(PLV2_NOTIFY, + this->proto, OPTIMIZED_REKEY); + notify->set_spi(notify, this->my_spi); + message->add_payload(message, (payload_t*)notify); + return TRUE; + } + + if (this->initiator) + { + sa_payload = sa_payload_create_from_proposals_v2(this->proposals); + } + else + { + sa_payload = sa_payload_create_from_proposal_v2(this->proposal); + } + message->add_payload(message, (payload_t*)sa_payload); + + /* add TSi/TSr payloads */ + ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi, + this->child.label); + message->add_payload(message, (payload_t*)ts_payload); + ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr, + this->child.label); + message->add_payload(message, (payload_t*)ts_payload); + return TRUE; } @@ -969,12 +989,23 @@ static void add_ipcomp_notify(private_child_create_t *this, } /** - * handle a received notify payload + * Handle a received notify payload. */ static void handle_notify(private_child_create_t *this, notify_payload_t *notify) { switch (notify->get_notify_type(notify)) { + case OPTIMIZED_REKEY: + if (this->optimized_rekeying) + { + this->other_spi = notify->get_spi(notify); + if (!this->other_spi) + { + DBG1(DBG_IKE, "received invalid %N notify, ignored", + notify_type_names, OPTIMIZED_REKEY); + } + } + break; case USE_TRANSPORT_MODE: this->mode = MODE_TRANSPORT; break; @@ -1207,9 +1238,15 @@ static void process_payloads(private_child_create_t *this, message_t *message) payload_t *payload; sa_payload_t *sa_payload; ts_payload_t *ts_payload; + linked_list_t *proposals = NULL, *tsi = NULL, *tsr = NULL; + linked_list_t *labels_i = NULL, *labels_r = NULL; + proposal_t *proposal = NULL; - /* defaults to TUNNEL mode */ - this->mode = MODE_TUNNEL; + if (!this->mode) + { + /* defaults to TUNNEL mode */ + this->mode = MODE_TUNNEL; + } enumerator = message->create_payload_enumerator(message); while (enumerator->enumerate(enumerator, &payload)) @@ -1218,20 +1255,20 @@ static void process_payloads(private_child_create_t *this, message_t *message) { case PLV2_SECURITY_ASSOCIATION: sa_payload = (sa_payload_t*)payload; - this->proposals = sa_payload->get_proposals(sa_payload); + proposals = sa_payload->get_proposals(sa_payload); break; case PLV2_KEY_EXCHANGE: process_ke_payload(this, (ke_payload_t*)payload); break; case PLV2_TS_INITIATOR: ts_payload = (ts_payload_t*)payload; - this->tsi = ts_payload->get_traffic_selectors(ts_payload); - this->labels_i = ts_payload->get_sec_labels(ts_payload); + tsi = ts_payload->get_traffic_selectors(ts_payload); + labels_i = ts_payload->get_sec_labels(ts_payload); break; case PLV2_TS_RESPONDER: ts_payload = (ts_payload_t*)payload; - this->tsr = ts_payload->get_traffic_selectors(ts_payload); - this->labels_r = ts_payload->get_sec_labels(ts_payload); + tsr = ts_payload->get_traffic_selectors(ts_payload); + labels_r = ts_payload->get_sec_labels(ts_payload); break; case PLV2_NOTIFY: handle_notify(this, (notify_payload_t*)payload); @@ -1241,6 +1278,118 @@ static void process_payloads(private_child_create_t *this, message_t *message) } } enumerator->destroy(enumerator); + + if (this->optimized_rekeying) + { + if (this->other_spi) + { + /* when using optimized rekeying, we use the original proposal but + * with the new SPI the peer supplied via notify */ + this->proposals->get_first(this->proposals, (void**)&proposal); + proposal->set_spi(proposal, this->other_spi); + + if (proposals) + { + DBG1(DBG_IKE, "peer sent unexpected SA payload during " + "optimized rekeying, ignored"); + proposals->destroy_offset(proposals, + offsetof(proposal_t, destroy)); + } + if (tsi || tsr || labels_i || labels_r) + { + DBG1(DBG_IKE, "peer sent unexpected TS payload(s) during " + "optimized rekeying, ignored"); + } + DESTROY_OFFSET_IF(tsi, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(tsr, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(labels_i, offsetof(sec_label_t, destroy)); + DESTROY_OFFSET_IF(labels_r, offsetof(sec_label_t, destroy)); + } + else + { + if (this->initiator) + { + DBG1(DBG_IKE, "peer didn't reply with expected %N notify," + "rekeying may fail", notify_type_names, OPTIMIZED_REKEY); + } + else + { + DBG2(DBG_IKE, "peer requested a regular rekeying, even though " + "optimized rekeying is supported"); + } + /* use regular rekeying */ + DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy)); + DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy)); + DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy)); + this->optimized_rekeying = FALSE; + } + } + + if (!this->optimized_rekeying) + { + this->proposals = proposals; + this->tsi = tsi; + this->tsr = tsr; + this->labels_i = labels_i; + this->labels_r = labels_r; + } +} + +/** + * Prepare proposed traffic selectors as initiator. + */ +static void prepare_proposed_ts(private_child_create_t *this) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + linked_list_t *list; + host_t *vip; + + /* check if we want a virtual IP, but don't have one */ + list = linked_list_create(); + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (!this->rekey) + { + enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg); + while (enumerator->enumerate(enumerator, &vip)) + { + /* propose a 0.0.0.0/0 or ::/0 subnet when we use virtual ip */ + vip = host_create_any(vip->get_family(vip)); + list->insert_last(list, vip); + } + enumerator->destroy(enumerator); + } + if (list->get_count(list)) + { + this->tsi = this->config->get_traffic_selectors(this->config, + TRUE, NULL, list, TRUE); + list->destroy_offset(list, offsetof(host_t, destroy)); + } + else + { /* no virtual IPs configured */ + list->destroy(list); + list = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE); + this->tsi = this->config->get_traffic_selectors(this->config, + TRUE, NULL, list, TRUE); + list->destroy(list); + } + list = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE); + this->tsr = this->config->get_traffic_selectors(this->config, + FALSE, NULL, list, TRUE); + list->destroy(list); + + if (this->packet_tsi) + { + this->tsi->insert_first(this->tsi, + this->packet_tsi->clone(this->packet_tsi)); + } + if (this->packet_tsr) + { + this->tsr->insert_first(this->tsr, + this->packet_tsr->clone(this->packet_tsr)); + } } /** @@ -1400,10 +1549,6 @@ METHOD(task_t, build_i_multi_ke, status_t, METHOD(task_t, build_i, status_t, private_child_create_t *this, message_t *message) { - enumerator_t *enumerator; - host_t *vip; - peer_cfg_t *peer_cfg; - linked_list_t *list; bool no_ke = TRUE; switch (message->get_exchange_type(message)) @@ -1439,67 +1584,28 @@ METHOD(task_t, build_i, status_t, return NEED_MORE; } - /* check if we want a virtual IP, but don't have one */ - list = linked_list_create(); - peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); - if (!this->rekey) - { - enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg); - while (enumerator->enumerate(enumerator, &vip)) - { - /* propose a 0.0.0.0/0 or ::/0 subnet when we use virtual ip */ - vip = host_create_any(vip->get_family(vip)); - list->insert_last(list, vip); - } - enumerator->destroy(enumerator); - } - if (list->get_count(list)) - { - this->tsi = this->config->get_traffic_selectors(this->config, - TRUE, NULL, list, TRUE); - list->destroy_offset(list, offsetof(host_t, destroy)); - } - else - { /* no virtual IPs configured */ - list->destroy(list); - list = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE); - this->tsi = this->config->get_traffic_selectors(this->config, - TRUE, NULL, list, TRUE); - list->destroy(list); - } - list = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE); - this->tsr = this->config->get_traffic_selectors(this->config, - FALSE, NULL, list, TRUE); - list->destroy(list); - - if (this->packet_tsi) + if (!this->optimized_rekeying) { - this->tsi->insert_first(this->tsi, - this->packet_tsi->clone(this->packet_tsi)); - } - if (this->packet_tsr) - { - this->tsr->insert_first(this->tsr, - this->packet_tsr->clone(this->packet_tsr)); - } + prepare_proposed_ts(this); - if (!generic_label_only(this) && !this->child.label) - { /* in the simple label mode we propose the configured label as we - * won't have labels from acquires */ - this->child.label = this->config->get_label(this->config); + if (!generic_label_only(this) && !this->child.label) + { /* in the simple label mode we propose the configured label as we + * won't have labels from acquires */ + this->child.label = this->config->get_label(this->config); + if (this->child.label) + { + this->child.label = this->child.label->clone(this->child.label); + } + } if (this->child.label) { - this->child.label = this->child.label->clone(this->child.label); + DBG2(DBG_CFG, "proposing security label '%s'", + this->child.label->get_string(this->child.label)); } - } - if (this->child.label) - { - DBG2(DBG_CFG, "proposing security label '%s'", - this->child.label->get_string(this->child.label)); - } - this->proposals = this->config->get_proposals(this->config, no_ke); - this->mode = this->config->get_mode(this->config); + this->proposals = this->config->get_proposals(this->config, no_ke); + this->mode = this->config->get_mode(this->config); + } this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE); this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE); @@ -1539,8 +1645,11 @@ METHOD(task_t, build_i, status_t, } if (!no_ke && !this->retry) - { /* during a rekeying the method might already be set */ - if (this->ke_method == KE_NONE) + { /* during a rekeying the method is already set if one was negotiated + * before, if not we try to propose one even during rekeying, unless we + * use optimized rekeying of an SA without PFS */ + if (this->ke_method == KE_NONE && + !this->optimized_rekeying) { this->ke_method = this->config->get_algorithm(this->config, KEY_EXCHANGE_METHOD); @@ -1589,13 +1698,16 @@ METHOD(task_t, build_i, status_t, return FAILED; } - this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); - this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); - this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); - this->tsi = NULL; - this->tsr = NULL; - this->proposals = NULL; - + /* keep the proposals and TS during an optimized rekeying */ + if (!this->optimized_rekeying) + { + this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); + this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); + this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); + this->tsi = NULL; + this->tsr = NULL; + this->proposals = NULL; + } return NEED_MORE; } @@ -2037,7 +2149,7 @@ METHOD(task_t, build_r, status_t, return SUCCESS; } - /* check if ike_config_t included non-critical error notifies */ + /* check if ike_config_t task added non-critical error notifies */ enumerator = message->create_payload_enumerator(message); while (enumerator->enumerate(enumerator, &payload)) { @@ -2345,6 +2457,12 @@ METHOD(task_t, process_i, status_t, DBG1(DBG_IKE, "peer didn't accept DH group %N, " "it requested %N", key_exchange_method_names, this->ke_method, key_exchange_method_names, alg); + if (this->optimized_rekeying) + { + DBG1(DBG_IKE, "peer rejected our DH group during " + "optimized rekeying, switch to regular rekeying"); + this->optimized_rekeying = FALSE; + } this->retry = TRUE; this->ke_method = alg; this->child_sa->set_state(this->child_sa, CHILD_RETRYING); @@ -2474,10 +2592,49 @@ METHOD(child_create_t, use_label, void, this->child.label = label ? label->clone(label) : NULL; } -METHOD(child_create_t, use_dh_group, void, - private_child_create_t *this, key_exchange_method_t dh_group) +METHOD(child_create_t, rekey_sa, void, + private_child_create_t *this, child_sa_t *old) { - this->ke_method = dh_group; + proposal_t *proposal; + uint16_t ke_method; + linked_list_t *my_ts, *other_ts; + + proposal = old->get_proposal(old); + if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD, + &ke_method, NULL)) + { /* reuse the KE method negotiated previously */ + this->ke_method = ke_method; + } + if (old->get_optimized_rekey(old) && + this->ike_sa->supports_extension(this->ike_sa, + EXT_OPTIMIZED_REKEY)) + { /* with optimized rekeying we reuse the proposal and TS */ + this->optimized_rekeying = TRUE; + proposal = proposal->clone(proposal, 0); + this->proposals = linked_list_create_with_items(proposal, NULL); + this->proto = old->get_protocol(old); + this->mode = old->get_mode(old); + my_ts = linked_list_create_from_enumerator( + old->create_ts_enumerator(old, TRUE)); + other_ts = linked_list_create_from_enumerator( + old->create_ts_enumerator(old, FALSE)); + if (this->initiator) + { + this->tsi = my_ts->clone_offset(my_ts, + offsetof(traffic_selector_t, clone)); + this->tsr = other_ts->clone_offset(other_ts, + offsetof(traffic_selector_t, clone)); + } + else + { + this->tsi = other_ts->clone_offset(other_ts, + offsetof(traffic_selector_t, clone)); + this->tsr = my_ts->clone_offset(my_ts, + offsetof(traffic_selector_t, clone)); + } + my_ts->destroy(my_ts); + other_ts->destroy(other_ts); + } } METHOD(child_create_t, get_child, child_sa_t*, @@ -2525,21 +2682,18 @@ METHOD(task_t, migrate, void, chunk_free(&this->my_nonce); chunk_free(&this->other_nonce); chunk_free(&this->link); - if (this->tsr) - { - this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); - } - if (this->tsi) - { - this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); - } - if (this->labels_i) - { - this->labels_i->destroy_offset(this->labels_i, offsetof(sec_label_t, destroy)); - } - if (this->labels_r) - { - this->labels_r->destroy_offset(this->labels_r, offsetof(sec_label_t, destroy)); + if (!this->optimized_rekeying) + { + DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy)); + DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy)); + this->tsi = NULL; + this->tsr = NULL; + DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy)); + this->proposals = NULL; + this->proto = PROTO_NONE; + this->mode = MODE_TUNNEL; } DESTROY_IF(this->child_sa); DESTROY_IF(this->proposal); @@ -2547,10 +2701,6 @@ METHOD(task_t, migrate, void, DESTROY_IF(this->ke); this->ke_failed = FALSE; clear_key_exchanges(this); - if (this->proposals) - { - this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); - } if (!this->rekey && !this->retry) { this->ke_method = KE_NONE; @@ -2558,13 +2708,9 @@ METHOD(task_t, migrate, void, this->ike_sa = ike_sa; this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa); this->proposal = NULL; - this->proposals = NULL; - this->tsi = NULL; - this->tsr = NULL; this->ke = NULL; this->nonceg = NULL; this->child_sa = NULL; - this->mode = MODE_TUNNEL; this->ipcomp = IPCOMP_NONE; this->ipcomp_received = IPCOMP_NONE; this->other_cpi = 0; @@ -2579,22 +2725,10 @@ METHOD(task_t, destroy, void, chunk_free(&this->my_nonce); chunk_free(&this->other_nonce); chunk_free(&this->link); - if (this->tsr) - { - this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); - } - if (this->tsi) - { - this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); - } - if (this->labels_i) - { - this->labels_i->destroy_offset(this->labels_i, offsetof(sec_label_t, destroy)); - } - if (this->labels_r) - { - this->labels_r->destroy_offset(this->labels_r, offsetof(sec_label_t, destroy)); - } + DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy)); + DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy)); if (!this->established) { DESTROY_IF(this->child_sa); @@ -2604,10 +2738,7 @@ METHOD(task_t, destroy, void, DESTROY_IF(this->proposal); DESTROY_IF(this->ke); clear_key_exchanges(this); - if (this->proposals) - { - this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); - } + DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy)); DESTROY_IF(this->config); DESTROY_IF(this->nonceg); DESTROY_IF(this->child.label); @@ -2633,7 +2764,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, .use_marks = _use_marks, .use_if_ids = _use_if_ids, .use_label = _use_label, - .use_dh_group = _use_dh_group, + .rekey_sa = _rekey_sa, .task = { .get_type = _get_type, .migrate = _migrate, diff --git a/src/libcharon/sa/ikev2/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index f1ecb5e070..7ebc5a3ddf 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2019 Tobias Brunner + * Copyright (C) 2018-2023 Tobias Brunner * Copyright (C) 2007 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -77,13 +77,12 @@ struct child_create_t { void (*use_label)(child_create_t *this, sec_label_t *label); /** - * Initially propose a specific DH group to override configuration. + * Use data from the given old SA (e.g. KE method) and possibly use + * optimized rekeying if supported by the peer and CHILD_SA. * - * This is used during rekeying to prefer the previously negotiated group. - * - * @param dh_group DH group to use + * @param old old CHILD_SA that is getting rekeyed */ - void (*use_dh_group)(child_create_t *this, key_exchange_method_t dh_group); + void (*rekey_sa)(child_create_t *this, child_sa_t *old); /** * Get the lower of the two nonces, used for rekey collisions. diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index b0f5f79005..91947cde86 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2022 Tobias Brunner + * Copyright (C) 2009-2023 Tobias Brunner * Copyright (C) 2005-2007 Martin Willi * Copyright (C) 2005 Jan Hutter * @@ -292,20 +292,13 @@ METHOD(task_t, build_i, status_t, if (!this->child_create) { child_cfg_t *config; - proposal_t *proposal; - uint16_t dh_group; uint32_t reqid; config = this->child_sa->get_config(this->child_sa); this->child_create = child_create_create(this->ike_sa, config->get_ref(config), TRUE, NULL, NULL); + this->child_create->rekey_sa(this->child_create, this->child_sa); - proposal = this->child_sa->get_proposal(this->child_sa); - if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD, - &dh_group, NULL)) - { /* reuse the DH group negotiated previously */ - this->child_create->use_dh_group(this->child_create, dh_group); - } reqid = this->child_sa->get_reqid(this->child_sa); this->child_create->use_reqid(this->child_create, reqid); this->child_create->use_marks(this->child_create, @@ -376,11 +369,35 @@ static void find_child(private_child_rekey_t *this, message_t *message) METHOD(task_t, process_r, status_t, private_child_rekey_t *this, message_t *message) { - /* let the CHILD_CREATE task process the message */ - this->child_create->task.process(&this->child_create->task, message); + child_cfg_t *config; + uint32_t reqid; find_child(this, message); + if (this->child_sa) + { + /* configure everything during the first request of a rekeying */ + if (message->get_exchange_type(message) == CREATE_CHILD_SA) + { + reqid = this->child_sa->get_reqid(this->child_sa); + this->child_create->use_reqid(this->child_create, reqid); + this->child_create->use_marks(this->child_create, + this->child_sa->get_mark(this->child_sa, TRUE).value, + this->child_sa->get_mark(this->child_sa, FALSE).value); + this->child_create->use_if_ids(this->child_create, + this->child_sa->get_if_id(this->child_sa, TRUE), + this->child_sa->get_if_id(this->child_sa, FALSE)); + this->child_create->use_label(this->child_create, + this->child_sa->get_label(this->child_sa)); + config = this->child_sa->get_config(this->child_sa); + this->child_create->set_config(this->child_create, + config->get_ref(config)); + this->child_create->rekey_sa(this->child_create, this->child_sa); + } + + /* let the CHILD_CREATE task process the message */ + this->child_create->task.process(&this->child_create->task, message); + } return NEED_MORE; } @@ -420,10 +437,8 @@ static bool actively_rekeying(private_child_rekey_t *this, bool *followup_sent) METHOD(task_t, build_r, status_t, private_child_rekey_t *this, message_t *message) { - child_cfg_t *config; child_sa_t *child_sa; child_sa_state_t state = CHILD_INSTALLED; - uint32_t reqid; bool is_collision, followup_sent = FALSE; if (!this->child_sa) @@ -450,19 +465,6 @@ METHOD(task_t, build_r, status_t, if (message->get_exchange_type(message) == CREATE_CHILD_SA) { - reqid = this->child_sa->get_reqid(this->child_sa); - this->child_create->use_reqid(this->child_create, reqid); - this->child_create->use_marks(this->child_create, - this->child_sa->get_mark(this->child_sa, TRUE).value, - this->child_sa->get_mark(this->child_sa, FALSE).value); - this->child_create->use_if_ids(this->child_create, - this->child_sa->get_if_id(this->child_sa, TRUE), - this->child_sa->get_if_id(this->child_sa, FALSE)); - this->child_create->use_label(this->child_create, - this->child_sa->get_label(this->child_sa)); - config = this->child_sa->get_config(this->child_sa); - this->child_create->set_config(this->child_create, - config->get_ref(config)); state = this->child_sa->get_state(this->child_sa); this->child_sa->set_state(this->child_sa, CHILD_REKEYING); } diff --git a/src/libcharon/tests/exchange_tests.h b/src/libcharon/tests/exchange_tests.h index f84b145496..e43a32775d 100644 --- a/src/libcharon/tests/exchange_tests.h +++ b/src/libcharon/tests/exchange_tests.h @@ -19,5 +19,5 @@ TEST_SUITE(ike_mid_sync_suite_create) TEST_SUITE(ike_rekey_suite_create) TEST_SUITE(child_create_suite_create) TEST_SUITE(child_delete_suite_create) -TEST_SUITE(child_rekey_suite_create) TEST_SUITE(childless_suite_create) +TEST_SUITE(child_rekey_suite_create) diff --git a/src/libcharon/tests/suites/test_child_rekey.c b/src/libcharon/tests/suites/test_child_rekey.c index bf37cfb0e6..920d3e7ef4 100644 --- a/src/libcharon/tests/suites/test_child_rekey.c +++ b/src/libcharon/tests/suites/test_child_rekey.c @@ -81,6 +81,10 @@ START_TEST(test_regular) /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */ assert_hook_not_called(child_rekey); assert_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); exchange_test_helper->process_message(exchange_test_helper, b, NULL); assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED); assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED); @@ -90,6 +94,10 @@ START_TEST(test_regular) /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */ assert_hook_rekey(child_rekey, spi_a, 3); assert_no_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); exchange_test_helper->process_message(exchange_test_helper, a, NULL); assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE); assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); @@ -178,6 +186,11 @@ START_TEST(test_regular_multi_ke) /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, KEi, TSi, TSr } --> */ assert_hook_not_called(child_rekey); assert_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); exchange_test_helper->process_message(exchange_test_helper, b, NULL); assert_child_sa_state(b, spi_b, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED); assert_ipsec_sas_installed(b, spi_a, spi_b); @@ -186,6 +199,12 @@ START_TEST(test_regular_multi_ke) /* <-- CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } */ assert_hook_not_called(child_rekey); assert_no_notify(IN, REKEY_SA); + assert_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); exchange_test_helper->process_message(exchange_test_helper, a, NULL); assert_child_sa_state(a, spi_a, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED); assert_ipsec_sas_installed(a, spi_a, spi_b); @@ -193,8 +212,12 @@ START_TEST(test_regular_multi_ke) /* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */ assert_hook_not_called(child_rekey); - assert_payload(IN, PLV2_KEY_EXCHANGE); assert_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_no_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); exchange_test_helper->process_message(exchange_test_helper, b, NULL); assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED); assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED); @@ -203,8 +226,12 @@ START_TEST(test_regular_multi_ke) /* <-- IKE_FOLLOWUP_KE { KEr } */ assert_hook_rekey(child_rekey, spi_a, 3); - assert_payload(IN, PLV2_KEY_EXCHANGE); assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_no_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); exchange_test_helper->process_message(exchange_test_helper, a, NULL); assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE); assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); @@ -1089,6 +1116,485 @@ START_TEST(test_regular_responder_incorrect_delete) } END_TEST +/** + * Regular and then optimized CHILD_SA rekey either initiated by the original + * initiator or responder of the IKE_SA. + */ +START_TEST(test_optimized) +{ + ike_sa_t *a, *b; + uint32_t spi_a = _i+1, spi_b = 2-_i; + + assert_track_sas_start(); + + if (_i) + { /* responder rekeys the CHILD_SA (SPI 2) */ + exchange_test_helper->establish_sa(exchange_test_helper, + &b, &a, NULL); + } + else + { /* initiator rekeys the CHILD_SA (SPI 1) */ + exchange_test_helper->establish_sa(exchange_test_helper, + &a, &b, NULL); + } + initiate_rekey(a, spi_a); + assert_ipsec_sas_installed(a, spi_a, spi_b); + + /* this should never get called as this results in a successful rekeying */ + assert_hook_not_called(child_updown); + + /* the CHILD_SA created with IKE_AUTH has to be rekeyed regularly as we + * don't know if we use PFS or not */ + + /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */ + assert_hook_not_called(child_rekey); + assert_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED); + assert_ipsec_sas_installed(b, spi_a, spi_b, 4); + assert_hook(); + + /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */ + assert_hook_rekey(child_rekey, spi_a, 3); + assert_no_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(a, spi_a, 3, 4); + assert_hook(); + + /* INFORMATIONAL { D } --> */ + assert_hook_rekey(child_rekey, spi_b, 4); + assert_jobs_scheduled(1); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, spi_b, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_count(b, 2); + assert_ipsec_sas_installed(b, spi_b, 3, 4); + assert_scheduler(); + assert_hook(); + /* <-- INFORMATIONAL { D } */ + assert_hook_not_called(child_rekey); + assert_jobs_scheduled(1); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, spi_a, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_count(a, 2); + assert_ipsec_sas_installed(a, spi_a, 3, 4); + assert_scheduler(); + assert_hook(); + + /* simulate the execution of the scheduled jobs */ + destroy_rekeyed(a, spi_a); + assert_child_sa_count(a, 1); + assert_ipsec_sas_installed(a, 3, 4); + destroy_rekeyed(b, spi_b); + assert_child_sa_count(b, 1); + assert_ipsec_sas_installed(a, 3, 4); + + /* child_updown */ + assert_hook(); + + /* after the first rekeying the CHILD_SA can be rekeyed optimized */ + initiate_rekey(a, 3); + /* this should never get called as this results in a successful rekeying */ + assert_hook_not_called(child_updown); + + /* CREATE_CHILD_SA { N(REKEY_SA), N(OPT_REKEY), Ni, [KEi] } --> */ + assert_hook_not_called(child_rekey); + assert_notify(IN, REKEY_SA); + assert_notify(IN, OPTIMIZED_REKEY); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, 4, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_state(b, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED); + assert_ipsec_sas_installed(b, 3, 4, 6); + assert_hook(); + + /* <-- CREATE_CHILD_SA { N(OPT_REKEY), Nr, [KEr] } */ + assert_hook_called(child_rekey); + assert_no_notify(IN, REKEY_SA); + assert_notify(IN, OPTIMIZED_REKEY); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, 3, CHILD_DELETING, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(a, 3, 5, 6); + assert_hook(); + + /* INFORMATIONAL { D } --> */ + assert_hook_called(child_rekey); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, 4, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(b, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_count(b, 2); + assert_ipsec_sas_installed(b, 4, 5, 6); + assert_hook(); + + /* <-- INFORMATIONAL { D } */ + assert_hook_not_called(child_rekey); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, 3, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 5, CHILD_INSTALLED); + assert_child_sa_count(a, 2); + assert_ipsec_sas_installed(a, 3, 5, 6); + assert_hook(); + + /* simulate the execution of the scheduled jobs */ + destroy_rekeyed(a, 3); + assert_child_sa_count(a, 1); + assert_ipsec_sas_installed(a, 5, 6); + destroy_rekeyed(b, 4); + assert_child_sa_count(b, 1); + assert_ipsec_sas_installed(b, 5, 6); + + /* child_updown */ + assert_hook(); + assert_track_sas(2, 2); + + call_ikesa(a, destroy); + call_ikesa(b, destroy); +} +END_TEST + +/** + * Optimized rekey of a CHILD_SA created with a CREATE_CHILD_SA exchange, either + * initiated by the original initiator or responder of the IKE_SA. + */ +START_TEST(test_optimized_childless) +{ + exchange_test_sa_conf_t conf = { + .initiator = { + .childless = CHILDLESS_FORCE, + }, + }; + ike_sa_t *init, *resp, *a, *b; + uint32_t spi_a = _i+1, spi_b = 2-_i; + + assert_track_sas_start(); + + exchange_test_helper->establish_sa(exchange_test_helper, + &init, &resp, &conf); + if (_i) + { /* responder rekeys the CHILD_SA (SPI 2) */ + a = resp; + b = init; + } + else + { /* initiator rekeys the CHILD_SA (SPI 1) */ + a = init; + b = resp; + } + assert_child_sa_count(init, 0); + + /* CREATE_CHILD_SA { SA, Ni, [KEi,] TSi, TSr } --> */ + assert_hook_called(child_updown); + assert_no_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, resp, NULL); + assert_hook(); + + /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */ + assert_hook_called(child_updown); + assert_no_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, init, NULL); + assert_hook(); + + assert_child_sa_state(a, spi_a, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(a, spi_a, spi_b); + assert_child_sa_state(b, spi_b, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(b, spi_a, spi_b); + + initiate_rekey(a, spi_a); + assert_ipsec_sas_installed(a, spi_a, spi_b); + + /* this should never get called as this results in a successful rekeying */ + assert_hook_not_called(child_updown); + + /* since the CHILD_SA was created with a CREATE_CHILD_SA exchange, we can + * use optimized rekeying*/ + + /* CREATE_CHILD_SA { N(REKEY_SA), N(OPT_REKEY), Ni, [KEi] } --> */ + assert_hook_not_called(child_rekey); + assert_notify(IN, REKEY_SA); + assert_notify(IN, OPTIMIZED_REKEY); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED); + assert_ipsec_sas_installed(b, spi_a, spi_b, 4); + assert_hook(); + + /* <-- CREATE_CHILD_SA { N(OPT_REKEY), Nr, [KEr] } */ + assert_hook_called(child_rekey); + assert_no_notify(IN, REKEY_SA); + assert_notify(IN, OPTIMIZED_REKEY); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(a, spi_a, 3, 4); + assert_hook(); + + /* INFORMATIONAL { D } --> */ + assert_hook_rekey(child_rekey, spi_b, 4); + assert_jobs_scheduled(1); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, spi_b, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_count(b, 2); + assert_ipsec_sas_installed(b, spi_b, 3, 4); + assert_scheduler(); + assert_hook(); + /* <-- INFORMATIONAL { D } */ + assert_hook_not_called(child_rekey); + assert_jobs_scheduled(1); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, spi_a, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_count(a, 2); + assert_ipsec_sas_installed(a, spi_a, 3, 4); + assert_scheduler(); + assert_hook(); + + /* simulate the execution of the scheduled jobs */ + destroy_rekeyed(a, spi_a); + assert_child_sa_count(a, 1); + assert_ipsec_sas_installed(a, 3, 4); + destroy_rekeyed(b, spi_b); + assert_child_sa_count(b, 1); + assert_ipsec_sas_installed(a, 3, 4); + + /* child_updown */ + assert_hook(); + assert_track_sas(2, 2); + + call_ikesa(a, destroy); + call_ikesa(b, destroy); +} +END_TEST + +/** + * Optimized CHILD_SA rekey either initiated by the original initiator or + * responder of the IKE_SA. + */ +START_TEST(test_optimized_multi_ke) +{ + exchange_test_sa_conf_t conf = multi_ke_conf; + ike_sa_t *init, *resp, *a, *b; + uint32_t spi_a = _i+1, spi_b = 2-_i; + + assert_track_sas_start(); + + /* use childless initiation so we can use optimized rekeying */ + conf.initiator.childless = CHILDLESS_FORCE; + exchange_test_helper->establish_sa(exchange_test_helper, + &init, &resp, &conf); + if (_i) + { /* responder rekeys the CHILD_SA (SPI 2) */ + a = resp; + b = init; + } + else + { /* initiator rekeys the CHILD_SA (SPI 1) */ + a = init; + b = resp; + } + assert_child_sa_count(init, 0); + + /* CREATE_CHILD_SA { SA, Ni, KEi, TSi, TSr } --> */ + assert_hook_not_called(child_updown); + assert_no_notify(IN, REKEY_SA); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, resp, NULL); + assert_hook(); + + /* <-- CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } */ + assert_hook_not_called(child_updown); + assert_no_notify(IN, REKEY_SA); + assert_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_payload(IN, PLV2_TS_INITIATOR); + assert_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, init, NULL); + assert_hook(); + + /* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */ + assert_hook_called(child_updown); + assert_no_notify(IN, REKEY_SA); + assert_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_no_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, resp, NULL); + assert_hook(); + + /* <-- IKE_FOLLOWUP_KE { KEr } */ + assert_hook_called(child_updown); + assert_no_notify(IN, REKEY_SA); + assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_no_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, init, NULL); + assert_hook(); + + assert_child_sa_state(a, spi_a, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(a, spi_a, spi_b); + assert_child_sa_state(b, spi_b, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(b, spi_a, spi_b); + + initiate_rekey(a, spi_a); + assert_ipsec_sas_installed(a, spi_a, spi_b); + + /* this should never get called as this results in a successful rekeying */ + assert_hook_not_called(child_updown); + + /* CREATE_CHILD_SA { N(REKEY_SA), N(OPT_REKEY), Ni, KEi } --> */ + assert_hook_not_called(child_rekey); + assert_notify(IN, REKEY_SA); + assert_notify(IN, OPTIMIZED_REKEY); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, spi_b, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(b, spi_a, spi_b); + assert_hook(); + + /* <-- CREATE_CHILD_SA { N(OPT_REKEY), Nr, KEr, N(ADD_KE) } */ + assert_hook_not_called(child_rekey); + assert_no_notify(IN, REKEY_SA); + assert_notify(IN, OPTIMIZED_REKEY); + assert_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, spi_a, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(a, spi_a, spi_b); + assert_hook(); + + /* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */ + assert_hook_not_called(child_rekey); + assert_no_notify(IN, REKEY_SA); + assert_no_notify(IN, OPTIMIZED_REKEY); + assert_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_no_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED); + assert_ipsec_sas_installed(b, spi_a, spi_b, 4); + assert_hook(); + + /* <-- IKE_FOLLOWUP_KE { KEr } */ + assert_hook_rekey(child_rekey, spi_a, 3); + assert_no_notify(IN, REKEY_SA); + assert_no_notify(IN, OPTIMIZED_REKEY); + assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION); + assert_no_payload(IN, PLV2_NONCE); + assert_payload(IN, PLV2_KEY_EXCHANGE); + assert_no_payload(IN, PLV2_TS_INITIATOR); + assert_no_payload(IN, PLV2_TS_RESPONDER); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_ipsec_sas_installed(a, spi_a, 3, 4); + assert_hook(); + + /* INFORMATIONAL { D } --> */ + assert_hook_rekey(child_rekey, spi_b, 4); + assert_jobs_scheduled(1); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, b, NULL); + assert_child_sa_state(b, spi_b, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_count(b, 2); + assert_ipsec_sas_installed(b, spi_b, 3, 4); + assert_scheduler(); + assert_hook(); + /* <-- INFORMATIONAL { D } */ + assert_hook_not_called(child_rekey); + assert_jobs_scheduled(1); + assert_single_payload(IN, PLV2_DELETE); + exchange_test_helper->process_message(exchange_test_helper, a, NULL); + assert_child_sa_state(a, spi_a, CHILD_DELETED, CHILD_OUTBOUND_NONE); + assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED); + assert_child_sa_count(a, 2); + assert_ipsec_sas_installed(a, spi_a, 3, 4); + assert_scheduler(); + assert_hook(); + + /* simulate the execution of the scheduled jobs */ + destroy_rekeyed(a, spi_a); + assert_child_sa_count(a, 1); + assert_ipsec_sas_installed(a, 3, 4); + destroy_rekeyed(b, spi_b); + assert_child_sa_count(b, 1); + assert_ipsec_sas_installed(a, 3, 4); + + /* child_updown */ + assert_hook(); + assert_track_sas(2, 2); + + call_ikesa(a, destroy); + call_ikesa(b, destroy); +} +END_TEST + /** * Both peers initiate the CHILD_SA rekeying concurrently and should handle * the collision properly depending on the nonces. @@ -4351,6 +4857,20 @@ START_TEST(test_collision_ike_delete) } END_TEST +START_SETUP(disable_optimized_rekey) +{ + lib->settings->set_bool(lib->settings, "%s.optimized_rekeying", + FALSE, lib->ns); +} +END_SETUP + +START_TEARDOWN(enable_optimized_rekey) +{ + lib->settings->set_bool(lib->settings, "%s.optimized_rekeying", + TRUE, lib->ns); +} +END_TEARDOWN + Suite *child_rekey_suite_create() { Suite *s; @@ -4359,6 +4879,7 @@ Suite *child_rekey_suite_create() s = suite_create("child rekey"); tc = tcase_create("regular"); + tcase_add_checked_fixture(tc, disable_optimized_rekey, enable_optimized_rekey); tcase_add_loop_test(tc, test_regular, 0, 2); tcase_add_loop_test(tc, test_regular_multi_ke, 0, 2); tcase_add_loop_test(tc, test_regular_ke_invalid, 0, 2); @@ -4370,7 +4891,14 @@ Suite *child_rekey_suite_create() tcase_add_test(tc, test_regular_responder_incorrect_delete); suite_add_tcase(s, tc); + tc = tcase_create("optimized"); + tcase_add_loop_test(tc, test_optimized, 0, 2); + tcase_add_loop_test(tc, test_optimized_childless, 0, 2); + tcase_add_loop_test(tc, test_optimized_multi_ke, 0, 2); + suite_add_tcase(s, tc); + tc = tcase_create("collisions rekey"); + tcase_add_checked_fixture(tc, disable_optimized_rekey, enable_optimized_rekey); tcase_add_loop_test(tc, test_collision, 0, 4); tcase_add_loop_test(tc, test_collision_multi_ke, 0, 4); tcase_add_loop_test(tc, test_collision_mixed, 0, 4);