diff options
author | Yves-Alexis Perez <corsac@debian.org> | 2016-07-16 15:19:53 +0200 |
---|---|---|
committer | Yves-Alexis Perez <corsac@debian.org> | 2016-07-16 15:19:53 +0200 |
commit | bf372706c469764d59e9f29c39e3ecbebd72b8d2 (patch) | |
tree | 0f0e296e2d50e4a7faf99ae6fa428d2681e81ea1 /src/libcharon/tests/suites | |
parent | 518dd33c94e041db0444c7d1f33da363bb8e3faf (diff) | |
download | vyos-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.c | 106 | ||||
-rw-r--r-- | src/libcharon/tests/suites/test_child_delete.c | 366 | ||||
-rw-r--r-- | src/libcharon/tests/suites/test_child_rekey.c | 1569 | ||||
-rw-r--r-- | src/libcharon/tests/suites/test_ike_delete.c | 137 | ||||
-rw-r--r-- | src/libcharon/tests/suites/test_ike_rekey.c | 1480 | ||||
-rw-r--r-- | src/libcharon/tests/suites/test_message_chapoly.c | 8 | ||||
-rw-r--r-- | src/libcharon/tests/suites/test_proposal.c | 81 |
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; +} |