*/
bool ke_failed;
+ /**
+ * How to handle key exchange methods
+ */
+ enum {
+ /** Ignore KE methods and don't derive a key */
+ KE_SKIP = 0,
+ /** Negotiate KE methods during the exchange */
+ KE_NEGOTIATE_METHODS = 1,
+ /** Establish a key if KE methods are selected */
+ KE_ESTABLISH_KEY = 2,
+ } ke_handling;
+
/**
* Link value for current key exchange
*/
/**
* Select a proposal
*/
-static bool select_proposal(private_child_create_t *this, bool no_ke)
+static bool select_proposal(private_child_create_t *this)
{
proposal_selection_flag_t flags = 0;
return FALSE;
}
- if (no_ke)
+ if (!(this->ke_handling & KE_NEGOTIATE_METHODS))
{
flags |= PROPOSAL_SKIP_KE;
}
uint16_t alg;
int i = 1;
- if (!this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
- { /* no PFS */
+ if (!(this->ke_handling & KE_ESTABLISH_KEY) ||
+ !this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
+ { /* no key establishment or no PFS */
return;
}
key_exchange_method_t method = this->key_exchanges[this->ke_index].method;
key_exchange_method_t received = ke->get_key_exchange_method(ke);
+ if (!(this->ke_handling & KE_ESTABLISH_KEY))
+ {
+ DBG1(DBG_IKE, "ignore unexpected KE payload with method %N",
+ key_exchange_method_names, received);
+ return;
+ }
+
/* the proposal is selected after processing the KE payload, so this is
* only relevant for additional key exchanges */
if (method && method != received)
{
uint16_t alg;
+ if (!(this->ke_handling & KE_ESTABLISH_KEY))
+ {
+ return TRUE;
+ }
+
if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
this->ke_method))
{
METHOD(task_t, build_i, status_t,
private_child_create_t *this, message_t *message)
{
- bool no_ke = TRUE;
-
switch (message->get_exchange_type(message))
{
case IKE_SA_INIT:
return get_nonce(message, &this->my_nonce);
- case CREATE_CHILD_SA:
- if (!generate_nonce(this))
- {
- message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
- return SUCCESS;
- }
- no_ke = FALSE;
- break;
case IKE_AUTH:
switch (defer_child_sa(this))
{
/* just continue to establish the CHILD_SA */
break;
}
+ /* negotiate KE methods if supported but don't establish a key */
+ if (this->ike_sa->supports_extension(this->ike_sa,
+ EXT_CHILD_SA_PFS_INFO))
+ {
+ this->ke_handling = KE_NEGOTIATE_METHODS;
+ }
/* send only in the first request, not in subsequent rounds */
this->public.task.build = (void*)return_need_more;
break;
+ case CREATE_CHILD_SA:
+ if (!generate_nonce(this))
+ {
+ message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
+ return SUCCESS;
+ }
+ /* establish a key if KE methods are selected */
+ this->ke_handling = KE_NEGOTIATE_METHODS | KE_ESTABLISH_KEY;
+ break;
default:
return NEED_MORE;
}
OPT_PER_CPU_SAS);
}
- this->proposals = this->config->get_proposals(this->config, no_ke);
+ this->proposals = this->config->get_proposals(this->config,
+ !(this->ke_handling & KE_NEGOTIATE_METHODS));
this->mode = this->config->get_mode(this->config);
this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
return FAILED;
}
- if (no_ke)
- { /* we might have one set if we are recreating this SA */
+ if (!(this->ke_handling & KE_NEGOTIATE_METHODS))
+ { /* make sure we don't have one set if we are recreating this SA */
this->ke_method = KE_NONE;
}
else if (!this->retry)
return FAILED;
}
- if (this->ke_method != KE_NONE)
+ if (this->ke_handling & KE_ESTABLISH_KEY && this->ke_method != KE_NONE)
{
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
this->ke_method);
return get_nonce(message, &this->other_nonce);
case CREATE_CHILD_SA:
get_nonce(message, &this->other_nonce);
+ /* establish a key if KE methods are selected */
+ this->ke_handling = KE_NEGOTIATE_METHODS | KE_ESTABLISH_KEY;
break;
case IKE_AUTH:
+ /* negotiate KE methods if supported but don't establish a key */
+ if (this->ike_sa->supports_extension(this->ike_sa,
+ EXT_CHILD_SA_PFS_INFO))
+ {
+ this->ke_handling = KE_NEGOTIATE_METHODS;
+ }
/* only handle first AUTH payload, not additional rounds */
this->public.task.process = (void*)return_need_more;
break;
* as responder.
*/
static bool key_exchange_done_and_install_r(private_child_create_t *this,
- message_t *message, bool ike_auth)
+ message_t *message)
{
bool all_done = FALSE;
handle_child_sa_failure(this, message);
return SUCCESS;
}
- if (!key_exchange_done_and_install_r(this, message, FALSE))
+ if (!key_exchange_done_and_install_r(this, message))
{
return NEED_MORE;
}
{
payload_t *payload;
enumerator_t *enumerator;
- bool no_ke = TRUE, ike_auth = FALSE;
+ bool ike_auth = FALSE;
switch (message->get_exchange_type(message))
{
chunk_empty);
return SUCCESS;
}
- no_ke = FALSE;
break;
case IKE_AUTH:
if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
}
enumerator->destroy(enumerator);
- if (!select_proposal(this, no_ke))
+ if (!select_proposal(this))
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
- if (!key_exchange_done_and_install_r(this, message, ike_auth))
+ if (!key_exchange_done_and_install_r(this, message))
{
this->public.task.build = _build_r_multi_ke;
this->public.task.process = _process_r_multi_ke;
* as initiator.
*/
static status_t key_exchange_done_and_install_i(private_child_create_t *this,
- message_t *message, bool ike_auth)
+ message_t *message)
{
if (key_exchange_done(this))
{
return delete_failed_sa(this);
}
- return key_exchange_done_and_install_i(this, message, FALSE);
+ return key_exchange_done_and_install_i(this, message);
}
METHOD(task_t, process_i, status_t,
{
enumerator_t *enumerator;
payload_t *payload;
- bool no_ke = TRUE, ike_auth = FALSE;
+ bool ike_auth = FALSE;
switch (message->get_exchange_type(message))
{
return get_nonce(message, &this->other_nonce);
case CREATE_CHILD_SA:
get_nonce(message, &this->other_nonce);
- no_ke = FALSE;
break;
case IKE_AUTH:
if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
chunk_t data;
uint16_t alg = KE_NONE;
- if (this->aborted)
+ data = notify->get_notification_data(notify);
+ if (data.len == sizeof(alg))
+ {
+ alg = untoh16(data.ptr);
+ }
+ if (ike_auth)
+ { /* make sure we ignore this notify during IKE_AUTH */
+ DBG1(DBG_IKE, "ignore %N notify with key exchange "
+ "method %N during IKE_AUTH",
+ key_exchange_method_names, alg,
+ notify_type_names, type);
+ break;
+ }
+ else if (this->aborted)
{ /* nothing to do if the task was aborted */
DBG1(DBG_IKE, "received %N notify in aborted %N task",
notify_type_names, type, task_type_names,
enumerator->destroy(enumerator);
return SUCCESS;
}
- data = notify->get_notification_data(notify);
- if (data.len == sizeof(alg))
- {
- alg = untoh16(data.ptr);
- }
- if (this->retry)
+ else if (this->retry)
{
DBG1(DBG_IKE, "already retried with key exchange method "
"%N, ignore requested %N", key_exchange_method_names,
process_payloads(this, message);
- if (!select_proposal(this, no_ke))
+ if (!select_proposal(this))
{
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
this->child_sa->set_per_cpu(this->child_sa, this->child.per_cpu);
}
- if (key_exchange_done_and_install_i(this, message, ike_auth) == NEED_MORE)
+ if (key_exchange_done_and_install_i(this, message) == NEED_MORE)
{
/* if the installation failed, we delete the failed SA, i.e. build() was
* changed, otherwise, we switch to multi-KE mode */
DESTROY_IF(this->nonceg);
DESTROY_IF(this->ke);
this->ke_failed = FALSE;
+ this->ke_handling = KE_SKIP;
clear_key_exchanges(this);
DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy));
if (!this->rekey && !this->retry)
/*
- * Copyright (C) 2016-2020 Tobias Brunner
+ * Copyright (C) 2016-2025 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
#include <tests/utils/job_asserts.h>
#include <tests/utils/sa_asserts.h>
+struct {
+ char *init;
+ char *resp;
+ char *enabled;
+ char *disabled;
+} ike_auth_ke[] = {
+ { "aes128-sha256", "aes128-sha256", "aes128-sha256", "aes128-sha256" },
+ { "aes128-sha256-curve25519", "aes128-sha256-curve25519",
+ "aes128-sha256-curve25519", "aes128-sha256" },
+ { "aes128-sha256-curve25519-none", "aes128-sha256",
+ "aes128-sha256", "aes128-sha256" },
+ { "aes128-sha256", "aes128-sha256-curve25519-none", "aes128-sha256",
+ "aes128-sha256" },
+ { "aes128-sha256-curve25519", "aes128-sha256", NULL, "aes128-sha256" },
+ { "aes128-sha256", "aes128-sha256-curve25519", NULL, "aes128-sha256" },
+};
+
+/**
+ * KE method negotiation during IKE_AUTH, which results in a selected KE method
+ * or a mismatch.
+ */
+START_TEST(test_ike_auth_ke_enabled)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .esp = ike_auth_ke[_i].init,
+ },
+ .responder = {
+ .esp = ike_auth_ke[_i].resp,
+ },
+ };
+ ike_sa_t *a, *b;
+ ike_sa_id_t *id_a, *id_b;
+ child_cfg_t *child_cfg;
+ child_sa_t *child_sa;
+ proposal_t *selected;
+
+ child_cfg = exchange_test_helper->create_sa(exchange_test_helper, &a, &b,
+ &conf);
+ id_a = a->get_id(a);
+ id_b = b->get_id(b);
+
+ call_ikesa(a, initiate, child_cfg, NULL);
+
+ /* IKE_SA_INIT --> */
+ assert_notify(IN, CHILD_SA_PFS_INFO_SUPPORTED);
+ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a));
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ /* <-- IKE_SA_INIT */
+ assert_notify(IN, CHILD_SA_PFS_INFO_SUPPORTED);
+ id_a->set_responder_spi(id_a, id_b->get_responder_spi(id_b));
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+
+ if (!ike_auth_ke[_i].enabled)
+ {
+ /* IKE_AUTH --> */
+ assert_hook_not_called(child_updown);
+ assert_no_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+
+ /* <-- IKE_AUTH */
+ assert_notify(IN, NO_PROPOSAL_CHOSEN);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ else
+ {
+ /* IKE_AUTH --> */
+ assert_hook_called(child_updown);
+ assert_no_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 1);
+ assert_hook();
+
+ /* <-- IKE_AUTH */
+ assert_hook_called(child_updown);
+ assert_no_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+
+ child_sa = a->get_child_sa(a, PROTO_ESP, 1, TRUE);
+ selected = proposal_create_from_string(PROTO_ESP, ike_auth_ke[_i].enabled);
+ ck_assert(selected->equals(selected, child_sa->get_proposal(child_sa)));
+ selected->destroy(selected);
+ }
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * With KE method negotiation during IKE_AUTH disabled, we don't get any KE
+ * methods or mismatches (until the SA is later rekeyed).
+ */
+START_TEST(test_ike_auth_ke_disabled)
+{
+ bool disable_init = _i > countof(ike_auth_ke);
+ _i = _i % countof(ike_auth_ke);
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .esp = ike_auth_ke[_i].init,
+ },
+ .responder = {
+ .esp = ike_auth_ke[_i].resp,
+ },
+ };
+ ike_sa_t *a, *b;
+ ike_sa_id_t *id_a, *id_b;
+ child_cfg_t *child_cfg;
+ child_sa_t *child_sa;
+ proposal_t *selected;
+
+ child_cfg = exchange_test_helper->create_sa(exchange_test_helper, &a, &b,
+ &conf);
+ id_a = a->get_id(a);
+ id_b = b->get_id(b);
+
+ if (disable_init)
+ {
+ lib->settings->set_bool(lib->settings, "%s.child_sa_pfs_info",
+ FALSE, lib->ns);
+ }
+
+ call_ikesa(a, initiate, child_cfg, NULL);
+
+ /* IKE_SA_INIT --> */
+ if (disable_init)
+ {
+ assert_no_notify(IN, CHILD_SA_PFS_INFO_SUPPORTED);
+ }
+ else
+ {
+ lib->settings->set_bool(lib->settings, "%s.child_sa_pfs_info",
+ FALSE, lib->ns);
+ }
+ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a));
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+
+ /* <-- IKE_SA_INIT */
+ assert_no_notify(IN, CHILD_SA_PFS_INFO_SUPPORTED);
+ id_a->set_responder_spi(id_a, id_b->get_responder_spi(id_b));
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+
+ /* IKE_AUTH --> */
+ assert_hook_called(child_updown);
+ assert_no_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 1);
+ assert_hook();
+
+ /* <-- IKE_AUTH */
+ assert_hook_called(child_updown);
+ assert_no_payload(IN, PLV2_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+
+ child_sa = a->get_child_sa(a, PROTO_ESP, 1, TRUE);
+ selected = proposal_create_from_string(PROTO_ESP, ike_auth_ke[_i].disabled);
+ ck_assert(selected->equals(selected, child_sa->get_proposal(child_sa)));
+ selected->destroy(selected);
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
/**
* The peers try to create a new CHILD_SA that looks exactly the same
* as the existing one, so it won't get initiated.
s = suite_create("child create");
+ tc = tcase_create("ike_auth ke");
+ tcase_add_loop_test(tc, test_ike_auth_ke_enabled, 0, countof(ike_auth_ke));
+ tcase_add_loop_test(tc, test_ike_auth_ke_disabled, 0, 2 * countof(ike_auth_ke));
+ suite_add_tcase(s, tc);
+
tc = tcase_create("initiate duplicate");
tcase_add_test(tc, test_duplicate);
suite_add_tcase(s, tc);
/*
- * Copyright (C) 2016-2022 Tobias Brunner
+ * Copyright (C) 2016-2025 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
}
END_TEST
+
+/**
+ * CHILD_SA rekey where the responder does not agree with the DH group selected
+ * by the initiator. However, because they negotiate the KE methods during
+ * IKE_AUTH, the rekeying initiated by the original initiator can be completed
+ * without INVALID_KE_PAYLOAD notify. If it's initiated by the original
+ * responder that's currently not the case as the responder strictly prefers
+ * its own configuration.
+ */
+START_TEST(test_regular_ke_mismatch)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .esp = "aes128-sha256-modp2048-modp3072",
+ },
+ .responder = {
+ .esp = "aes128-sha256-modp3072-modp2048",
+ },
+ };
+ ike_sa_t *a, *b;
+ uint32_t spi_a = _i+1, spi_b = 2-_i, spi_i = 3, spi_r = 4;
+
+ assert_track_sas_start();
+
+ if (_i)
+ { /* responder rekeys the CHILD_SA (SPI 2) */
+ assert_notify(IN, CHILD_SA_PFS_INFO_SUPPORTED);
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, &conf);
+ }
+ else
+ { /* initiator rekeys the CHILD_SA (SPI 1) */
+ assert_notify(IN, CHILD_SA_PFS_INFO_SUPPORTED);
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &conf);
+ }
+ 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);
+
+ if (_i)
+ {
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_rekey);
+ assert_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, spi_b, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, spi_a, spi_b);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
+ assert_hook_not_called(child_rekey);
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, spi_a, CHILD_REKEYING);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, spi_a, spi_b);
+ assert_hook();
+
+ spi_i = spi_r++;
+ }
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_rekey);
+ assert_notify(IN, REKEY_SA);
+ assert_no_notify(OUT, INVALID_KE_PAYLOAD);
+ 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, spi_r, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, spi_a, spi_b, spi_r);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ assert_hook_rekey(child_rekey, spi_a, spi_i);
+ assert_no_notify(IN, REKEY_SA);
+ 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, spi_i, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, spi_a, spi_i, spi_r);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_rekey(child_rekey, spi_b, spi_r);
+ 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, spi_r, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, spi_b, spi_i, spi_r);
+ 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, spi_a, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, spi_i, CHILD_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, spi_a, spi_i, spi_r);
+ 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, spi_i, spi_r);
+ destroy_rekeyed(b, spi_b);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, spi_i, spi_r);
+
+ /* child_updown */
+ assert_hook();
+ assert_track_sas(2, 2);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
/**
* CHILD_SA rekey where the responder does not agree with the DH group selected
* by the initiator, either initiated by the original initiator or responder of
ike_sa_t *a, *b;
uint32_t spi_a = _i+1, spi_b = 2-_i;
+ /* disable KE negotiation during IKE_AUTH, which prevents this mismatch */
+ lib->settings->set_bool(lib->settings, "%s.child_sa_pfs_info",
+ FALSE, lib->ns);
+
assert_track_sas_start();
if (_i)
ike_sa_t *a, *b;
uint32_t spi_a = _i+1, spi_b = 2-_i;
+ /* disable KE negotiation during IKE_AUTH, which prevents this mismatch */
+ lib->settings->set_bool(lib->settings, "%s.child_sa_pfs_info",
+ FALSE, lib->ns);
+
assert_track_sas_start();
if (_i)
END_TEST
/**
- * Both peers initiate the CHILD_SA reekying concurrently but the proposed DH
+ * Both peers initiate the CHILD_SA rekeying concurrently but the proposed DH
* groups are not the same after handling the INVALID_KE_PAYLOAD they should
* still handle the collision properly depending on the nonces.
*/
};
ike_sa_t *a, *b;
+ /* disable KE negotiation during IKE_AUTH, which prevents this mismatch */
+ lib->settings->set_bool(lib->settings, "%s.child_sa_pfs_info",
+ FALSE, lib->ns);
+
assert_track_sas_start();
exchange_test_helper->establish_sa(exchange_test_helper,
ike_sa_t *a, *b;
message_t *msg;
+ /* disable KE negotiation during IKE_AUTH, which prevents this mismatch */
+ lib->settings->set_bool(lib->settings, "%s.child_sa_pfs_info",
+ FALSE, lib->ns);
+
assert_track_sas_start();
exchange_test_helper->establish_sa(exchange_test_helper,
tc = tcase_create("regular");
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_mismatch, 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);
tcase_add_test(tc, test_regular_responder_ignore_soft_expire);