summaryrefslogtreecommitdiff
path: root/src/libcharon/tests/suites
diff options
context:
space:
mode:
authorYves-Alexis Perez <corsac@debian.org>2016-07-16 15:19:53 +0200
committerYves-Alexis Perez <corsac@debian.org>2016-07-16 15:19:53 +0200
commitbf372706c469764d59e9f29c39e3ecbebd72b8d2 (patch)
tree0f0e296e2d50e4a7faf99ae6fa428d2681e81ea1 /src/libcharon/tests/suites
parent518dd33c94e041db0444c7d1f33da363bb8e3faf (diff)
downloadvyos-strongswan-bf372706c469764d59e9f29c39e3ecbebd72b8d2.tar.gz
vyos-strongswan-bf372706c469764d59e9f29c39e3ecbebd72b8d2.zip
Imported Upstream version 5.5.0
Diffstat (limited to 'src/libcharon/tests/suites')
-rw-r--r--src/libcharon/tests/suites/test_child_create.c106
-rw-r--r--src/libcharon/tests/suites/test_child_delete.c366
-rw-r--r--src/libcharon/tests/suites/test_child_rekey.c1569
-rw-r--r--src/libcharon/tests/suites/test_ike_delete.c137
-rw-r--r--src/libcharon/tests/suites/test_ike_rekey.c1480
-rw-r--r--src/libcharon/tests/suites/test_message_chapoly.c8
-rw-r--r--src/libcharon/tests/suites/test_proposal.c81
7 files changed, 3743 insertions, 4 deletions
diff --git a/src/libcharon/tests/suites/test_child_create.c b/src/libcharon/tests/suites/test_child_create.c
new file mode 100644
index 000000000..20a47f6bf
--- /dev/null
+++ b/src/libcharon/tests/suites/test_child_create.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "test_suite.h"
+
+#include <daemon.h>
+#include <tests/utils/exchange_test_helper.h>
+#include <tests/utils/exchange_test_asserts.h>
+#include <tests/utils/job_asserts.h>
+#include <tests/utils/sa_asserts.h>
+
+/**
+ * One of the peers tries to create a new CHILD_SA while the other concurrently
+ * started to rekey the IKE_SA. TEMPORARY_FAILURE should be returned on both
+ * sides and the peers should prepare to retry.
+ */
+START_TEST(test_collision_ike_rekey)
+{
+ child_cfg_t *child_cfg;
+ child_cfg_create_t child = {
+ .mode = MODE_TUNNEL,
+ };
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ assert_hook_not_called(child_updown);
+ child_cfg = child_cfg_create("child", &child);
+ child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
+ child_cfg->add_traffic_selector(child_cfg, TRUE,
+ traffic_selector_create_dynamic(0, 0, 65535));
+ child_cfg->add_traffic_selector(child_cfg, FALSE,
+ traffic_selector_create_dynamic(0, 0, 65535));
+ call_ikesa(a, initiate, child_cfg, 0, NULL, NULL);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+
+ call_ikesa(b, rekey);
+
+ /* CREATE_CHILD_SA { SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_updown);
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 1);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_hook_not_called(child_updown);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 1);
+ assert_scheduler();
+ assert_hook();
+
+ /* CREATE_CHILD_SA { N(TEMP_FAIL) } --> */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_ESTABLISHED);
+ assert_scheduler();
+
+ /* make sure no message was sent after handling the TEMPORARY_FAILURE and
+ * that the task to retry creating the CHILD_SA is queued and not active
+ * and it can't be initiated immediately */
+ ck_assert(!exchange_test_helper->sender->dequeue(exchange_test_helper->sender));
+ assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
+ assert_num_tasks(a, 1, TASK_QUEUE_QUEUED);
+ call_ikesa(a, initiate, NULL, 0, NULL, NULL);
+ assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
+
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+Suite *child_create_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("child create");
+
+ tc = tcase_create("collisions ike rekey");
+ tcase_add_test(tc, test_collision_ike_rekey);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/src/libcharon/tests/suites/test_child_delete.c b/src/libcharon/tests/suites/test_child_delete.c
new file mode 100644
index 000000000..437e919c7
--- /dev/null
+++ b/src/libcharon/tests/suites/test_child_delete.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "test_suite.h"
+
+#include <daemon.h>
+#include <tests/utils/exchange_test_helper.h>
+#include <tests/utils/exchange_test_asserts.h>
+#include <tests/utils/job_asserts.h>
+#include <tests/utils/sa_asserts.h>
+
+/**
+ * Regular CHILD_SA deletion either initiated by the original initiator or
+ * responder of the IKE_SA.
+ */
+START_TEST(test_regular)
+{
+ ike_sa_t *a, *b;
+
+ if (_i)
+ { /* responder deletes the CHILD_SA (SPI 2) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, NULL);
+ }
+ else
+ { /* initiator deletes the CHILD_SA (SPI 1) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ }
+ assert_hook_not_called(child_updown);
+ call_ikesa(a, delete_child_sa, PROTO_ESP, _i+1, FALSE);
+ assert_child_sa_state(a, _i+1, CHILD_DELETING);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Both peers initiate the CHILD_SA deletion concurrently and should handle
+ * the collision properly.
+ */
+START_TEST(test_collision)
+{
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ /* both peers delete the CHILD_SA concurrently */
+ assert_hook_not_called(child_updown);
+ call_ikesa(a, delete_child_sa, PROTO_ESP, 1, FALSE);
+ assert_child_sa_state(a, 1, CHILD_DELETING);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 2, FALSE);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_hook();
+
+ /* RFC 7296 says:
+ *
+ * Normally, the response in the INFORMATIONAL exchange will contain
+ * Delete payloads for the paired SAs going in the other direction.
+ * There is one exception. If, by chance, both ends of a set of SAs
+ * independently decide to close them, each may send a Delete payload
+ * and the two requests may cross in the network. If a node receives a
+ * delete request for SAs for which it has already issued a delete
+ * request, it MUST delete the outgoing SAs while processing the request
+ * and the incoming SAs while processing the response. In that case,
+ * the responses MUST NOT include Delete payloads for the deleted SAs,
+ * since that would result in duplicate deletion and could in theory
+ * delete the wrong SA.
+ *
+ * We don't handle SAs separately so we expect both are still installed,
+ * but the INFORMATIONAL response should not contain a DELETE payload.
+ */
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_updown);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ /* <-- INFORMATIONAL { D } */
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETING);
+ assert_hook();
+
+ /* <-- INFORMATIONAL { } */
+ assert_hook_updown(child_updown, FALSE);
+ assert_message_empty(IN);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
+ assert_hook();
+ /* INFORMATIONAL { } --> */
+ assert_hook_updown(child_updown, FALSE);
+ assert_message_empty(IN);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * This is like the collision above but one of the DELETEs is dropped or delayed
+ * so the other peer is not aware that there is a collision.
+ */
+START_TEST(test_collision_drop)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ /* both peers delete the CHILD_SA concurrently */
+ assert_hook_not_called(child_updown);
+ call_ikesa(a, delete_child_sa, PROTO_ESP, 1, FALSE);
+ assert_child_sa_state(a, 1, CHILD_DELETING);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 2, FALSE);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_updown);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_hook();
+
+ /* drop/delay the responder's message */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- INFORMATIONAL { } */
+ assert_hook_updown(child_updown, FALSE);
+ assert_message_empty(IN);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } (delayed/retransmitted) */
+ assert_hook_not_called(child_updown);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_hook();
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_updown(child_updown, FALSE);
+ assert_message_empty(IN);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a rekey of the IKE_SA of the CHILD_SA the other
+ * peer is concurrently trying to delete.
+ *
+ * delete ----\ /---- rekey IKE
+ * \-----/----> detect collision
+ * detect collision <---------/ /---- delete
+ * TEMP_FAIL ----\ /
+ * \----/----->
+ * <--------/
+ */
+START_TEST(test_collision_ike_rekey)
+{
+ ike_sa_t *a, *b;
+ uint32_t spi_a = _i+1;
+
+ if (_i)
+ { /* responder deletes the CHILD_SA (SPI 2) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, NULL);
+ }
+ else
+ { /* initiator deletes the CHILD_SA (SPI 1) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ }
+ call_ikesa(a, delete_child_sa, PROTO_ESP, spi_a, FALSE);
+ assert_child_sa_state(a, spi_a, CHILD_DELETING);
+ call_ikesa(b, rekey);
+ assert_ike_sa_state(b, IKE_REKEYING);
+
+ /* this should never get called as there is no successful rekeying */
+ assert_hook_not_called(ike_rekey);
+
+ /* RFC 7296, 2.25.2: If a peer receives a request to delete a Child SA when
+ * it is currently rekeying the IKE SA, it SHOULD reply as usual, with a
+ * Delete payload.
+ */
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(OUT, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_REKEYING);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ /* RFC 7296, 2.25.1: If a peer receives a request to rekey the IKE SA, and
+ * it is currently, rekeying, or closing a Child SA of that IKE SA, it
+ * SHOULD reply with TEMPORARY_FAILURE.
+ */
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, spi_a, CHILD_DELETING);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(child_updown, FALSE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { N(TEMP_FAIL) } --> */
+ /* we expect a job to retry the rekeying is scheduled */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_ESTABLISHED);
+ assert_scheduler();
+
+ /* ike_rekey */
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a delete of the IKE_SA of the CHILD_SA the other
+ * peer is concurrently trying to delete.
+ *
+ * delete ----\ /---- delete IKE
+ * \-----/----> detect collision
+ * <---------/ /---- delete
+ * delete ----\ /
+ * \----/----->
+ * sa already gone <--------/
+ */
+START_TEST(test_collision_ike_delete)
+{
+ ike_sa_t *a, *b;
+ uint32_t spi_a = _i+1;
+ message_t *msg;
+ status_t s;
+
+ 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);
+ }
+ call_ikesa(a, delete_child_sa, PROTO_ESP, spi_a, FALSE);
+ assert_child_sa_state(a, spi_a, CHILD_DELETING);
+ call_ikesa(b, delete);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* RFC 7296, 2.25.2 does not explicitly state what the behavior SHOULD be if
+ * a peer receives a request to delete a CHILD_SA when it is currently
+ * closing the IKE SA. We expect a regular response.
+ */
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(OUT, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ /* RFC 7296, 2.25.1 does not explicitly state what the behavior SHOULD be if
+ * a peer receives a request to close the IKE SA if it is currently deleting
+ * a Child SA of that IKE SA. Let's just close the IKE_SA and forget the
+ * delete.
+ */
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ assert_message_empty(OUT);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ /* the SA is already gone */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+ msg->destroy(msg);
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_not_called(child_updown);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+ assert_hook();
+}
+END_TEST
+
+Suite *child_delete_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("child delete");
+
+ tc = tcase_create("regular");
+ tcase_add_loop_test(tc, test_regular, 0, 2);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions");
+ tcase_add_test(tc, test_collision);
+ tcase_add_test(tc, test_collision_drop);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions ike rekey");
+ tcase_add_loop_test(tc, test_collision_ike_rekey, 0, 2);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions ike delete");
+ tcase_add_loop_test(tc, test_collision_ike_delete, 0, 2);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/src/libcharon/tests/suites/test_child_rekey.c b/src/libcharon/tests/suites/test_child_rekey.c
new file mode 100644
index 000000000..fcac49388
--- /dev/null
+++ b/src/libcharon/tests/suites/test_child_rekey.c
@@ -0,0 +1,1569 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "test_suite.h"
+
+#include <daemon.h>
+#include <tests/utils/exchange_test_helper.h>
+#include <tests/utils/exchange_test_asserts.h>
+#include <tests/utils/job_asserts.h>
+#include <tests/utils/sa_asserts.h>
+
+/**
+ * Initiate rekeying the CHILD_SA with the given SPI on the given IKE_SA.
+ */
+#define initiate_rekey(sa, spi) ({ \
+ assert_hook_not_called(child_updown); \
+ assert_hook_not_called(child_rekey); \
+ call_ikesa(sa, rekey_child_sa, PROTO_ESP, spi); \
+ assert_child_sa_state(sa, spi, CHILD_REKEYING); \
+ assert_hook(); \
+ assert_hook(); \
+})
+
+/**
+ * Regular CHILD_SA rekey either initiated by the original initiator or
+ * responder of the IKE_SA.
+ */
+START_TEST(test_regular)
+{
+ ike_sa_t *a, *b;
+ uint32_t spi_a = _i+1, spi_b = 2-_i;
+
+ 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);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_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_REKEYED);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ assert_hook_called(child_rekey);
+ 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);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_not_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_INSTALLED);
+ assert_child_sa_count(b, 1);
+ 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_INSTALLED);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+
+ /* child_updown */
+ assert_hook();
+
+ 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
+ * the IKE_SA.
+ */
+START_TEST(test_regular_ke_invalid)
+{
+ 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;
+
+ if (_i)
+ { /* responder rekeys the CHILD_SA (SPI 2) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, &conf);
+ }
+ else
+ { /* initiator rekeys the CHILD_SA (SPI 1) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &conf);
+ }
+ initiate_rekey(a, spi_a);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* 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_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_hook();
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_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_REKEYED);
+ assert_child_sa_state(b, 6, CHILD_INSTALLED);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ assert_hook_called(child_rekey);
+ 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);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_rekey);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 6, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+ 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, 5, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+
+ /* child_updown */
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Check that the responder ignores soft expires while waiting for the delete
+ * after a rekeying.
+ */
+START_TEST(test_regular_responder_ignore_soft_expire)
+{
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ initiate_rekey(a, 1);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_called(child_rekey);
+ assert_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ assert_hook_called(child_rekey);
+ assert_no_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETING);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED);
+ assert_hook();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
+ /* this should not produce a message, if it does there won't be a delete
+ * payload below */
+ call_ikesa(b, rekey_child_sa, PROTO_ESP, 2);
+ assert_child_sa_state(b, 2, CHILD_REKEYED);
+
+ /* INFORMATIONAL { D } --> */
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+ /* <-- INFORMATIONAL { D } */
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Check that the responder handles hard expires properly while waiting for the
+ * delete after a rekeying (e.g. if the initiator of the rekeying fails to
+ * delete the CHILD_SA for some reason).
+ */
+START_TEST(test_regular_responder_handle_hard_expire)
+{
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ initiate_rekey(a, 1);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_called(child_rekey);
+ assert_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ assert_hook_called(child_rekey);
+ assert_no_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETING);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED);
+ assert_hook();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
+ /* this is similar to a regular delete collision */
+ assert_single_payload(OUT, PLV2_DELETE);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 2, TRUE);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+
+ /* INFORMATIONAL { D } --> */
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_child_sa_state(a, 2, CHILD_DELETING);
+ /* <-- INFORMATIONAL { D } */
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED);
+ assert_child_sa_state(a, 1, CHILD_DELETING);
+ /* <-- INFORMATIONAL { } */
+ assert_message_empty(IN);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ /* INFORMATIONAL { } --> */
+ assert_message_empty(IN);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Both peers initiate the CHILD_SA reekying concurrently and should handle
+ * the collision properly depending on the nonces.
+ */
+START_TEST(test_collision)
+{
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* When rekeyings collide we get two CHILD_SAs with a total of four nonces.
+ * The CHILD_SA with the lowest nonce SHOULD be deleted by the peer that
+ * created that CHILD_SA. The replaced CHILD_SA is deleted by the peer that
+ * initiated the surviving SA.
+ * Four nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /----- ...
+ * ... -----\
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted CHILD_SA (either redundant or replaced) */
+ uint32_t spi_del_a, spi_del_b;
+ /* SPIs of the kept CHILD_SA */
+ uint32_t spi_a, spi_b;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 4, 3, 5 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 4, 3, 5 },
+ };
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a, 1);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_rekey(child_rekey, 2, 5);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED);
+ assert_hook();
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_rekey(child_rekey, 1, 6);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ if (data[_i].spi_del_a == 1)
+ { /* currently we call this again if we keep our own replacement as we
+ * already called it above */
+ assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+ assert_child_sa_count(b, 2);
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ assert_child_sa_count(a, 2);
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * This is like the rekey collision above, but one peer deletes the
+ * redundant/old SA before the other peer receives the CREATE_CHILD_SA
+ * response:
+ *
+ * rekey ----\ /---- rekey
+ * \-----/----> detect collision
+ * detect collision <---------/ /----
+ * ----\ /
+ * \----/----->
+ * handle delete <--------/------- delete SA
+ * --------/------->
+ * handle rekey <------/
+ * delete SA ---------------->
+ * <----------------
+ */
+START_TEST(test_collision_delayed_response)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Four nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /----- ...
+ * ... -----\
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted CHILD_SA (either redundant or replaced) */
+ uint32_t spi_del_a, spi_del_b;
+ /* SPIs of the kept CHILD_SA */
+ uint32_t spi_a, spi_b;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 4, 3, 5 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 4, 3, 5 },
+ };
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a, 1);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_rekey(child_rekey, 2, 5);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED);
+ assert_hook();
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_rekey(child_rekey, 1, 6);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED);
+ assert_hook();
+
+ /* delay the CREATE_CHILD_SA response from b to a */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ }
+ else
+ {
+ assert_child_sa_state(a, 1, CHILD_REKEYED);
+ assert_child_sa_count(a, 1);
+ }
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+ assert_child_sa_count(b, 2);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
+ if (data[_i].spi_del_a == 1)
+ {
+ assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_hook();
+ }
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ assert_child_sa_count(a, 2);
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * In this scenario one of the peers does not notice that there is a
+ * rekey collision:
+ *
+ * rekey ----\ /---- rekey
+ * \ /
+ * detect collision <-----\---/
+ * -------\-------->
+ * \ /---- delete old SA
+ * \-/----> detect collision
+ * detect collision <---------/ /---- TEMP_FAIL
+ * delete -----------/---->
+ * aborts rekeying <---------/
+ */
+START_TEST(test_collision_delayed_request)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Three nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * N3/5 <-----\--/
+ * ... -----\ \-------> ...
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF } },
+ { { 0xFF, 0x00, 0xFF } },
+ { { 0xFF, 0xFF, 0x00 } },
+ };
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a, 1);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b, 2);
+
+ /* delay the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_rekey(child_rekey, 1, 5);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED);
+ assert_hook();
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ assert_hook_rekey(child_rekey, 2, 4);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_hook();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ assert_scheduler();
+
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Similar to above one peer fails to notice the collision but the
+ * CREATE_CHILD_SA request is even more delayed:
+ *
+ * rekey ----\ /---- rekey
+ * \ /
+ * detect collision <-----\---/
+ * -------\-------->
+ * detect collision <-------\-------- delete old SA
+ * delete ---------\------>
+ * \----->
+ * /---- CHILD_SA_NOT_FOUND
+ * aborts rekeying <----------/
+ */
+START_TEST(test_collision_delayed_request_more)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Three nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * N3/5 <-----\--/
+ * ... -----\ \-------> ...
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF } },
+ { { 0xFF, 0x00, 0xFF } },
+ { { 0xFF, 0xFF, 0x00 } },
+ };
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a, 1);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b, 2);
+
+ /* delay the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_rekey(child_rekey, 1, 5);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED);
+ assert_hook();
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ assert_hook_rekey(child_rekey, 2, 4);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_hook();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_single_notify(OUT, CHILD_SA_NOT_FOUND);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+ /* <-- CREATE_CHILD_SA { N(NO_CHILD_SA) } */
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ assert_scheduler();
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Both peers initiate the CHILD_SA reekying 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.
+ */
+START_TEST(test_collision_ke_invalid)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .esp = "aes128-sha256-modp2048-modp3072",
+ },
+ .responder = {
+ .esp = "aes128-sha256-modp3072-modp2048",
+ },
+ };
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &conf);
+
+ /* Eight nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /---- INVAL_KE
+ * INVAL_KE -----\ /
+ * <-----\--/
+ * N5/7 -----\ \------->
+ * \ /---- N6/8
+ * \--/----> N7/9
+ * N8/10 <--------/ /---- ...
+ * ... ------\
+ *
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted CHILD_SA (either redundant or replaced) */
+ uint32_t spi_del_a, spi_del_b;
+ /* SPIs of the kept CHILD_SA */
+ uint32_t spi_a, spi_b;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 7, 2,10, 8 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 8, 7, 9 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 7, 2,10, 8 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 8, 7, 9 },
+ };
+
+ /* make sure the nonces of the first try don't affect the retries */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(a, 1);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(b, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYING);
+ assert_child_sa_count(b, 1);
+ assert_hook();
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYING);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ 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, 1, CHILD_REKEYING);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+ /* CREATE_CHILD_SA { N(INVAL_KE) } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ assert_hook_not_called(child_rekey);
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYING);
+ assert_child_sa_count(b, 1);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_rekey(child_rekey, 2, 9);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED);
+ assert_child_sa_state(b, 9, CHILD_INSTALLED);
+ assert_hook();
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_rekey(child_rekey, 1, 10);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED);
+ assert_child_sa_state(a,10, CHILD_INSTALLED);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ if (data[_i].spi_del_a == 1)
+ { /* currently we call this again if we keep our own replacement as we
+ * already called it above */
+ assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ else
+ {
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ }
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ else
+ {
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ }
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+ assert_child_sa_count(b, 2);
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ assert_child_sa_count(a, 2);
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * This is a variation of the above but with the retry by one peer delayed so
+ * that to the other peer it looks like there is no collision.
+ */
+START_TEST(test_collision_ke_invalid_delayed_retry)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .esp = "aes128-sha256-modp2048-modp3072",
+ },
+ .responder = {
+ .esp = "aes128-sha256-modp3072-modp2048",
+ },
+ };
+ ike_sa_t *a, *b;
+ message_t *msg;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &conf);
+
+ /* Seven nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /---- INVAL_KE
+ * INVAL_KE -----\ /
+ * <-----\--/
+ * N5/7 -----\ \------->
+ * <-----\--------- N6/8
+ * N7/9 -------\------->
+ * <-------\------- DELETE
+ * ... ------\ \----->
+ * /---- TEMP_FAIL
+ *
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF } },
+ { { 0xFF, 0x00, 0xFF } },
+ { { 0xFF, 0xFF, 0x00 } },
+ };
+
+ /* make sure the nonces of the first try don't affect the retries */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(a, 1);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(b, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYING);
+ assert_child_sa_count(b, 1);
+ assert_hook();
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYING);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ 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, 1, CHILD_REKEYING);
+ assert_child_sa_count(a, 1);
+ assert_hook();
+ /* CREATE_CHILD_SA { N(INVAL_KE) } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ assert_hook_not_called(child_rekey);
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYING);
+ assert_child_sa_count(b, 1);
+ assert_hook();
+
+ /* delay the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_rekey(child_rekey, 1, 9);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED);
+ assert_child_sa_state(a, 9, CHILD_INSTALLED);
+ assert_hook();
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ assert_hook_rekey(child_rekey, 2, 8);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_child_sa_state(b, 8, CHILD_INSTALLED);
+ assert_hook();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_child_sa_state(b, 2, CHILD_DELETING);
+ assert_child_sa_state(b, 8, CHILD_INSTALLED);
+
+ /* <-- INFORMATIONAL { D } */
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 9, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 9, CHILD_INSTALLED);
+ assert_child_sa_count(a, 1);
+ assert_scheduler();
+
+ /* INFORMATIONAL { D } --> */
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 8, CHILD_INSTALLED);
+ assert_child_sa_count(b, 1);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a DELETE of the CHILD_SA the other peer is
+ * concurrently trying to rekey.
+ *
+ * rekey ----\ /---- delete
+ * \-----/----> detect collision
+ * detect collision <---------/ /---- TEMP_FAIL
+ * delete ----\ /
+ * \----/----->
+ * aborts rekeying <--------/
+ */
+START_TEST(test_collision_delete)
+{
+ ike_sa_t *a, *b;
+ uint32_t spi_a = _i+1, spi_b = 2-_i;
+
+ 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);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, spi_b, FALSE);
+ assert_child_sa_state(b, spi_b, CHILD_DELETING);
+
+ /* this should never get called as there is no successful rekeying on
+ * either side */
+ assert_hook_not_called(child_rekey);
+
+ /* RFC 7296, 2.25.1: If a peer receives a request to rekey a CHILD_SA that
+ * it is currently trying to close, it SHOULD reply with TEMPORARY_FAILURE.
+ */
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_updown);
+ assert_notify(IN, REKEY_SA);
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, spi_b, CHILD_DELETING);
+ assert_hook();
+
+ /* RFC 7296, 2.25.1: If a peer receives a request to delete a CHILD_SA that
+ * it is currently trying to rekey, it SHOULD reply as usual, with a DELETE
+ * payload.
+ */
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_single_payload(OUT, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_hook_not_called(child_updown);
+ /* we don't expect a job to retry the rekeying */
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_scheduler();
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ /* child_rekey */
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a DELETE of the CHILD_SA the other peer is
+ * concurrently trying to rekey. However, the delete request is delayed or
+ * dropped, so the peer doing the rekeying is unaware of the collision.
+ *
+ * rekey ----\ /---- delete
+ * \-----/----> detect collision
+ * reschedule <---------/------ TEMP_FAIL
+ * <--------/
+ * delete ---------------->
+ *
+ * The job will not find the SA to retry rekeying.
+ */
+START_TEST(test_collision_delete_drop_delete)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+ uint32_t spi_a = _i+1, spi_b = 2-_i;
+
+ 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);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, spi_b, FALSE);
+ assert_child_sa_state(b, spi_b, CHILD_DELETING);
+
+ /* this should never get called as there is no successful rekeying on
+ * either side */
+ assert_hook_not_called(child_rekey);
+
+ /* RFC 7296, 2.25.1: If a peer receives a request to rekey a CHILD_SA that
+ * it is currently trying to close, it SHOULD reply with TEMPORARY_FAILURE.
+ */
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_updown);
+ assert_notify(IN, REKEY_SA);
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, spi_b, CHILD_DELETING);
+ assert_hook();
+
+ /* delay the DELETE request */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_hook_not_called(child_updown);
+ /* we expect a job to retry the rekeying is scheduled */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, spi_a, CHILD_INSTALLED);
+ assert_scheduler();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } (delayed) */
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_single_payload(OUT, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_child_sa_count(a, 0);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ /* child_rekey */
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a DELETE of the CHILD_SA the other peer is
+ * concurrently trying to rekey. However, the rekey request is delayed or
+ * dropped, so the peer doing the deleting is unaware of the collision.
+ *
+ * rekey ----\ /---- delete
+ * detect collision <----\-----/
+ * delete ------\--------->
+ * \-------->
+ * /---- CHILD_SA_NOT_FOUND
+ * aborts rekeying <----------/
+ */
+ START_TEST(test_collision_delete_drop_rekey)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+ uint32_t spi_a = _i+1, spi_b = 2-_i;
+
+ 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);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, spi_b, FALSE);
+ assert_child_sa_state(b, spi_b, CHILD_DELETING);
+
+ /* this should never get called as there is no successful rekeying on
+ * either side */
+ assert_hook_not_called(child_rekey);
+
+ /* delay the CREATE_CHILD_SA request */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* RFC 7296, 2.25.1: If a peer receives a request to delete a CHILD_SA that
+ * it is currently trying to rekey, it SHOULD reply as usual, with a DELETE
+ * payload.
+ */
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_single_payload(OUT, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_count(b, 0);
+ assert_hook();
+
+ /* RFC 7296, 2.25.1: If a peer receives a to rekey a Child SA that does not
+ * exist, it SHOULD reply with CHILD_SA_NOT_FOUND.
+ */
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
+ assert_hook_not_called(child_updown);
+ assert_notify(IN, REKEY_SA);
+ assert_single_notify(OUT, CHILD_SA_NOT_FOUND);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(NO_CHILD_SA) } */
+ assert_hook_not_called(child_updown);
+ /* no jobs or tasks should get scheduled/queued */
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_scheduler();
+ assert_hook();
+
+ /* child_rekey */
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * FIXME: Not sure what we can do about the following:
+ *
+ * One of the hosts initiates a rekeying of a CHILD_SA and after responding to
+ * it the other peer deletes the new SA. However, the rekey response is
+ * delayed or dropped, so the peer doing the rekeying receives a delete for an
+ * unknown CHILD_SA and then has a rekeyed CHILD_SA that should not exist.
+ *
+ * rekey ---------------->
+ * /---- rekey
+ * unknown SA <----------/----- delete new SA
+ * ----------/----->
+ * <--------/
+ *
+ * The peers' states are now out of sync.
+ *
+ * Perhaps the rekey initiator could keep track of deletes for non-existing SAs
+ * while rekeying and then check against the SPIs when handling the
+ * CREATE_CHILD_SA response.
+ */
+
+
+/**
+ * One of the hosts initiates a rekey of the IKE_SA of the CHILD_SA the other
+ * peer is concurrently trying to rekey.
+ *
+ * rekey ----\ /---- rekey IKE
+ * \-----/----> detect collision
+ * detect collision <---------/ /---- TEMP_FAIL
+ * TEMP_FAIL ----\ /
+ * \----/----->
+ * <--------/
+ */
+START_TEST(test_collision_ike_rekey)
+{
+ ike_sa_t *a, *b;
+ uint32_t spi_a = _i+1;
+
+ 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);
+ call_ikesa(b, rekey);
+ assert_ike_sa_state(b, IKE_REKEYING);
+
+ /* these should never get called as there is no successful rekeying on
+ * either side */
+ assert_hook_not_called(ike_rekey);
+ assert_hook_not_called(child_rekey);
+
+ /* RFC 7296, 2.25.2: If a peer receives a request to rekey a CHILD_SA when
+ * it is currently rekeying the IKE SA, it SHOULD reply with
+ * TEMPORARY_FAILURE.
+ */
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_REKEYING);
+
+ /* RFC 7296, 2.25.1: If a peer receives a request to rekey the IKE SA, and
+ * it is currently, rekeying, or closing a Child SA of that IKE SA, it
+ * SHOULD reply with TEMPORARY_FAILURE.
+ */
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, spi_a, CHILD_REKEYING);
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ /* we expect a job to retry the rekeying is scheduled */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, spi_a, CHILD_INSTALLED);
+ assert_scheduler();
+
+ /* CREATE_CHILD_SA { N(TEMP_FAIL) } --> */
+ /* we expect a job to retry the rekeying is scheduled */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_ESTABLISHED);
+ assert_scheduler();
+
+ /* ike_rekey/child_rekey */
+ assert_hook();
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a delete of the IKE_SA of the CHILD_SA the other
+ * peer is concurrently trying to rekey.
+ *
+ * rekey ----\ /---- delete IKE
+ * \-----/----> detect collision
+ * <---------/ /---- TEMP_FAIL
+ * delete ----\ /
+ * \----/----->
+ * sa already gone <--------/
+ */
+START_TEST(test_collision_ike_delete)
+{
+ ike_sa_t *a, *b;
+ uint32_t spi_a = _i+1;
+ message_t *msg;
+ status_t s;
+
+ 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);
+ call_ikesa(b, delete);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* this should never get called as there is no successful rekeying on
+ * either side */
+ assert_hook_not_called(child_rekey);
+
+ /* RFC 7296, 2.25.2 does not explicitly state what the behavior SHOULD be if
+ * a peer receives a request to rekey a CHILD_SA when it is currently
+ * closing the IKE SA. We expect a TEMPORARY_FAILURE notify.
+ */
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* RFC 7296, 2.25.1 does not explicitly state what the behavior SHOULD be if
+ * a peer receives a request to close the IKE SA if it is currently rekeying
+ * a Child SA of that IKE SA. Let's just close the IKE_SA and forget the
+ * rekeying.
+ */
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ assert_message_empty(OUT);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ /* the SA is already gone */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+ msg->destroy(msg);
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* child_rekey */
+ assert_hook();
+}
+END_TEST
+
+Suite *child_rekey_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("child rekey");
+
+ tc = tcase_create("regular");
+ tcase_add_loop_test(tc, test_regular, 0, 2);
+ tcase_add_loop_test(tc, test_regular_ke_invalid, 0, 2);
+ tcase_add_test(tc, test_regular_responder_ignore_soft_expire);
+ tcase_add_test(tc, test_regular_responder_handle_hard_expire);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions rekey");
+ tcase_add_loop_test(tc, test_collision, 0, 4);
+ tcase_add_loop_test(tc, test_collision_delayed_response, 0, 4);
+ tcase_add_loop_test(tc, test_collision_delayed_request, 0, 3);
+ tcase_add_loop_test(tc, test_collision_delayed_request_more, 0, 3);
+ tcase_add_loop_test(tc, test_collision_ke_invalid, 0, 4);
+ tcase_add_loop_test(tc, test_collision_ke_invalid_delayed_retry, 0, 3);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions delete");
+ tcase_add_loop_test(tc, test_collision_delete, 0, 2);
+ tcase_add_loop_test(tc, test_collision_delete_drop_delete, 0, 2);
+ tcase_add_loop_test(tc, test_collision_delete_drop_rekey, 0, 2);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions ike rekey");
+ tcase_add_loop_test(tc, test_collision_ike_rekey, 0, 2);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions ike delete");
+ tcase_add_loop_test(tc, test_collision_ike_delete, 0, 2);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/src/libcharon/tests/suites/test_ike_delete.c b/src/libcharon/tests/suites/test_ike_delete.c
new file mode 100644
index 000000000..d79f9bc50
--- /dev/null
+++ b/src/libcharon/tests/suites/test_ike_delete.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "test_suite.h"
+
+#include <tests/utils/exchange_test_helper.h>
+#include <tests/utils/exchange_test_asserts.h>
+#include <tests/utils/sa_asserts.h>
+
+/**
+ * Regular IKE_SA delete either initiated by the original initiator or
+ * responder of the IKE_SA.
+ */
+START_TEST(test_regular)
+{
+ ike_sa_t *a, *b;
+ status_t s;
+
+ if (_i)
+ { /* responder deletes the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, NULL);
+ }
+ else
+ { /* initiator deletes the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ }
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+ call_ikesa(a, delete);
+ assert_ike_sa_state(a, IKE_DELETING);
+ assert_hook();
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ 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);
+ assert_hook();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { } */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ 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);
+ assert_hook();
+ assert_hook();
+}
+END_TEST
+
+/**
+ * Both peers initiate the IKE_SA deletion concurrently and should handle the
+ * collision properly.
+ */
+START_TEST(test_collision)
+{
+ ike_sa_t *a, *b;
+ status_t s;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+ call_ikesa(a, delete);
+ assert_ike_sa_state(a, IKE_DELETING);
+ call_ikesa(b, delete);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_hook();
+ assert_hook();
+
+ /* RFC 7296 says: If a peer receives a request to close an IKE SA that it
+ * is currently trying to close, it SHOULD reply as usual, and forget about
+ * its own close request.
+ * So we expect the SA to just get closed with an empty response still sent.
+ */
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_message_empty(OUT);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_message_empty(OUT);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ assert_hook();
+ assert_hook();
+}
+END_TEST
+
+Suite *ike_delete_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("ike delete");
+
+ tc = tcase_create("regular");
+ tcase_add_loop_test(tc, test_regular, 0, 2);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions");
+ tcase_add_test(tc, test_collision);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/src/libcharon/tests/suites/test_ike_rekey.c b/src/libcharon/tests/suites/test_ike_rekey.c
new file mode 100644
index 000000000..ba39657a4
--- /dev/null
+++ b/src/libcharon/tests/suites/test_ike_rekey.c
@@ -0,0 +1,1480 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "test_suite.h"
+
+#include <tests/utils/exchange_test_helper.h>
+#include <tests/utils/exchange_test_asserts.h>
+#include <tests/utils/job_asserts.h>
+#include <tests/utils/sa_asserts.h>
+
+/**
+ * Initiate rekeying the given IKE_SA.
+ */
+#define initiate_rekey(sa) ({ \
+ assert_hook_not_called(ike_rekey); \
+ call_ikesa(sa, rekey); \
+ assert_ike_sa_state(a, IKE_REKEYING); \
+ assert_hook(); \
+})
+
+/**
+ * Regular IKE_SA rekeying either initiated by the original initiator or
+ * responder of the IKE_SA.
+ */
+START_TEST(test_regular)
+{
+ ike_sa_t *a, *b, *new_sa;
+ status_t s;
+
+ 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 { SA, Ni, KEi } --> */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ assert_no_notify(IN, REKEY_SA);
+ 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 { SA, Nr, KEr } */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ assert_no_notify(IN, REKEY_SA);
+ 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();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * IKE_SA rekeying where the responder does not agree with the DH group selected
+ * by the initiator, either initiated by the original initiator or responder of
+ * the IKE_SA.
+ */
+START_TEST(test_regular_ke_invalid)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .ike = "aes128-sha256-modp2048-modp3072",
+ },
+ .responder = {
+ .ike = "aes128-sha256-modp3072-modp2048",
+ },
+ };
+ ike_sa_t *a, *b, *sa;
+ status_t s;
+
+ lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
+ FALSE, lib->ns);
+ if (_i)
+ { /* responder rekeys the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, &conf);
+ }
+ else
+ { /* initiator rekeys the IKE_SA */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &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);
+
+ lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
+ TRUE, lib->ns);
+
+ initiate_rekey(a);
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ assert_hook_not_called(ike_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_ESTABLISHED);
+ assert_child_sa_count(b, 1);
+ assert_ike_sa_count(0);
+
+ /* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ 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();
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_REKEYED);
+ assert_child_sa_count(b, 0);
+ sa = assert_ike_sa_checkout(3, 5, FALSE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(1);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, KEr } */
+ assert_hook_rekey(ike_rekey, 1, 3);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_ike_sa_state(a, IKE_DELETING);
+ assert_child_sa_count(a, 0);
+ sa = assert_ike_sa_checkout(3, 5, TRUE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(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();
+
+ 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.
+ */
+START_TEST(test_collision)
+{
+ ike_sa_t *a, *b, *sa;
+ status_t status;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* When rekeyings collide we get two IKE_SAs with a total of four nonces.
+ * The IKE_SA with the lowest nonce SHOULD be deleted by the peer that
+ * created that IKE_SA. The replaced IKE_SA is deleted by the peer that
+ * initiated the surviving SA.
+ * Four nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * IKE_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /----- ...
+ * ... -----\
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted IKE_SAs (either redundant or replaced) */
+ uint32_t del_a_i, del_a_r;
+ uint32_t del_b_i, del_b_r;
+ /* SPIs of the kept IKE_SA */
+ uint32_t spi_i, spi_r;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 2, 4, 6, 3, 5 },
+ };
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b);
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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 { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* simplify next steps by checking in original IKE_SAs */
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, a);
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, b);
+ assert_ike_sa_count(2);
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, KEr } */
+ assert_hook_rekey(ike_rekey, 1, data[_i].spi_i);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ /* as original initiator a is initiator of both SAs it could delete */
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, TRUE);
+ assert_ike_sa_state(sa, IKE_DELETING);
+ assert_child_sa_count(sa, 0);
+ /* if b won it will delete the original SA a initiated */
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i == 1);
+ assert_ike_sa_state(sa, IKE_REKEYED);
+ assert_child_sa_count(sa, 0);
+ sa = assert_ike_sa_checkout(data[_i].spi_i, data[_i].spi_r,
+ data[_i].del_a_i == 1);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(4);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { SA, Nr, KEr } --> */
+ assert_hook_rekey(ike_rekey, 1, data[_i].spi_i);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ /* if b wins it deletes the SA originally initiated by a */
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i != 1);
+ assert_ike_sa_state(sa, IKE_DELETING);
+ assert_child_sa_count(sa, 0);
+ /* a only deletes SAs for which b is responder */
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, FALSE);
+ assert_ike_sa_state(sa, IKE_REKEYED);
+ assert_child_sa_count(sa, 0);
+ sa = assert_ike_sa_checkout(data[_i].spi_i, data[_i].spi_r,
+ data[_i].del_b_i == 1);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(6);
+ 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);
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, FALSE);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(5);
+ /* <-- INFORMATIONAL { D } */
+ assert_single_payload(IN, PLV2_DELETE);
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i == 1);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(4);
+ /* <-- INFORMATIONAL { } */
+ assert_message_empty(IN);
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, TRUE);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(3);
+ /* INFORMATIONAL { } --> */
+ assert_message_empty(IN);
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i != 1);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(2);
+
+ /* ike_rekey/ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+ assert_hook();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * Both peers initiate the IKE_SA rekeying concurrently but the proposed DH
+ * gropus are not the same. After handling the INVALID_KE_PAYLOAD they should
+ * still handle the collision properly depending on the nonces.
+ */
+START_TEST(test_collision_ke_invalid)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .ike = "aes128-sha256-modp2048-modp3072",
+ },
+ .responder = {
+ .ike = "aes128-sha256-modp3072-modp2048",
+ },
+ };
+ ike_sa_t *a, *b, *sa;
+ status_t status;
+
+ lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
+ FALSE, lib->ns);
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &conf);
+
+ lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
+ TRUE, lib->ns);
+
+ /* Six nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * IKE_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /---- INVAL_KE
+ * INVAL_KE -----\ /
+ * <-----\--/
+ * N1/3 -----\ \------->
+ * \ /---- N2/4
+ * \--/----> N5/7
+ * N6/8 <--------/ /---- ...
+ * ... ------\
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted IKE_SAs (either redundant or replaced) */
+ uint32_t del_a_i, del_a_r;
+ uint32_t del_b_i, del_b_r;
+ /* SPIs of the kept IKE_SA */
+ uint32_t spi_i, spi_r;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 7, 1, 2, 4, 8 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 2, 4, 8, 3, 7 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 7, 1, 2, 4, 8 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 2, 4, 8, 3, 7 },
+ };
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b);
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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 { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ assert_hook_not_called(ike_rekey);
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ 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();
+
+ /* CREATE_CHILD_SA { N(INVAL_KE) } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ assert_hook_not_called(child_rekey);
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ 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 { SA, Ni, KEi } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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 { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* simplify next steps by checking in original IKE_SAs */
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, a);
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, b);
+ assert_ike_sa_count(2);
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, KEr } */
+ assert_hook_rekey(ike_rekey, 1, data[_i].spi_i);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ /* as original initiator a is initiator of both SAs it could delete */
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, TRUE);
+ assert_ike_sa_state(sa, IKE_DELETING);
+ assert_child_sa_count(sa, 0);
+ /* if b won it will delete the original SA a initiated */
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i == 1);
+ assert_ike_sa_state(sa, IKE_REKEYED);
+ assert_child_sa_count(sa, 0);
+ sa = assert_ike_sa_checkout(data[_i].spi_i, data[_i].spi_r,
+ data[_i].del_a_i == 1);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(4);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { SA, Nr, KEr } --> */
+ assert_hook_rekey(ike_rekey, 1, data[_i].spi_i);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ /* if b wins it deletes the SA originally initiated by a */
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i != 1);
+ assert_ike_sa_state(sa, IKE_DELETING);
+ assert_child_sa_count(sa, 0);
+ /* a only deletes SAs for which b is responder */
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, FALSE);
+ assert_ike_sa_state(sa, IKE_REKEYED);
+ assert_child_sa_count(sa, 0);
+ sa = assert_ike_sa_checkout(data[_i].spi_i, data[_i].spi_r,
+ data[_i].del_b_i == 1);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(6);
+ 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);
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, FALSE);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(5);
+ /* <-- INFORMATIONAL { D } */
+ assert_single_payload(IN, PLV2_DELETE);
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i == 1);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(4);
+ /* <-- INFORMATIONAL { } */
+ assert_message_empty(IN);
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, TRUE);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(3);
+ /* INFORMATIONAL { } --> */
+ assert_message_empty(IN);
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i != 1);
+ status = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, status);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(2);
+
+ /* ike_rekey/ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+ assert_hook();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * This is like the collision above but one of the retries is delayed.
+ */
+START_TEST(test_collision_ke_invalid_delayed_retry)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .ike = "aes128-sha256-modp2048-modp3072",
+ },
+ .responder = {
+ .ike = "aes128-sha256-modp3072-modp2048",
+ },
+ };
+ ike_sa_t *a, *b, *sa;
+ message_t *msg;
+ status_t s;
+
+ lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
+ FALSE, lib->ns);
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &conf);
+
+ lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
+ TRUE, lib->ns);
+
+ /* Five nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * IKE_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /---- INVAL_KE
+ * INVAL_KE -----\ /
+ * <-----\--/
+ * N1/3 -----\ \------->
+ * <-----\--------- N2/4
+ * N5/7 -------\------->
+ * <-------\------- DELETE
+ * ... ------\ \----->
+ * /---- TEMP_FAIL
+ *
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF } },
+ { { 0xFF, 0x00, 0xFF } },
+ { { 0xFF, 0xFF, 0x00 } },
+ };
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b);
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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 { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ assert_hook_not_called(ike_rekey);
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ 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();
+
+ /* CREATE_CHILD_SA { N(INVAL_KE) } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ assert_hook_not_called(child_rekey);
+ assert_single_notify(IN, INVALID_KE_PAYLOAD);
+ 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();
+
+ /* delay the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* CREATE_CHILD_SA { SA, Nr, KEr } --> */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_child_sa_count(b, 0);
+ sa = assert_ike_sa_checkout(4, 7, TRUE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(1);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> (delayed) */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ assert_single_payload(IN, PLV2_DELETE);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ sa = assert_ike_sa_checkout(4, 7, FALSE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(2);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ /* the SA is already gone */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+ msg->destroy(msg);
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_not_called(ike_rekey);
+ assert_message_empty(IN);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+
+ /* ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * This is like the rekey collision above, but one peer deletes the
+ * redundant/old SA before the other peer receives the CREATE_CHILD_SA
+ * response:
+ * Peer A Peer B
+ * rekey ----\ /---- rekey
+ * \-----/----> detect collision
+ * detect collision <---------/ /----
+ * -----------/---->
+ * handle delete <---------/------ delete redundant/old SA
+ * ---------/------>
+ * handle rekey <-------/
+ * delete SA ---------------->
+ * <----------------
+ *
+ * If peer B won the collision it deletes the old IKE_SA, in which case
+ * this situation is handled as if peer B was not aware of the collision (see
+ * below). That is, peer A finalizes the rekeying initiated by the peer and
+ * deletes the IKE_SA (it has no way of knowing whether the peer was aware of
+ * the collision or not). Peer B will expect the redundant IKE_SA to get
+ * deleted, but that will never happen if the response arrives after the SA is
+ * already gone. So a job should be queued that deletes it after a while.
+ *
+ * If peer B lost it will switch to the new IKE_SA and delete the redundant
+ * IKE_SA and expect a delete for the old IKE_SA. In this case peer A will
+ * simply retransmit until it receives a response to the rekey request, all the
+ * while ignoring the delete requests for the unknown IKE_SA. Afterwards,
+ * everything works as in a regular collision (however, until peer A receives
+ * the response it will not be able to receive any messages on the new IKE_SA).
+ */
+START_TEST(test_collision_delayed_response)
+{
+ ike_sa_t *a, *b, *sa;
+ message_t *msg, *d;
+ status_t s;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Four nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * IKE_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /----- ...
+ * ... -----\
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted IKE_SAs (either redundant or replaced) */
+ uint32_t del_a_i, del_a_r;
+ uint32_t del_b_i, del_b_r;
+ /* SPIs of the kept IKE_SA */
+ uint32_t spi_i, spi_r;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 2, 4, 6, 3, 5 },
+ };
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b);
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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 { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* delay the CREATE_CHILD_SA response from b to a */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* simplify next steps by checking in original IKE_SAs */
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, a);
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, b);
+ assert_ike_sa_count(2);
+
+ /* CREATE_CHILD_SA { SA, Nr, KEr } --> */
+ assert_hook_rekey(ike_rekey, 1, data[_i].spi_i);
+ /* besides the job that retransmits the delete, we expect a job that
+ * deletes the redundant IKE_SA if we expect the other to delete it */
+ assert_jobs_scheduled(data[_i].del_b_i == 1 ? 2 : 1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ /* if b wins it deletes the SA originally initiated by a */
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r,
+ data[_i].del_b_i != 1);
+ assert_ike_sa_state(sa, IKE_DELETING);
+ assert_child_sa_count(sa, 0);
+ /* a only deletes SAs for which b is responder */
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, FALSE);
+ assert_ike_sa_state(sa, IKE_REKEYED);
+ assert_child_sa_count(sa, 0);
+ sa = assert_ike_sa_checkout(data[_i].spi_i, data[_i].spi_r,
+ data[_i].del_b_i == 1);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(4);
+ assert_scheduler();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ if (data[_i].del_b_i == 1)
+ { /* b won, it deletes the replaced IKE_SA */
+ assert_hook_rekey(ike_rekey, 1, data[_i].spi_i);
+ assert_single_payload(IN, PLV2_DELETE);
+ s = exchange_test_helper->process_message(exchange_test_helper, a,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, a);
+ sa = assert_ike_sa_checkout(data[_i].spi_i, data[_i].spi_r, FALSE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(4);
+ assert_hook();
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_not_called(ike_rekey);
+ assert_message_empty(IN);
+ s = exchange_test_helper->process_message(exchange_test_helper, b,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, b);
+ assert_ike_sa_count(3);
+ assert_hook();
+ /* the job will later remove this redundant IKE_SA on b */
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, FALSE);
+ assert_ike_sa_state(sa, IKE_REKEYED);
+ assert_sa_idle(sa);
+ /* <-- CREATE_CHILD_SA { SA, Nr, KEr } (delayed) */
+ /* the IKE_SA (a) does not exist anymore */
+ msg->destroy(msg);
+ }
+ else
+ { /* b lost, the delete is for the non-existing redundant IKE_SA */
+ d = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, KEr } (delayed) */
+ assert_hook_rekey(ike_rekey, 1, data[_i].spi_i);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ /* as original initiator a is initiator of both SAs it could delete */
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, TRUE);
+ assert_ike_sa_state(sa, IKE_DELETING);
+ assert_child_sa_count(sa, 0);
+ /* this is the redundant SA b is trying to delete */
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r, FALSE);
+ assert_ike_sa_state(sa, IKE_REKEYED);
+ assert_child_sa_count(sa, 0);
+ sa = assert_ike_sa_checkout(data[_i].spi_i, data[_i].spi_r,
+ data[_i].del_a_i == 1);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(6);
+ 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);
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, FALSE);
+ s = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(5);
+ /* <-- INFORMATIONAL { } */
+ assert_message_empty(IN);
+ sa = assert_ike_sa_checkout(data[_i].del_a_i, data[_i].del_a_r, TRUE);
+ s = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(4);
+
+ /* <-- INFORMATIONAL { D } (retransmit/delayed) */
+ assert_single_payload(IN, PLV2_DELETE);
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r, FALSE);
+ s = exchange_test_helper->process_message(exchange_test_helper, sa, d);
+ ck_assert_int_eq(DESTROY_ME, s);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(3);
+ /* INFORMATIONAL { } --> */
+ assert_message_empty(IN);
+ sa = assert_ike_sa_checkout(data[_i].del_b_i, data[_i].del_b_r, TRUE);
+ s = exchange_test_helper->process_message(exchange_test_helper, sa,
+ NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa);
+ assert_ike_sa_count(2);
+ /* ike_rekey */
+ assert_hook();
+ }
+
+ /* ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * In this scenario one of the peers does not notice that there is a rekey
+ * collision because the other request is dropped:
+ *
+ * rekey ----\ /---- rekey
+ * \ /
+ * detect collision <-----\---/
+ * -------\-------->
+ * detect collision <-------\-------- delete old SA
+ * delete ---------\------>
+ * rekey done \-----> SA not found (or it never arrives)
+ */
+START_TEST(test_collision_dropped_request)
+{
+ ike_sa_t *a, *b, *sa;
+ message_t *msg;
+ status_t s;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Three nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * N3/5 <-----\--/
+ * ... -----\ \-------> ...
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ /* SPIs of the deleted IKE_SAs (either redundant or replaced) */
+ uint32_t del_a_i, del_a_r;
+ uint32_t del_b_i, del_b_r;
+ /* SPIs of the kept IKE_SA */
+ uint32_t spi_i, spi_r;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0x00, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ { { 0xFF, 0xFF, 0x00 }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0xFF, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ };
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a);
+ /* drop the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+ msg->destroy(msg);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b);
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_child_sa_count(b, 0);
+ sa = assert_ike_sa_checkout(4, 5, TRUE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(1);
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ assert_single_payload(IN, PLV2_DELETE);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ sa = assert_ike_sa_checkout(4, 5, FALSE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(2);
+ assert_hook();
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_not_called(ike_rekey);
+ assert_message_empty(IN);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+
+ /* ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * In this scenario one of the peers does not notice that there is a rekey
+ * collision because the other request is delayed:
+ *
+ * rekey ----\ /---- rekey
+ * \ /
+ * detect collision <-----\---/
+ * -------\-------->
+ * \ /---- delete old SA
+ * \-/----> detect collision
+ * detect collision <---------/ /---- TEMP_FAIL
+ * delete -----------/---->
+ * rekey done /
+ * sa already gone <--------/
+ */
+START_TEST(test_collision_delayed_request)
+{
+ ike_sa_t *a, *b, *sa;
+ message_t *msg;
+ status_t s;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Three nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * N3/5 <-----\--/
+ * ... -----\ \-------> ...
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ /* SPIs of the deleted IKE_SAs (either redundant or replaced) */
+ uint32_t del_a_i, del_a_r;
+ uint32_t del_b_i, del_b_r;
+ /* SPIs of the kept IKE_SA */
+ uint32_t spi_i, spi_r;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0x00, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ { { 0xFF, 0xFF, 0x00 }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0xFF, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ };
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b);
+
+ /* delay the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_child_sa_count(b, 0);
+ sa = assert_ike_sa_checkout(4, 5, TRUE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(1);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> (delayed) */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ assert_single_payload(IN, PLV2_DELETE);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ sa = assert_ike_sa_checkout(4, 5, FALSE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(2);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ /* the SA is already gone */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+ msg->destroy(msg);
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_not_called(ike_rekey);
+ assert_message_empty(IN);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+
+ /* ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * In this scenario one of the peers does not notice that there is a rekey
+ * collision and the delete arrives after the TEMPORARY_FAILURE notify:
+ *
+ * rekey ----\ /---- rekey
+ * \ /
+ * detect collision <-----\---/
+ * -------\-------->
+ * \ /---- delete old SA
+ * \-/----> detect collision
+ * no reschedule <---------/------ TEMP_FAIL
+ * detect collision <--------/
+ * delete ---------------->
+ * rekey done
+ */
+START_TEST(test_collision_delayed_request_and_delete)
+{
+ ike_sa_t *a, *b, *sa;
+ message_t *msg;
+ status_t s;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Three nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * N3/5 <-----\--/
+ * ... -----\ \-------> ...
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ /* SPIs of the deleted IKE_SAs (either redundant or replaced) */
+ uint32_t del_a_i, del_a_r;
+ uint32_t del_b_i, del_b_r;
+ /* SPIs of the kept IKE_SA */
+ uint32_t spi_i, spi_r;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0x00, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ { { 0xFF, 0xFF, 0x00 }, 3, 5, 1, 2, 4, 6 },
+ { { 0xFF, 0xFF, 0xFF }, 1, 2, 4, 6, 3, 5 },
+ };
+ /* these should never get called as this results in a successful rekeying */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b);
+
+ /* delay the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(ike_rekey);
+ 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();
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_child_sa_count(b, 0);
+ sa = assert_ike_sa_checkout(4, 5, TRUE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(1);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> (delayed) */
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* delay the INFORMATIONAL request from b to a */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_hook_rekey(ike_rekey, 1, 4);
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_ike_sa_state(a, IKE_REKEYED);
+ assert_child_sa_count(a, 0);
+ sa = assert_ike_sa_checkout(4, 5, FALSE);
+ assert_ike_sa_state(sa, IKE_ESTABLISHED);
+ assert_child_sa_count(sa, 1);
+ assert_ike_sa_count(2);
+ assert_scheduler();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } (delayed) */
+ assert_single_payload(IN, PLV2_DELETE);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_not_called(ike_rekey);
+ assert_message_empty(IN);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+
+ /* ike_updown/child_updown */
+ assert_hook();
+ assert_hook();
+
+ charon->ike_sa_manager->flush(charon->ike_sa_manager);
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a DELETE of the IKE_SA the other peer is
+ * concurrently trying to rekey.
+ *
+ * rekey ----\ /---- delete
+ * \-----/----> detect collision
+ * detect collision <---------/ /---- TEMP_FAIL
+ * delete ----\ /
+ * \----/----->
+ * sa already gone <--------/
+ */
+START_TEST(test_collision_delete)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+ status_t s;
+
+ 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);
+ }
+ /* this should never get called as this does not result in a successful
+ * rekeying on either side */
+ assert_hook_not_called(ike_rekey);
+
+ initiate_rekey(a);
+ call_ikesa(b, delete);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* RFC 7296, 2.25.2: If a peer receives a request to rekey an IKE SA that
+ * it is currently trying to close, it SHOULD reply with TEMPORARY_FAILURE.
+ */
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ assert_hook_not_called(ike_updown);
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_ike_sa_count(0);
+ assert_hook();
+
+ /* RFC 7296, 2.25.2: If a peer receives a request to close an IKE SA that
+ * it is currently rekeying, it SHOULD reply as usual, and forget its own
+ * rekeying request.
+ */
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_message_empty(OUT);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ /* the SA is already gone */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+ msg->destroy(msg);
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* ike_rekey */
+ assert_hook();
+}
+END_TEST
+
+/**
+ * One of the hosts initiates a DELETE of the IKE_SA the other peer is
+ * concurrently trying to rekey. However, the delete request is delayed or
+ * dropped, so the peer doing the rekeying is unaware of the collision.
+ *
+ * rekey ----\ /---- delete
+ * \-----/----> detect collision
+ * reschedule <---------/------ TEMP_FAIL
+ * <--------/
+ * delete ---------------->
+ */
+START_TEST(test_collision_delete_drop_delete)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+ status_t s;
+
+ 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);
+ }
+ /* this should never get called as this does not result in a successful
+ * rekeying on either side */
+ assert_hook_not_called(ike_rekey);
+
+ initiate_rekey(a);
+ call_ikesa(b, delete);
+ assert_ike_sa_state(b, IKE_DELETING);
+
+ /* RFC 7296, 2.25.2: If a peer receives a request to rekey an IKE SA that
+ * it is currently trying to close, it SHOULD reply with TEMPORARY_FAILURE.
+ */
+
+ /* CREATE_CHILD_SA { SA, Ni, KEi } --> */
+ assert_hook_not_called(ike_updown);
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_ike_sa_state(b, IKE_DELETING);
+ assert_ike_sa_count(0);
+ assert_hook();
+
+ /* delay the DELETE request */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_hook_not_called(ike_updown);
+ assert_hook_not_called(child_updown);
+ /* we expect a job to retry the rekeying is scheduled */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_ike_sa_state(a, IKE_ESTABLISHED);
+ assert_scheduler();
+ assert_hook();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } (delayed) */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_message_empty(OUT);
+ s = exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(a, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_updown(ike_updown, FALSE);
+ assert_hook_updown(child_updown, FALSE);
+ s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ ck_assert_int_eq(DESTROY_ME, s);
+ call_ikesa(b, destroy);
+ assert_hook();
+ assert_hook();
+
+ /* ike_rekey */
+ assert_hook();
+}
+END_TEST
+
+Suite *ike_rekey_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("ike rekey");
+
+ tc = tcase_create("regular");
+ tcase_add_loop_test(tc, test_regular, 0, 2);
+ tcase_add_loop_test(tc, test_regular_ke_invalid, 0, 2);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions rekey");
+ tcase_add_loop_test(tc, test_collision, 0, 4);
+ tcase_add_loop_test(tc, test_collision_ke_invalid, 0, 4);
+ tcase_add_loop_test(tc, test_collision_ke_invalid_delayed_retry, 0, 3);
+ tcase_add_loop_test(tc, test_collision_delayed_response, 0, 4);
+ tcase_add_loop_test(tc, test_collision_dropped_request, 0, 3);
+ tcase_add_loop_test(tc, test_collision_delayed_request, 0, 3);
+ tcase_add_loop_test(tc, test_collision_delayed_request_and_delete, 0, 3);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("collisions delete");
+ tcase_add_loop_test(tc, test_collision_delete, 0, 2);
+ tcase_add_loop_test(tc, test_collision_delete_drop_delete, 0, 2);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/src/libcharon/tests/suites/test_message_chapoly.c b/src/libcharon/tests/suites/test_message_chapoly.c
index e871cf6c2..f4a74abb4 100644
--- a/src/libcharon/tests/suites/test_message_chapoly.c
+++ b/src/libcharon/tests/suites/test_message_chapoly.c
@@ -40,7 +40,7 @@ METHOD(aead_t, get_iv_gen, iv_gen_t*,
}
METHOD(iv_gen_t, get_iv, bool,
- iv_gen_t *this, u_int64_t seq, size_t size, u_int8_t *buffer)
+ iv_gen_t *this, uint64_t seq, size_t size, uint8_t *buffer)
{
if (size != 8)
{
@@ -51,7 +51,7 @@ METHOD(iv_gen_t, get_iv, bool,
}
METHOD(iv_gen_t, allocate_iv, bool,
- iv_gen_t *this, u_int64_t seq, size_t size, chunk_t *chunk)
+ iv_gen_t *this, uint64_t seq, size_t size, chunk_t *chunk)
{
if (size != 8)
{
@@ -66,10 +66,10 @@ METHOD(iv_gen_t, allocate_iv, bool,
*/
START_TEST(test_chacha20poly1305)
{
- u_int64_t spii, spir;
+ uint64_t spii, spir;
ike_sa_id_t *id;
message_t *m;
- u_int32_t window = htonl(10);
+ uint32_t window = htonl(10);
chunk_t chunk, exp;
keymat_t keymat = {
.get_version = _get_version,
diff --git a/src/libcharon/tests/suites/test_proposal.c b/src/libcharon/tests/suites/test_proposal.c
new file mode 100644
index 000000000..a6226f68f
--- /dev/null
+++ b/src/libcharon/tests/suites/test_proposal.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "test_suite.h"
+
+#include <config/proposal.h>
+
+static struct {
+ char *self;
+ char *other;
+ char *expected;
+} select_data[] = {
+ { "aes128", "aes128", "aes128" },
+ { "aes128", "aes256", NULL },
+ { "aes128-aes256", "aes256-aes128", "aes128" },
+ { "aes256-aes128", "aes128-aes256", "aes256" },
+ { "aes128-aes256-sha1-sha256", "aes256-aes128-sha256-sha1", "aes128-sha1" },
+ { "aes256-aes128-sha256-sha1", "aes128-aes256-sha1-sha256", "aes256-sha256" },
+ { "aes128-sha256-modp3072", "aes128-sha256", NULL },
+ { "aes128-sha256", "aes128-sha256-modp3072", NULL },
+ { "aes128-sha256-modp3072", "aes128-sha256-modpnone", NULL },
+ { "aes128-sha256-modpnone", "aes128-sha256-modp3072", NULL },
+ { "aes128-sha256-modp3072-modpnone", "aes128-sha256", "aes128-sha256" },
+ { "aes128-sha256", "aes128-sha256-modp3072-modpnone", "aes128-sha256" },
+ { "aes128-sha256-modp3072-modpnone", "aes128-sha256-modpnone-modp3072", "aes128-sha256-modp3072" },
+ { "aes128-sha256-modpnone-modp3072", "aes128-sha256-modp3072-modpnone", "aes128-sha256-modpnone" },
+};
+
+START_TEST(test_select)
+{
+ proposal_t *self, *other, *selected, *expected;
+
+ self = proposal_create_from_string(PROTO_ESP,
+ select_data[_i].self);
+ other = proposal_create_from_string(PROTO_ESP,
+ select_data[_i].other);
+ selected = self->select(self, other, FALSE);
+ if (select_data[_i].expected)
+ {
+ expected = proposal_create_from_string(PROTO_ESP,
+ select_data[_i].expected);
+ ck_assert(selected);
+ ck_assert_msg(expected->equals(expected, selected), "proposal %P does "
+ "not match expected %P", selected, expected);
+ expected->destroy(expected);
+ }
+ else
+ {
+ ck_assert(!selected);
+ }
+ DESTROY_IF(selected);
+ other->destroy(other);
+ self->destroy(self);
+}
+END_TEST
+
+Suite *proposal_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("proposal");
+
+ tc = tcase_create("select");
+ tcase_add_loop_test(tc, test_select, 0, countof(select_data));
+ suite_add_tcase(s, tc);
+
+ return s;
+}