/*
- * Copyright (C) 2008-2020 Tobias Brunner
+ * Copyright (C) 2008-2023 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
* Whether to follow IKEv2 redirects as per RFC 5685
*/
bool follow_redirects;
+
+ /**
+ * Whether to use optimized rekeying
+ */
+ bool optimized_rekeying;
};
/**
}
/**
- * build the payloads for the message
+ * Add an SA payload to the message, either generated from configured proposals
+ * or returning the selected proposal.
+ *
+ * The optional SPI of the new SA is encoded during a rekeying.
+ *
+ * Returns TRUE if additional KEs are proposed.
*/
-static bool build_payloads(private_ike_init_t *this, message_t *message)
+static bool build_sa_payload(private_ike_init_t *this, ike_cfg_t *ike_cfg,
+ uint64_t new_spi, message_t *message)
{
sa_payload_t *sa_payload;
- ke_payload_t *ke_payload;
- nonce_payload_t *nonce_payload;
linked_list_t *proposal_list, *other_dh_groups;
- ike_sa_id_t *id;
- proposal_t *proposal;
enumerator_t *enumerator;
- ike_cfg_t *ike_cfg;
+ proposal_t *proposal;
bool additional_ke = FALSE;
- id = this->ike_sa->get_id(this->ike_sa);
-
- ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
-
if (this->initiator)
{
proposal_list = ike_cfg->get_proposals(ike_cfg);
enumerator = proposal_list->create_enumerator(proposal_list);
while (enumerator->enumerate(enumerator, (void**)&proposal))
{
- /* include SPI of new IKE_SA when we are rekeying */
- if (this->old_sa)
- {
- proposal->set_spi(proposal, id->get_initiator_spi(id));
- }
+ proposal->set_spi(proposal, new_spi);
/* move the selected DH group to the front of the proposal */
if (!proposal->promote_transform(proposal, KEY_EXCHANGE_METHOD,
this->ke_method))
}
else
{
- if (this->old_sa)
- {
- /* include SPI of new IKE_SA when we are rekeying */
- this->proposal->set_spi(this->proposal, id->get_responder_spi(id));
- }
+ this->proposal->set_spi(this->proposal, new_spi);
sa_payload = sa_payload_create_from_proposal_v2(this->proposal);
additional_ke = proposal_has_additional_ke(this->proposal);
}
message->add_payload(message, (payload_t*)sa_payload);
+ return additional_ke;
+}
+
+/**
+ * build the payloads for the message
+ */
+static bool build_payloads(private_ike_init_t *this, message_t *message)
+{
+ ke_payload_t *ke_payload;
+ notify_payload_t *notify;
+ nonce_payload_t *nonce_payload;
+ ike_sa_id_t *id;
+ ike_cfg_t *ike_cfg;
+ uint64_t my_new_spi = 0;
+ bool additional_ke = FALSE;
+
+ id = this->ike_sa->get_id(this->ike_sa);
+ ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
+
+ if (this->old_sa)
+ {
+ my_new_spi = this->initiator ? id->get_initiator_spi(id)
+ : id->get_responder_spi(id);
+ }
+
+ if (this->optimized_rekeying)
+ {
+ notify = notify_payload_create_from_protocol_and_type(PLV2_NOTIFY,
+ PROTO_IKE, OPTIMIZED_REKEY);
+ notify->set_ike_spi(notify, my_new_spi);
+ message->add_payload(message, (payload_t*)notify);
+ }
+ else
+ {
+ additional_ke = build_sa_payload(this, ike_cfg, my_new_spi, message);
+ }
ke_payload = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE,
this->ke);
payload_t *payload;
ike_sa_id_t *id;
ke_payload_t *ke_pld = NULL;
+ uint64_t new_spi = 0;
enumerator = message->create_payload_enumerator(message);
while (enumerator->enumerate(enumerator, &payload))
switch (notify->get_notify_type(notify))
{
+ case OPTIMIZED_REKEY:
+ if (this->optimized_rekeying)
+ {
+ new_spi = notify->get_ike_spi(notify);
+ if (!new_spi)
+ {
+ DBG1(DBG_IKE, "received invalid %N notify, "
+ "ignored", notify_type_names,
+ OPTIMIZED_REKEY);
+ }
+ }
+ break;
case FRAGMENTATION_SUPPORTED:
this->ike_sa->enable_extension(this->ike_sa,
EXT_IKE_FRAGMENTATION);
}
enumerator->destroy(enumerator);
+ if (this->optimized_rekeying)
+ {
+ if (new_spi)
+ {
+ /* when using optimized rekeying, we use the original proposal but
+ * with the new SPI the peer supplied via notify */
+ if (this->proposal)
+ {
+ DBG1(DBG_IKE, "peer sent unexpected SA payload during "
+ "optimized rekeying, ignored");
+ this->proposal->destroy(this->proposal);
+ }
+ this->proposal = this->old_sa->get_proposal(this->old_sa);
+ this->proposal = this->proposal->clone(this->proposal, 0);
+ this->proposal->set_spi(this->proposal, new_spi);
+ }
+ 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");
+ }
+ this->optimized_rekeying = FALSE;
+ }
+ }
+
if (this->proposal)
{
this->ike_sa->set_proposal(this->ike_sa, this->proposal);
if (!this->ke)
{
if (this->old_sa &&
- lib->settings->get_bool(lib->settings,
- "%s.prefer_previous_dh_group", TRUE, lib->ns))
+ this->old_sa->supports_extension(this->old_sa,
+ EXT_OPTIMIZED_REKEY))
+ {
+ this->optimized_rekeying = TRUE;
+ }
+ if (this->old_sa &&
+ (this->optimized_rekeying ||
+ lib->settings->get_bool(lib->settings, "%s.prefer_previous_dh_group",
+ TRUE, lib->ns)))
{ /* reuse the DH group we used for the old IKE_SA when rekeying */
proposal_t *proposal;
uint16_t dh_group;
}
else if (this->ke->get_method(this->ke) != this->ke_method)
{ /* reset DH instance if group changed (INVALID_KE_PAYLOAD) */
+ if (this->optimized_rekeying)
+ {
+ DBG1(DBG_IKE, "peer rejected our DH group during optimized "
+ "rekeying, switch to regular rekeying");
+ this->optimized_rekeying = FALSE;
+ }
this->ke->destroy(this->ke);
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
this->ke_method);
}
#endif /* ME */
+ if (this->old_sa &&
+ this->old_sa->supports_extension(this->old_sa, EXT_OPTIMIZED_REKEY))
+ { /* we expect an optimized rekeying if both peers support it */
+ this->optimized_rekeying = TRUE;
+ }
+
process_payloads(this, message);
return NEED_MORE;
/* CREATE_CHILD_SA { SA, Ni, KEi } --> */
assert_hook_rekey(ike_rekey, 1, 3);
assert_no_notify(IN, REKEY_SA);
+ assert_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_payload(IN, PLV2_NONCE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ike_sa_state(b, IKE_REKEYED);
assert_child_sa_count(b, 0);
/* <-- CREATE_CHILD_SA { SA, Nr, KEr } */
assert_hook_rekey(ike_rekey, 1, 3);
assert_no_notify(IN, REKEY_SA);
+ assert_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_payload(IN, PLV2_NONCE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_ike_sa_state(a, IKE_DELETING);
assert_child_sa_count(a, 0);
/* CREATE_CHILD_SA { SA, Ni, KEi } --> */
assert_hook_not_called(ike_rekey);
assert_no_notify(IN, REKEY_SA);
+ assert_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_payload(IN, PLV2_NONCE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ike_sa_state(b, IKE_REKEYING);
assert_child_sa_count(b, 1);
/* <-- CREATE_CHILD_SA { SA, Nr, KEr, N(ADD_KE) } */
assert_hook_not_called(ike_rekey);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ assert_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_payload(IN, PLV2_NONCE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_ike_sa_state(a, IKE_REKEYING);
assert_child_sa_count(a, 1);
/* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
assert_hook_rekey(ike_rekey, 1, 3);
assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_no_payload(IN, PLV2_NONCE);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ike_sa_state(b, IKE_REKEYED);
/* <-- IKE_FOLLOWUP_KE { KEr } */
assert_hook_rekey(ike_rekey, 1, 3);
assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_no_payload(IN, PLV2_NONCE);
assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_ike_sa_state(a, IKE_DELETING);
}
END_TEST
+/**
+ * Optimized IKE_SA rekeying either initiated by the original initiator or
+ * responder of the IKE_SA.
+ */
+START_TEST(test_optimized)
+{
+ ike_sa_t *a, *b, *new_sa;
+ status_t s;
+
+ assert_track_sas_start();
+
+ if (_i)
+ { /* responder rekeys the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, NULL);
+ }
+ else
+ { /* initiator rekeys the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ }
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ initiate_rekey(a);
+
+ /* CREATE_CHILD_SA { N(OPT_REKEY), Ni, KEi } --> */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ assert_notify(IN, OPTIMIZED_REKEY);
+ assert_no_notify(IN, REKEY_SA);
+ assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_payload(IN, PLV2_NONCE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_REKEYED);
+ assert_child_sa_count(b, 0);
+ new_sa = assert_ike_sa_checkout(3, 4, FALSE);
+ assert_ike_sa_state(new_sa, IKE_ESTABLISHED);
+ assert_child_sa_count(new_sa, 1);
+ assert_ike_sa_count(1);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(OPT_REKEY), Nr, KEr } */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ assert_notify(IN, OPTIMIZED_REKEY);
+ assert_no_notify(IN, REKEY_SA);
+ assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_payload(IN, PLV2_NONCE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_ike_sa_state(a, IKE_DELETING);
+ assert_child_sa_count(a, 0);
+ new_sa = assert_ike_sa_checkout(3, 4, TRUE);
+ assert_ike_sa_state(new_sa, IKE_ESTABLISHED);
+ assert_child_sa_count(new_sa, 1);
+ assert_ike_sa_count(2);
+ assert_hook();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(ike_rekey);
+
+ /* INFORMATIONAL { D } --> */
+ assert_single_payload(IN, PLV2_DELETE);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ /* <-- INFORMATIONAL { } */
+ assert_message_empty(IN);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+
+ /* ike_rekey/ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+ assert_hook();
+ assert_track_sas(2, 2);
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * Optimized IKE_SA rekeying with multiple key exchanges either initiated by the
+ * original initiator or responder of the IKE_SA.
+ */
+START_TEST(test_optimized_multi_ke)
+{
+ ike_sa_t *a, *b, *new_sa;
+ status_t s;
+
+ assert_track_sas_start();
+
+ if (_i)
+ { /* responder rekeys the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, &multi_ke_conf);
+ }
+ else
+ { /* initiator rekeys the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &multi_ke_conf);
+ }
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ initiate_rekey(a);
+
+ /* CREATE_CHILD_SA { N(OPT_REKEY), Ni, KEi } --> */
+ assert_hook_not_called(ike_rekey);
+ assert_notify(IN, OPTIMIZED_REKEY);
+ assert_no_notify(IN, REKEY_SA);
+ assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_payload(IN, PLV2_NONCE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_REKEYING);
+ assert_child_sa_count(b, 1);
+ assert_ike_sa_count(0);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(OPT_REKEY), Nr, KEr, N(ADD_KE) } */
+ assert_hook_not_called(ike_rekey);
+ 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);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_ike_sa_state(a, IKE_REKEYING);
+ assert_child_sa_count(a, 1);
+ assert_ike_sa_count(0);
+ assert_hook();
+
+ /* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ assert_no_notify(IN, OPTIMIZED_REKEY);
+ assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_no_payload(IN, PLV2_NONCE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_REKEYED);
+ assert_child_sa_count(b, 0);
+ new_sa = assert_ike_sa_checkout(3, 4, FALSE);
+ assert_ike_sa_state(new_sa, IKE_ESTABLISHED);
+ assert_child_sa_count(new_sa, 1);
+ assert_ike_sa_count(1);
+ assert_hook();
+
+ /* <-- IKE_FOLLOWUP_KE { KEr } */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ assert_no_notify(IN, OPTIMIZED_REKEY);
+ assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_no_payload(IN, PLV2_SECURITY_ASSOCIATION);
+ assert_no_payload(IN, PLV2_NONCE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_ike_sa_state(a, IKE_DELETING);
+ assert_child_sa_count(a, 0);
+ new_sa = assert_ike_sa_checkout(3, 4, TRUE);
+ assert_ike_sa_state(new_sa, IKE_ESTABLISHED);
+ assert_child_sa_count(new_sa, 1);
+ assert_ike_sa_count(2);
+ assert_hook();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(ike_rekey);
+
+ /* INFORMATIONAL { D } --> */
+ assert_single_payload(IN, PLV2_DELETE);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ /* <-- INFORMATIONAL { } */
+ assert_message_empty(IN);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+
+ /* ike_rekey/ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+ assert_hook();
+ assert_track_sas(2, 2);
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
/**
* Both peers initiate the IKE_SA rekeying concurrently and should handle the
* collision properly depending on the nonces.
}
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 *ike_rekey_suite_create()
{
Suite *s;
s = suite_create("ike 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);
tcase_add_loop_test(tc, test_regular_ke_invalid_multi_ke, 0, 2);
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_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);
suite_add_tcase(s, tc);
tc = tcase_create("collisions delete");
+ tcase_add_checked_fixture(tc, disable_optimized_rekey, enable_optimized_rekey);
tcase_add_loop_test(tc, test_collision_delete, 0, 2);
tcase_add_loop_test(tc, test_collision_delete_multi_ke, 0, 2);
tcase_add_loop_test(tc, test_collision_delete_drop_delete, 0, 2);