From 3be622ad230c70e9753f9f9737333a2f803b125e Mon Sep 17 00:00:00 2001 From: sairoop-elear Date: Fri, 12 Apr 2019 17:06:15 +0530 Subject: [PATCH] Add unit test cases for encrypted storage key rotation --- src/devtools.c | 1 + src/devtools.h | 9 + src/meshlink.c | 16 + src/meshlink.h | 13 + src/meshlink.sym | 2 + test/blackbox/run_blackbox_tests/Makefile.am | 3 +- .../run_blackbox_tests/run_blackbox_tests.c | 2 + .../test_cases_key_rotation.c | 514 ++++++++++++++++++ .../test_cases_key_rotation.h | 26 + 9 files changed, 585 insertions(+), 1 deletion(-) create mode 100644 test/blackbox/run_blackbox_tests/test_cases_key_rotation.c create mode 100644 test/blackbox/run_blackbox_tests/test_cases_key_rotation.h diff --git a/src/devtools.c b/src/devtools.c index f7d388ca..2aba935b 100644 --- a/src/devtools.c +++ b/src/devtools.c @@ -35,6 +35,7 @@ static void nop_probe(void) { } void (*devtool_trybind_probe)(void) = nop_probe; +void (*devtool_keyrotate_probe)(int stage) = nop_probe; /* Return an array of edges in the current network graph. * Data captures the current state and will not be updated. diff --git a/src/devtools.h b/src/devtools.h index 04a33452..1a808e04 100644 --- a/src/devtools.h +++ b/src/devtools.h @@ -159,4 +159,13 @@ extern meshlink_handle_t *devtool_open_in_netns(const char *confbase, const char */ extern void (*devtool_trybind_probe)(void); +/// Debug function pointer variable for encrypted key rotate API +/** This function pointer variable is a userspace tracepoint or debugger callback for + * encrypted key rotation function @a meshlink_encrypted_key_rotate @a. + * On assigning a debug function variable invokes callback for each stage from the key rotate API. + * + * @param stage Debug stage number. + */ +extern void (*devtool_keyrotate_probe)(int stage); + #endif diff --git a/src/meshlink.c b/src/meshlink.c index 134abd83..89baa328 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -1051,6 +1051,22 @@ bool meshlink_open_params_set_storage_key(meshlink_open_params_t *params, const return true; } +bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key, size_t new_keylen) { + + // While copying old config files to new config files + devtool_keyrotate_probe(1); + // After completed creating new config files in confbase/new/ + devtool_keyrotate_probe(2); + // Rename confbase/current to confbase/old/ + devtool_keyrotate_probe(3); + // Rename confbase/new/ to confbase/current + devtool_keyrotate_probe(4); + // Before deleting old sub-directory + devtool_keyrotate_probe(5); + + return false; +} + void meshlink_open_params_free(meshlink_open_params_t *params) { if(!params) { meshlink_errno = MESHLINK_EINVAL; diff --git a/src/meshlink.h b/src/meshlink.h index 96f2d183..6a1c758a 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -1176,6 +1176,19 @@ extern void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node */ extern void meshlink_enable_discovery(meshlink_handle_t *mesh, bool enable); +/// Performs key rotation for an encrypted storage + +/** This rotates the (master) key for an encrypted storage and discards the old key + * if the call succeeded. This is an atomic call. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param key A pointer to the new key used to encrypt storage. + * @param keylen The length of the new key in bytes. + * + * @return This function returns true if the key rotation for the encrypted storage succeeds, false otherwise. + */ +extern bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key, size_t new_keylen); + #ifdef __cplusplus } #endif diff --git a/src/meshlink.sym b/src/meshlink.sym index 10802769..6bf27627 100644 --- a/src/meshlink.sym +++ b/src/meshlink.sym @@ -3,6 +3,7 @@ devtool_export_json_all_edges_state devtool_get_all_edges devtool_get_all_submeshes devtool_get_node_status +devtool_keyrotate_probe devtool_open_in_netns devtool_trybind_probe meshlink_add_address @@ -19,6 +20,7 @@ meshlink_channel_shutdown meshlink_close meshlink_destroy meshlink_enable_discovery +meshlink_encrypted_key_rotate meshlink_errno meshlink_export meshlink_get_all_nodes diff --git a/test/blackbox/run_blackbox_tests/Makefile.am b/test/blackbox/run_blackbox_tests/Makefile.am index 379ce46a..517ad9ab 100644 --- a/test/blackbox/run_blackbox_tests/Makefile.am +++ b/test/blackbox/run_blackbox_tests/Makefile.am @@ -64,7 +64,8 @@ run_blackbox_tests_SOURCES = \ test_cases_autoconnect.c \ test_cases_set_connection_try_cb.c \ test_cases_random_port_bindings01.c \ - test_cases_random_port_bindings02.c + test_cases_random_port_bindings02.c \ + test_cases_key_rotation.c run_blackbox_tests_LDADD = ../../../src/libmeshlink.la $(LXC_LIBS) $(CMOCKA_LIBS) run_blackbox_tests_CFLAGS = -D_GNU_SOURCE $(LXC_CFLAGS) $(CMOCKA_CFLAGS) diff --git a/test/blackbox/run_blackbox_tests/run_blackbox_tests.c b/test/blackbox/run_blackbox_tests/run_blackbox_tests.c index 62387c94..a1eecb51 100644 --- a/test/blackbox/run_blackbox_tests/run_blackbox_tests.c +++ b/test/blackbox/run_blackbox_tests/run_blackbox_tests.c @@ -63,6 +63,7 @@ #include "test_cases_channel_set_receive_cb.h" #include "test_cases_hint_address.h" #include "test_optimal_pmtu.h" +#include "test_cases_key_rotation.h" #include "test_cases_channel_conn.h" #include "test_cases_get_all_nodes_by_dev_class.h" @@ -147,6 +148,7 @@ int main(int argc, char *argv[]) { failed_tests += test_cases_connection_try(); failed_tests += test_optimal_pmtu(); + failed_tests += test_meshlink_encrypted_key_rotation(); failed_tests += test_meshlink_random_port_bindings01(); failed_tests += test_meshlink_random_port_bindings02(); diff --git a/test/blackbox/run_blackbox_tests/test_cases_key_rotation.c b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.c new file mode 100644 index 00000000..2768c80c --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.c @@ -0,0 +1,514 @@ +/* + test_cases_key_rotation.c -- Execution of specific meshlink black box test cases + Copyright (C) 2019 Guus Sliepen + + 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. + + 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. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "execute_tests.h" +#include "test_cases_key_rotation.h" +#include "../common/test_step.h" +#include "../common/common_handlers.h" +#include "../../../src/devtools.h" +#include "../../utils.h" + +static void test_case_key_rotation_01(void **state); +static bool test_key_rotation_01(void); +static void test_case_key_rotation_02(void **state); +static bool test_key_rotation_02(void); +static void test_case_key_rotation_03(void **state); +static bool test_key_rotation_03(void); +static void test_case_key_rotation_04(void **state); +static bool test_key_rotation_04(void); +static void test_case_key_rotation_05(void **state); +static bool test_key_rotation_05(void); + +static void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) { + + static const char *levelstr[] = { + [MESHLINK_DEBUG] = "\x1b[34mDEBUG", + [MESHLINK_INFO] = "\x1b[32mINFO", + [MESHLINK_WARNING] = "\x1b[33mWARNING", + [MESHLINK_ERROR] = "\x1b[31mERROR", + [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL", + }; + + fprintf(stderr, "%s(%s):\x1b[0m %s\n", mesh->name, levelstr[level], text); +} + +/* Execute key rotation Test Case # 1 - Sanity test */ +static void test_case_key_rotation_01(void **state) { + execute_test(test_key_rotation_01, state); +} + +/* Test Steps for key rotation Test Case # 1 + + Test Steps: + 1. Open encrypted node instance, call encrypted rotate API with + invalid input parameters to the call. + + Expected Result: + Key rotate should fail when called with invalid parameters. +*/ +static bool test_key_rotation_01(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_destroy("encrypted_conf"); + + // Open a new meshlink instance. + + meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + + // Pass invalid arguments + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, NULL, 5); + assert_int_equal(keyrotation_status, false); + + keyrotation_status = meshlink_encrypted_key_rotate(NULL, "newkey", 6); + assert_int_equal(keyrotation_status, false); + + keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 0); + assert_int_equal(keyrotation_status, false); + + // Cleanup + + meshlink_close(mesh); + meshlink_destroy("encrypted_conf"); + + return true; +} + +/* Execute key rotation Test Case # 2 - Sanity test */ +static void test_case_key_rotation_02(void **state) { + execute_test(test_key_rotation_02, state); +} + +/* Test Steps for key rotation Test Case # 2 + + Test Steps: + 1. Open encrypted node instance, rotate it's key with a newkey and close the node. + 2. Reopen the encrypted node instance with the newkey + + Expected Result: + Opening encrypted node instance should succeed when tried to open with newkey that's + been changed to new by key rotate API. +*/ +static bool test_key_rotation_02(void) { + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + meshlink_destroy("encrypted_conf"); + + // Open a new meshlink instance. + + meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + + // Set a new port for the mesh + + int port = 0x1000 + (rand() & 0x7fff); + assert_int_equal(meshlink_set_port(mesh, port), true); + + // Key rotate the encrypted_conf storage with new key + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6); + assert_int_equal(keyrotation_status, true); + + meshlink_close(mesh); + + // Reopen the meshlink instance with the new key + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + assert_int_not_equal(mesh, NULL); + + // Validate the port number that we changed in the last run. + + assert_int_equal(meshlink_get_port(mesh), port); + + // Cleanup + + meshlink_close(mesh); + meshlink_destroy("encrypted_conf"); + + return true; +} + +/* Execute key rotation Test Case # 3 - Sanity test */ +static void test_case_key_rotation_03(void **state) { + execute_test(test_key_rotation_03, state); +} + +/* Test Steps for key rotation Test Case # 3 + + Test Steps: + 1. Open encrypted node instance, rotate it's key with a newkey and close the node. + 2. Reopen the encrypted node instance with the oldkey + + Expected Result: + Opening encrypted node instance should fail when tried to open with oldkey that's + been changed to new by key rotate API. +*/ +static bool test_key_rotation_03(void) { + meshlink_destroy("encrypted_conf"); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open a new meshlink instance. + + meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + + // Key rotate the encrypted_conf storage with new key + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6); + assert_int_equal(keyrotation_status, true); + + meshlink_close(mesh); + + // Reopen the meshlink instance with the new key + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_equal(mesh, NULL); + + // Cleanup + + meshlink_destroy("encrypted_conf"); + + return true; +} + +/* Execute key rotation Test Case # 4 - Sanity test */ +static void test_case_key_rotation_04(void **state) { + execute_test(test_key_rotation_04, state); +} + +/* Test Steps for key rotation Test Case # 4 + Verify whether key rotation API gracefully handles invitations porting from + old key to new key. + + Test Steps: + 1. Open foo node instance and generate invitations for peer and bar. + 2. Do key rotation with newkey and verify invitation timestamps post key rotation. + 3. Change timestamp of peer key to expire and Open instances of foo, bar and peer nodes + and try to join bar and peer node. + + Expected Result: + Key rotation API should never change the any file status attributes of an invitation file. +*/ +static bool test_key_rotation_04(void) { + meshlink_handle_t *mesh; + meshlink_handle_t *mesh1; + meshlink_handle_t *mesh2; + struct dirent *ent; + DIR *d; + char invitation_path_buff[500]; + struct stat temp_stat; + struct stat peer_stat; + struct utimbuf timebuf; + bool join_status; + char *invitations_directory_path = "encrypted_conf/current/invitations/"; + + meshlink_destroy("encrypted_conf"); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + // Open a new meshlink instance. + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + + // Generate invitations + + char *invitation1 = meshlink_invite(mesh, NULL, "peer"); + assert_int_not_equal(invitation1, NULL); + + // Read the peer invitation file status structure + + strcpy(invitation_path_buff, invitations_directory_path); + d = opendir(invitation_path_buff); + assert(d); + + while((ent = readdir(d)) != NULL) { + if(ent->d_name[0] == '.') { + continue; + } + + strcpy(invitation_path_buff, invitations_directory_path); + strcat(invitation_path_buff, ent->d_name); + assert(stat(invitation_path_buff, &temp_stat) != -1); + + if((temp_stat.st_mode & S_IFMT) == S_IFREG) { + break; + } + } + + assert(ent); + + closedir(d); + + char *invitation2 = meshlink_invite(mesh, NULL, "bar"); + assert_int_not_equal(invitation2, NULL); + + // Key rotate the encrypted_conf storage with new key + + bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6); + assert_int_equal(keyrotation_status, true); + + meshlink_close(mesh); + + // Compare invitation file timestamps of old key with new key + + assert(stat(invitation_path_buff, &peer_stat) != -1); + assert_int_equal(peer_stat.st_mtime, temp_stat.st_mtime); + + // Change timestamp for @ peer @ node invitation + + timebuf.actime = peer_stat.st_atime; + timebuf.modtime = peer_stat.st_mtime - 604805; // > 1 week + + assert(utime(invitation_path_buff, &timebuf) != -1); + + + // Reopen the meshlink instance with the new key + + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + assert_int_not_equal(mesh, NULL); + + mesh1 = meshlink_open("encrypted_conf.1", "peer", "encrypted", DEV_CLASS_BACKBONE); + assert_int_not_equal(mesh1, NULL); + + mesh2 = meshlink_open("encrypted_conf.2", "bar", "encrypted", DEV_CLASS_BACKBONE); + assert_int_not_equal(mesh2, NULL); + + assert(meshlink_start(mesh)); + + join_status = meshlink_join(mesh1, invitation1); + assert_int_equal(join_status, false); + + join_status = meshlink_join(mesh2, invitation2); + assert_int_equal(join_status, true); + + // Cleanup + + free(invitation1); + free(invitation2); + meshlink_close(mesh); + meshlink_close(mesh1); + meshlink_close(mesh2); + meshlink_destroy("encrypted_conf"); + meshlink_destroy("encrypted_conf.1"); + meshlink_destroy("encrypted_conf.2"); + + return true; +} + +/* Execute key rotation Test Case # 5 - Atomicity test */ +static void test_case_key_rotation_05(void **state) { + execute_test(test_key_rotation_05, state); +} + +static int break_stage; + +static void nop_stage(int stage) { + return; +} + +static void debug_probe(int stage) { + + // Terminate the node at the specified stage (by @ break_stage @ ) + if(stage == break_stage) { + raise(SIGINT); + } else if((break_stage < 1) || (break_stage > 11)) { + fprintf(stderr, "INVALID stage break\n"); + raise(SIGABRT); + } + + return; +} + +/* Test Steps for key rotation Test Case # 5 + Debug all stages of key rotate API and verify it's atomicity + + Test Steps: + 1. Open foo node instance. + 2. In a loop break meshlink node instance at each stage incrementally + in a fork process + 3. Reopen node instance post termination. + + Expected Result: + Terminating node instance when meshlink_encrypted_key_rotate function called + at any stage should give atomic result when reopened. +*/ +static bool test_key_rotation_05(void) { + pid_t pid; + int status; + meshlink_handle_t *mesh; + meshlink_destroy("encrypted_conf"); + meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb); + + assert(signal(SIGINT, SIG_DFL) != SIG_ERR); + assert(signal(SIGABRT, SIG_DFL) != SIG_ERR); + + // Set debug_probe callback + + devtool_keyrotate_probe = debug_probe; + int new_port = 12000; + int pipefd[2]; + + // incrementally debug meshlink_encrypted_key_rotate API atomicity + + for(break_stage = 1; break_stage <= 10; break_stage += 1) { + fprintf(stderr, "Debugging stage %d\n", break_stage); + meshlink_destroy("encrypted_conf"); + + assert(pipe(pipefd) != -1); + + pid = fork(); + assert(pid != -1); + + if(!pid) { + close(pipefd[0]); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert(mesh); + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + meshlink_enable_discovery(mesh, false); + + assert(meshlink_set_port(mesh, new_port)); + + char *invitation = meshlink_invite(mesh, NULL, "bar"); + assert(invitation); + + assert(write(pipefd[1], invitation, strlen(invitation) + 1) != -1); + + meshlink_encrypted_key_rotate(mesh, "newkey", 6); + raise(SIGABRT); + } + + close(pipefd[1]); + + // Wait for child exit and verify which signal terminated it + + assert(waitpid(pid, &status, 0) != -1); + assert_int_equal(WIFSIGNALED(status), true); + assert_int_equal(WTERMSIG(status), SIGINT); + + // Reopen the node with invalid key other than old and new key should fail and should not affect + // the existing confbase + + fprintf(stderr, "Opening mesh with invalid key\n"); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "invalidkey", 9); + assert_int_equal(mesh, NULL); + + // Reopen the node with the "newkey", if it failed to open with "newkey" then + // opening with the "oldkey" should succeed + + fprintf(stderr, "Opening mesh with new-key\n"); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6); + + if(!mesh) { + fprintf(stderr, "Opening mesh with new-key failed trying to open with old-key\n"); + mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6); + assert_int_not_equal(mesh, NULL); + } + + meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb); + meshlink_enable_discovery(mesh, false); + + // Verify the newly set port and generated invitation + + int get_port = meshlink_get_port(mesh); + assert_int_equal(get_port, new_port); + + char invitation[200]; + assert(read(pipefd[0], invitation, sizeof(invitation)) != -1); + + assert(meshlink_start(mesh)); + + meshlink_destroy("encrypted_conf.1"); + + set_sync_flag(&status_changed_cond, false); + bar_reachable = false; + + meshlink_handle_t *mesh2 = meshlink_open("encrypted_conf.1", "bar", "bar", DEV_CLASS_BACKBONE); + assert(mesh2); + + meshlink_set_node_status_cb(mesh2, node_status_cb); + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb); + meshlink_enable_discovery(mesh2, false); + + assert_int_equal(meshlink_join(mesh2, invitation), true); + + // cleanup + + meshlink_close(mesh); + meshlink_close(mesh2); + + close(pipefd[0]); + } + + // Cleanup + + meshlink_destroy("encrypted_conf"); + meshlink_destroy("encrypted_conf.1"); + devtool_keyrotate_probe = nop_stage; + return true; +} + +int test_meshlink_encrypted_key_rotation(void) { + /* State structures for key rotation Test Cases */ + black_box_state_t test_case_key_rotation_01_state = { + .test_case_name = "test_case_key_rotation_01", + }; + black_box_state_t test_case_key_rotation_02_state = { + .test_case_name = "test_case_key_rotation_02", + }; + black_box_state_t test_case_key_rotation_03_state = { + .test_case_name = "test_case_key_rotation_03", + }; + black_box_state_t test_case_key_rotation_04_state = { + .test_case_name = "test_case_key_rotation_04", + }; + black_box_state_t test_case_key_rotation_05_state = { + .test_case_name = "test_case_key_rotation_05", + }; + + const struct CMUnitTest blackbox_status_tests[] = { + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_01, NULL, NULL, + (void *)&test_case_key_rotation_01_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_02, NULL, NULL, + (void *)&test_case_key_rotation_02_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_03, NULL, NULL, + (void *)&test_case_key_rotation_03_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_04, NULL, NULL, + (void *)&test_case_key_rotation_04_state), + cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_05, NULL, NULL, + (void *)&test_case_key_rotation_05_state), + }; + total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]); + + return cmocka_run_group_tests(blackbox_status_tests, NULL, NULL); +} diff --git a/test/blackbox/run_blackbox_tests/test_cases_key_rotation.h b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.h new file mode 100644 index 00000000..1562b245 --- /dev/null +++ b/test/blackbox/run_blackbox_tests/test_cases_key_rotation.h @@ -0,0 +1,26 @@ +#ifndef TEST_CASES_KEY_ROTATION_H +#define TEST_CASES_KEY_ROTATION_H + +/* + test_cases_key_rotation.h -- Declarations for Individual Test Case implementation functions + Copyright (C) 2019 Guus Sliepen + + 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. + + 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. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +extern int test_meshlink_encrypted_key_rotation(void); +extern int total_tests; + +#endif // TEST_CASES_KEY_ROTATION_H -- 2.39.2