2 test_cases_key_rotation.c -- Execution of specific meshlink black box test cases
3 Copyright (C) 2019 Guus Sliepen <guus@meshlink.io>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 #include <sys/types.h>
35 #include "execute_tests.h"
36 #include "test_cases_key_rotation.h"
37 #include "../common/test_step.h"
38 #include "../common/common_handlers.h"
39 #include "../../../src/devtools.h"
40 #include "../../utils.h"
42 static void test_case_key_rotation_01(void **state);
43 static bool test_key_rotation_01(void);
44 static void test_case_key_rotation_02(void **state);
45 static bool test_key_rotation_02(void);
46 static void test_case_key_rotation_03(void **state);
47 static bool test_key_rotation_03(void);
48 static void test_case_key_rotation_04(void **state);
49 static bool test_key_rotation_04(void);
50 static void test_case_key_rotation_05(void **state);
51 static bool test_key_rotation_05(void);
53 static void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
55 static const char *levelstr[] = {
56 [MESHLINK_DEBUG] = "\x1b[34mDEBUG",
57 [MESHLINK_INFO] = "\x1b[32mINFO",
58 [MESHLINK_WARNING] = "\x1b[33mWARNING",
59 [MESHLINK_ERROR] = "\x1b[31mERROR",
60 [MESHLINK_CRITICAL] = "\x1b[31mCRITICAL",
63 fprintf(stderr, "%s(%s):\x1b[0m %s\n", mesh->name, levelstr[level], text);
66 /* Execute key rotation Test Case # 1 - Sanity test */
67 static void test_case_key_rotation_01(void **state) {
68 execute_test(test_key_rotation_01, state);
71 /* Test Steps for key rotation Test Case # 1
74 1. Open encrypted node instance, call encrypted rotate API with
75 invalid input parameters to the call.
78 Key rotate should fail when called with invalid parameters.
80 static bool test_key_rotation_01(void) {
81 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
82 meshlink_destroy("encrypted_conf");
84 // Open a new meshlink instance.
86 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
87 assert_int_not_equal(mesh, NULL);
89 // Pass invalid arguments
91 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, NULL, 5);
92 assert_int_equal(keyrotation_status, false);
94 keyrotation_status = meshlink_encrypted_key_rotate(NULL, "newkey", 6);
95 assert_int_equal(keyrotation_status, false);
97 keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 0);
98 assert_int_equal(keyrotation_status, false);
102 meshlink_close(mesh);
103 meshlink_destroy("encrypted_conf");
108 /* Execute key rotation Test Case # 2 - Sanity test */
109 static void test_case_key_rotation_02(void **state) {
110 execute_test(test_key_rotation_02, state);
113 /* Test Steps for key rotation Test Case # 2
116 1. Open encrypted node instance, rotate it's key with a newkey and close the node.
117 2. Reopen the encrypted node instance with the newkey
120 Opening encrypted node instance should succeed when tried to open with newkey that's
121 been changed to new by key rotate API.
123 static bool test_key_rotation_02(void) {
124 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
125 meshlink_destroy("encrypted_conf");
127 // Open a new meshlink instance.
129 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
130 assert_int_not_equal(mesh, NULL);
131 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
133 // Set a new port for the mesh
135 int port = 0x1000 + (rand() & 0x7fff);
136 assert_int_equal(meshlink_set_port(mesh, port), true);
138 // Key rotate the encrypted_conf storage with new key
140 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
141 assert_int_equal(keyrotation_status, true);
143 meshlink_close(mesh);
145 // Reopen the meshlink instance with the new key
147 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
148 assert_int_not_equal(mesh, NULL);
150 // Validate the port number that we changed in the last run.
152 assert_int_equal(meshlink_get_port(mesh), port);
156 meshlink_close(mesh);
157 meshlink_destroy("encrypted_conf");
162 /* Execute key rotation Test Case # 3 - Sanity test */
163 static void test_case_key_rotation_03(void **state) {
164 execute_test(test_key_rotation_03, state);
167 /* Test Steps for key rotation Test Case # 3
170 1. Open encrypted node instance, rotate it's key with a newkey and close the node.
171 2. Reopen the encrypted node instance with the oldkey
174 Opening encrypted node instance should fail when tried to open with oldkey that's
175 been changed to new by key rotate API.
177 static bool test_key_rotation_03(void) {
178 meshlink_destroy("encrypted_conf");
179 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
181 // Open a new meshlink instance.
183 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
184 assert_int_not_equal(mesh, NULL);
186 // Key rotate the encrypted_conf storage with new key
188 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
189 assert_int_equal(keyrotation_status, true);
191 meshlink_close(mesh);
193 // Reopen the meshlink instance with the new key
195 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
196 assert_int_equal(mesh, NULL);
200 meshlink_destroy("encrypted_conf");
205 /* Execute key rotation Test Case # 4 - Sanity test */
206 static void test_case_key_rotation_04(void **state) {
207 execute_test(test_key_rotation_04, state);
210 /* Test Steps for key rotation Test Case # 4
211 Verify whether key rotation API gracefully handles invitations porting from
215 1. Open foo node instance and generate invitations for peer and bar.
216 2. Do key rotation with newkey and verify invitation timestamps post key rotation.
217 3. Change timestamp of peer key to expire and Open instances of foo, bar and peer nodes
218 and try to join bar and peer node.
221 Key rotation API should never change the any file status attributes of an invitation file.
223 static bool test_key_rotation_04(void) {
224 meshlink_handle_t *mesh;
225 meshlink_handle_t *mesh1;
226 meshlink_handle_t *mesh2;
229 char invitation_path_buff[500];
230 struct stat temp_stat;
231 struct stat peer_stat;
232 struct utimbuf timebuf;
234 char *invitations_directory_path = "encrypted_conf/current/invitations/";
236 meshlink_destroy("encrypted_conf");
237 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
239 // Open a new meshlink instance.
241 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
242 assert_int_not_equal(mesh, NULL);
244 // Generate invitations
246 char *invitation1 = meshlink_invite(mesh, NULL, "peer");
247 assert_int_not_equal(invitation1, NULL);
249 // Read the peer invitation file status structure
251 strcpy(invitation_path_buff, invitations_directory_path);
252 d = opendir(invitation_path_buff);
255 while((ent = readdir(d)) != NULL) {
256 if(ent->d_name[0] == '.') {
260 strcpy(invitation_path_buff, invitations_directory_path);
261 strcat(invitation_path_buff, ent->d_name);
262 assert(stat(invitation_path_buff, &temp_stat) != -1);
264 if((temp_stat.st_mode & S_IFMT) == S_IFREG) {
273 char *invitation2 = meshlink_invite(mesh, NULL, "bar");
274 assert_int_not_equal(invitation2, NULL);
276 // Key rotate the encrypted_conf storage with new key
278 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
279 assert_int_equal(keyrotation_status, true);
281 meshlink_close(mesh);
283 // Compare invitation file timestamps of old key with new key
285 assert(stat(invitation_path_buff, &peer_stat) != -1);
286 assert_int_equal(peer_stat.st_mtime, temp_stat.st_mtime);
288 // Change timestamp for @ peer @ node invitation
290 timebuf.actime = peer_stat.st_atime;
291 timebuf.modtime = peer_stat.st_mtime - 604805; // > 1 week
293 assert(utime(invitation_path_buff, &timebuf) != -1);
296 // Reopen the meshlink instance with the new key
298 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
299 assert_int_not_equal(mesh, NULL);
301 mesh1 = meshlink_open("encrypted_conf.1", "peer", "encrypted", DEV_CLASS_BACKBONE);
302 assert_int_not_equal(mesh1, NULL);
304 mesh2 = meshlink_open("encrypted_conf.2", "bar", "encrypted", DEV_CLASS_BACKBONE);
305 assert_int_not_equal(mesh2, NULL);
307 assert(meshlink_start(mesh));
309 join_status = meshlink_join(mesh1, invitation1);
310 assert_int_equal(join_status, false);
312 join_status = meshlink_join(mesh2, invitation2);
313 assert_int_equal(join_status, true);
319 meshlink_close(mesh);
320 meshlink_close(mesh1);
321 meshlink_close(mesh2);
322 meshlink_destroy("encrypted_conf");
323 meshlink_destroy("encrypted_conf.1");
324 meshlink_destroy("encrypted_conf.2");
329 /* Execute key rotation Test Case # 5 - Atomicity test */
330 static void test_case_key_rotation_05(void **state) {
331 execute_test(test_key_rotation_05, state);
334 static int break_stage;
336 static void nop_stage(int stage) {
340 static void debug_probe(int stage) {
342 // Terminate the node at the specified stage (by @ break_stage @ )
343 if(stage == break_stage) {
345 } else if((break_stage < 1) || (break_stage > 11)) {
346 fprintf(stderr, "INVALID stage break\n");
353 /* Test Steps for key rotation Test Case # 5
354 Debug all stages of key rotate API and verify it's atomicity
357 1. Open foo node instance.
358 2. In a loop break meshlink node instance at each stage incrementally
360 3. Reopen node instance post termination.
363 Terminating node instance when meshlink_encrypted_key_rotate function called
364 at any stage should give atomic result when reopened.
366 static bool test_key_rotation_05(void) {
369 meshlink_handle_t *mesh;
370 meshlink_destroy("encrypted_conf");
371 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
373 assert(signal(SIGINT, SIG_DFL) != SIG_ERR);
374 assert(signal(SIGABRT, SIG_DFL) != SIG_ERR);
376 // Set debug_probe callback
378 devtool_keyrotate_probe = debug_probe;
379 int new_port = 12000;
382 // incrementally debug meshlink_encrypted_key_rotate API atomicity
384 for(break_stage = 1; break_stage <= 10; break_stage += 1) {
385 fprintf(stderr, "Debugging stage %d\n", break_stage);
386 meshlink_destroy("encrypted_conf");
388 assert(pipe(pipefd) != -1);
395 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
397 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
398 meshlink_enable_discovery(mesh, false);
400 assert(meshlink_set_port(mesh, new_port));
402 char *invitation = meshlink_invite(mesh, NULL, "bar");
405 assert(write(pipefd[1], invitation, strlen(invitation) + 1) != -1);
407 meshlink_encrypted_key_rotate(mesh, "newkey", 6);
413 // Wait for child exit and verify which signal terminated it
415 assert(waitpid(pid, &status, 0) != -1);
416 assert_int_equal(WIFSIGNALED(status), true);
417 assert_int_equal(WTERMSIG(status), SIGINT);
419 // Reopen the node with invalid key other than old and new key should fail and should not affect
420 // the existing confbase
422 fprintf(stderr, "Opening mesh with invalid key\n");
423 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "invalidkey", 9);
424 assert_int_equal(mesh, NULL);
426 // Reopen the node with the "newkey", if it failed to open with "newkey" then
427 // opening with the "oldkey" should succeed
429 fprintf(stderr, "Opening mesh with new-key\n");
430 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
433 fprintf(stderr, "Opening mesh with new-key failed trying to open with old-key\n");
434 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
435 assert_int_not_equal(mesh, NULL);
438 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
439 meshlink_enable_discovery(mesh, false);
441 // Verify the newly set port and generated invitation
443 int get_port = meshlink_get_port(mesh);
444 assert_int_equal(get_port, new_port);
446 char invitation[200];
447 assert(read(pipefd[0], invitation, sizeof(invitation)) != -1);
449 assert(meshlink_start(mesh));
451 meshlink_destroy("encrypted_conf.1");
453 set_sync_flag(&status_changed_cond, false);
454 bar_reachable = false;
456 meshlink_handle_t *mesh2 = meshlink_open("encrypted_conf.1", "bar", "bar", DEV_CLASS_BACKBONE);
459 meshlink_set_node_status_cb(mesh2, node_status_cb);
460 meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb);
461 meshlink_enable_discovery(mesh2, false);
463 assert_int_equal(meshlink_join(mesh2, invitation), true);
467 meshlink_close(mesh);
468 meshlink_close(mesh2);
475 meshlink_destroy("encrypted_conf");
476 meshlink_destroy("encrypted_conf.1");
477 devtool_keyrotate_probe = nop_stage;
481 int test_meshlink_encrypted_key_rotation(void) {
482 /* State structures for key rotation Test Cases */
483 black_box_state_t test_case_key_rotation_01_state = {
484 .test_case_name = "test_case_key_rotation_01",
486 black_box_state_t test_case_key_rotation_02_state = {
487 .test_case_name = "test_case_key_rotation_02",
489 black_box_state_t test_case_key_rotation_03_state = {
490 .test_case_name = "test_case_key_rotation_03",
492 black_box_state_t test_case_key_rotation_04_state = {
493 .test_case_name = "test_case_key_rotation_04",
495 black_box_state_t test_case_key_rotation_05_state = {
496 .test_case_name = "test_case_key_rotation_05",
499 const struct CMUnitTest blackbox_status_tests[] = {
500 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_01, NULL, NULL,
501 (void *)&test_case_key_rotation_01_state),
502 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_02, NULL, NULL,
503 (void *)&test_case_key_rotation_02_state),
504 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_03, NULL, NULL,
505 (void *)&test_case_key_rotation_03_state),
506 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_04, NULL, NULL,
507 (void *)&test_case_key_rotation_04_state),
508 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_05, NULL, NULL,
509 (void *)&test_case_key_rotation_05_state),
511 total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]);
513 return cmocka_run_group_tests(blackbox_status_tests, NULL, NULL);