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.
32 #include <sys/types.h>
39 #include "execute_tests.h"
40 #include "test_cases_key_rotation.h"
41 #include "../common/test_step.h"
42 #include "../common/common_handlers.h"
43 #include "../../../src/devtools.h"
44 #include "../../utils.h"
46 static void test_case_key_rotation_01(void **state);
47 static bool test_key_rotation_01(void);
48 static void test_case_key_rotation_02(void **state);
49 static bool test_key_rotation_02(void);
50 static void test_case_key_rotation_03(void **state);
51 static bool test_key_rotation_03(void);
52 static void test_case_key_rotation_04(void **state);
53 static bool test_key_rotation_04(void);
54 static void test_case_key_rotation_05(void **state);
55 static bool test_key_rotation_05(void);
57 /* Execute key rotation Test Case # 1 - Sanity test */
58 static void test_case_key_rotation_01(void **state) {
59 execute_test(test_key_rotation_01, state);
62 /* Test Steps for key rotation Test Case # 1
65 1. Open encrypted node instance, call encrypted rotate API with
66 invalid input parameters to the call.
69 Key rotate should fail when called with invalid parameters.
71 static bool test_key_rotation_01(void) {
72 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
73 assert(meshlink_destroy("encrypted_conf"));
75 // Open a new meshlink instance.
77 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
78 assert_int_not_equal(mesh, NULL);
80 // Pass invalid arguments
82 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, NULL, 5);
83 assert_int_equal(keyrotation_status, false);
85 keyrotation_status = meshlink_encrypted_key_rotate(NULL, "newkey", 6);
86 assert_int_equal(keyrotation_status, false);
88 keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 0);
89 assert_int_equal(keyrotation_status, false);
94 assert(meshlink_destroy("encrypted_conf"));
99 /* Execute key rotation Test Case # 2 - Sanity test */
100 static void test_case_key_rotation_02(void **state) {
101 execute_test(test_key_rotation_02, state);
104 /* Test Steps for key rotation Test Case # 2
107 1. Open encrypted node instance, rotate it's key with a newkey and close the node.
108 2. Reopen the encrypted node instance with the newkey
111 Opening encrypted node instance should succeed when tried to open with newkey that's
112 been changed to new by key rotate API.
114 static bool test_key_rotation_02(void) {
115 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
116 assert(meshlink_destroy("encrypted_conf"));
118 // Open a new meshlink instance.
120 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
121 assert_int_not_equal(mesh, NULL);
122 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
124 // Set a new port for the mesh
126 int port = 0x1000 + (rand() & 0x7fff);
127 assert_int_equal(meshlink_set_port(mesh, port), true);
129 // Key rotate the encrypted_conf storage with new key
131 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
132 assert_int_equal(keyrotation_status, true);
134 meshlink_close(mesh);
136 // Reopen the meshlink instance with the new key
138 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
139 assert_int_not_equal(mesh, NULL);
141 // Validate the port number that we changed in the last run.
143 assert_int_equal(meshlink_get_port(mesh), port);
147 meshlink_close(mesh);
148 assert(meshlink_destroy("encrypted_conf"));
153 /* Execute key rotation Test Case # 3 - Sanity test */
154 static void test_case_key_rotation_03(void **state) {
155 execute_test(test_key_rotation_03, state);
158 /* Test Steps for key rotation Test Case # 3
161 1. Open encrypted node instance, rotate it's key with a newkey and close the node.
162 2. Reopen the encrypted node instance with the oldkey
165 Opening encrypted node instance should fail when tried to open with oldkey that's
166 been changed to new by key rotate API.
168 static bool test_key_rotation_03(void) {
169 assert(meshlink_destroy("encrypted_conf"));
170 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
172 // Open a new meshlink instance.
174 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
175 assert_int_not_equal(mesh, NULL);
177 // Key rotate the encrypted_conf storage with new key
179 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
180 assert_int_equal(keyrotation_status, true);
182 meshlink_close(mesh);
184 // Reopen the meshlink instance with the new key
186 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
187 assert_int_equal(mesh, NULL);
191 assert(meshlink_destroy("encrypted_conf"));
196 /* Execute key rotation Test Case # 4 - Sanity test */
197 static void test_case_key_rotation_04(void **state) {
198 execute_test(test_key_rotation_04, state);
201 /* Test Steps for key rotation Test Case # 4
202 Verify whether key rotation API gracefully handles invitations porting from
206 1. Open foo node instance and generate invitations for peer and bar.
207 2. Do key rotation with newkey and verify invitation timestamps post key rotation.
208 3. Change timestamp of peer key to expire and Open instances of foo, bar and peer nodes
209 and try to join bar and peer node.
212 Key rotation API should never change the any file status attributes of an invitation file.
214 static bool test_key_rotation_04(void) {
215 meshlink_handle_t *mesh;
216 meshlink_handle_t *mesh1;
217 meshlink_handle_t *mesh2;
220 char invitation_path_buff[500];
221 struct stat temp_stat;
222 struct stat peer_stat;
223 struct utimbuf timebuf;
225 char *invitations_directory_path = "encrypted_conf/current/invitations/";
227 assert(meshlink_destroy("encrypted_conf"));
228 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
230 // Open a new meshlink instance.
232 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
233 assert_int_not_equal(mesh, NULL);
235 // Generate invitations
237 char *invitation1 = meshlink_invite(mesh, NULL, "peer");
238 assert_int_not_equal(invitation1, NULL);
240 // Read the peer invitation file status structure
242 strcpy(invitation_path_buff, invitations_directory_path);
243 d = opendir(invitation_path_buff);
246 while((ent = readdir(d)) != NULL) {
247 if(ent->d_name[0] == '.') {
251 strcpy(invitation_path_buff, invitations_directory_path);
252 strcat(invitation_path_buff, ent->d_name);
253 assert(stat(invitation_path_buff, &temp_stat) != -1);
255 if((temp_stat.st_mode & S_IFMT) == S_IFREG) {
264 char *invitation2 = meshlink_invite(mesh, NULL, "bar");
265 assert_int_not_equal(invitation2, NULL);
267 // Key rotate the encrypted_conf storage with new key
269 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
270 assert_int_equal(keyrotation_status, true);
272 meshlink_close(mesh);
274 // Compare invitation file timestamps of old key with new key
276 assert(stat(invitation_path_buff, &peer_stat) != -1);
277 assert_int_equal(peer_stat.st_mtime, temp_stat.st_mtime);
279 // Change timestamp for @ peer @ node invitation
281 timebuf.actime = peer_stat.st_atime;
282 timebuf.modtime = peer_stat.st_mtime - 604805; // > 1 week
284 assert(utime(invitation_path_buff, &timebuf) != -1);
287 // Reopen the meshlink instance with the new key
289 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
290 assert_int_not_equal(mesh, NULL);
292 mesh1 = meshlink_open("encrypted_conf.1", "peer", "encrypted", DEV_CLASS_BACKBONE);
293 assert_int_not_equal(mesh1, NULL);
295 mesh2 = meshlink_open("encrypted_conf.2", "bar", "encrypted", DEV_CLASS_BACKBONE);
296 assert_int_not_equal(mesh2, NULL);
298 assert(meshlink_start(mesh));
300 join_status = meshlink_join(mesh1, invitation1);
301 assert_int_equal(join_status, false);
303 join_status = meshlink_join(mesh2, invitation2);
304 assert_int_equal(join_status, true);
310 meshlink_close(mesh);
311 meshlink_close(mesh1);
312 meshlink_close(mesh2);
313 assert(meshlink_destroy("encrypted_conf"));
314 assert(meshlink_destroy("encrypted_conf.1"));
315 assert(meshlink_destroy("encrypted_conf.2"));
320 /* Execute key rotation Test Case # 5 - Atomicity test */
321 static void test_case_key_rotation_05(void **state) {
322 execute_test(test_key_rotation_05, state);
325 static int break_stage;
327 static void nop_stage(int stage) {
333 static void debug_probe(int stage) {
335 // Terminate the node at the specified stage (by @ break_stage @ )
336 if(stage == break_stage) {
338 } else if((break_stage < 1) || (break_stage > 3)) {
339 fprintf(stderr, "INVALID stage break\n");
346 /* Test Steps for key rotation Test Case # 5
347 Debug all stages of key rotate API and verify it's atomicity
350 1. Open foo node instance.
351 2. In a loop break meshlink node instance at each stage incrementally
353 3. Reopen node instance post termination.
356 Terminating node instance when meshlink_encrypted_key_rotate function called
357 at any stage should give atomic result when reopened.
359 static bool test_key_rotation_05(void) {
362 meshlink_handle_t *mesh;
363 assert(meshlink_destroy("encrypted_conf"));
364 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
366 assert(signal(SIGINT, SIG_DFL) != SIG_ERR);
367 assert(signal(SIGABRT, SIG_DFL) != SIG_ERR);
369 // Set debug_probe callback
371 devtool_keyrotate_probe = debug_probe;
372 int new_port = 12000;
375 // incrementally debug meshlink_encrypted_key_rotate API atomicity
377 for(break_stage = 1; break_stage <= 3; break_stage += 1) {
378 fprintf(stderr, "Debugging stage %d\n", break_stage);
379 assert(meshlink_destroy("encrypted_conf"));
381 assert(pipe(pipefd) != -1);
388 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
390 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
391 meshlink_enable_discovery(mesh, false);
393 assert(meshlink_set_port(mesh, new_port));
395 char *invitation = meshlink_invite(mesh, NULL, "bar");
398 assert(write(pipefd[1], invitation, strlen(invitation) + 1) != -1);
400 assert(meshlink_encrypted_key_rotate(mesh, "newkey", 6));
406 // Wait for child exit and verify which signal terminated it
408 assert(waitpid(pid, &status, 0) != -1);
409 assert_int_equal(WIFSIGNALED(status), true);
410 assert_int_equal(WTERMSIG(status), SIGINT);
412 // Reopen the node with invalid key other than old and new key should fail and should not affect
413 // the existing confbase
415 fprintf(stderr, "Opening mesh with invalid key\n");
416 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "invalidkey", 9);
417 assert_int_equal(mesh, NULL);
419 // Reopen the node with the "newkey", if it failed to open with "newkey" then
420 // opening with the "oldkey" should succeed
422 fprintf(stderr, "Opening mesh with new-key\n");
423 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
426 fprintf(stderr, "Opening mesh with new-key failed trying to open with old-key\n");
427 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
428 assert_int_not_equal(mesh, NULL);
431 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
432 meshlink_enable_discovery(mesh, false);
434 // Verify the newly set port and generated invitation
436 int get_port = meshlink_get_port(mesh);
437 assert_int_equal(get_port, new_port);
439 char invitation[200];
440 assert(read(pipefd[0], invitation, sizeof(invitation)) != -1);
442 assert(meshlink_start(mesh));
444 assert(meshlink_destroy("encrypted_conf.1"));
446 meshlink_handle_t *mesh2 = meshlink_open("encrypted_conf.1", "bar", "bar", DEV_CLASS_BACKBONE);
449 meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb);
450 meshlink_enable_discovery(mesh2, false);
452 assert_int_equal(meshlink_join(mesh2, invitation), true);
456 meshlink_close(mesh);
457 meshlink_close(mesh2);
464 assert(meshlink_destroy("encrypted_conf"));
465 assert(meshlink_destroy("encrypted_conf.1"));
466 devtool_keyrotate_probe = nop_stage;
470 int test_meshlink_encrypted_key_rotation(void) {
471 /* State structures for key rotation Test Cases */
472 black_box_state_t test_case_key_rotation_01_state = {
473 .test_case_name = "test_case_key_rotation_01",
475 black_box_state_t test_case_key_rotation_02_state = {
476 .test_case_name = "test_case_key_rotation_02",
478 black_box_state_t test_case_key_rotation_03_state = {
479 .test_case_name = "test_case_key_rotation_03",
481 black_box_state_t test_case_key_rotation_04_state = {
482 .test_case_name = "test_case_key_rotation_04",
484 black_box_state_t test_case_key_rotation_05_state = {
485 .test_case_name = "test_case_key_rotation_05",
488 const struct CMUnitTest blackbox_status_tests[] = {
489 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_01, NULL, NULL,
490 (void *)&test_case_key_rotation_01_state),
491 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_02, NULL, NULL,
492 (void *)&test_case_key_rotation_02_state),
493 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_03, NULL, NULL,
494 (void *)&test_case_key_rotation_03_state),
495 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_04, NULL, NULL,
496 (void *)&test_case_key_rotation_04_state),
497 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_05, NULL, NULL,
498 (void *)&test_case_key_rotation_05_state),
500 total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]);
502 return cmocka_run_group_tests(blackbox_status_tests, NULL, NULL);