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 /* Execute key rotation Test Case # 1 - Sanity test */
54 static void test_case_key_rotation_01(void **state) {
55 execute_test(test_key_rotation_01, state);
58 /* Test Steps for key rotation Test Case # 1
61 1. Open encrypted node instance, call encrypted rotate API with
62 invalid input parameters to the call.
65 Key rotate should fail when called with invalid parameters.
67 static bool test_key_rotation_01(void) {
68 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
69 meshlink_destroy("encrypted_conf");
71 // Open a new meshlink instance.
73 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
74 assert_int_not_equal(mesh, NULL);
76 // Pass invalid arguments
78 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, NULL, 5);
79 assert_int_equal(keyrotation_status, false);
81 keyrotation_status = meshlink_encrypted_key_rotate(NULL, "newkey", 6);
82 assert_int_equal(keyrotation_status, false);
84 keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 0);
85 assert_int_equal(keyrotation_status, false);
90 meshlink_destroy("encrypted_conf");
95 /* Execute key rotation Test Case # 2 - Sanity test */
96 static void test_case_key_rotation_02(void **state) {
97 execute_test(test_key_rotation_02, state);
100 /* Test Steps for key rotation Test Case # 2
103 1. Open encrypted node instance, rotate it's key with a newkey and close the node.
104 2. Reopen the encrypted node instance with the newkey
107 Opening encrypted node instance should succeed when tried to open with newkey that's
108 been changed to new by key rotate API.
110 static bool test_key_rotation_02(void) {
111 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
112 meshlink_destroy("encrypted_conf");
114 // Open a new meshlink instance.
116 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
117 assert_int_not_equal(mesh, NULL);
118 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
120 // Set a new port for the mesh
122 int port = 0x1000 + (rand() & 0x7fff);
123 assert_int_equal(meshlink_set_port(mesh, port), true);
125 // Key rotate the encrypted_conf storage with new key
127 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
128 assert_int_equal(keyrotation_status, true);
130 meshlink_close(mesh);
132 // Reopen the meshlink instance with the new key
134 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
135 assert_int_not_equal(mesh, NULL);
137 // Validate the port number that we changed in the last run.
139 assert_int_equal(meshlink_get_port(mesh), port);
143 meshlink_close(mesh);
144 meshlink_destroy("encrypted_conf");
149 /* Execute key rotation Test Case # 3 - Sanity test */
150 static void test_case_key_rotation_03(void **state) {
151 execute_test(test_key_rotation_03, state);
154 /* Test Steps for key rotation Test Case # 3
157 1. Open encrypted node instance, rotate it's key with a newkey and close the node.
158 2. Reopen the encrypted node instance with the oldkey
161 Opening encrypted node instance should fail when tried to open with oldkey that's
162 been changed to new by key rotate API.
164 static bool test_key_rotation_03(void) {
165 meshlink_destroy("encrypted_conf");
166 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
168 // Open a new meshlink instance.
170 meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
171 assert_int_not_equal(mesh, NULL);
173 // Key rotate the encrypted_conf storage with new key
175 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
176 assert_int_equal(keyrotation_status, true);
178 meshlink_close(mesh);
180 // Reopen the meshlink instance with the new key
182 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
183 assert_int_equal(mesh, NULL);
187 meshlink_destroy("encrypted_conf");
192 /* Execute key rotation Test Case # 4 - Sanity test */
193 static void test_case_key_rotation_04(void **state) {
194 execute_test(test_key_rotation_04, state);
197 /* Test Steps for key rotation Test Case # 4
198 Verify whether key rotation API gracefully handles invitations porting from
202 1. Open foo node instance and generate invitations for peer and bar.
203 2. Do key rotation with newkey and verify invitation timestamps post key rotation.
204 3. Change timestamp of peer key to expire and Open instances of foo, bar and peer nodes
205 and try to join bar and peer node.
208 Key rotation API should never change the any file status attributes of an invitation file.
210 static bool test_key_rotation_04(void) {
211 meshlink_handle_t *mesh;
212 meshlink_handle_t *mesh1;
213 meshlink_handle_t *mesh2;
216 char invitation_path_buff[500];
217 struct stat temp_stat;
218 struct stat peer_stat;
219 struct utimbuf timebuf;
221 char *invitations_directory_path = "encrypted_conf/current/invitations/";
223 meshlink_destroy("encrypted_conf");
224 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
226 // Open a new meshlink instance.
228 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
229 assert_int_not_equal(mesh, NULL);
231 // Generate invitations
233 char *invitation1 = meshlink_invite(mesh, NULL, "peer");
234 assert_int_not_equal(invitation1, NULL);
236 // Read the peer invitation file status structure
238 strcpy(invitation_path_buff, invitations_directory_path);
239 d = opendir(invitation_path_buff);
242 while((ent = readdir(d)) != NULL) {
243 if(ent->d_name[0] == '.') {
247 strcpy(invitation_path_buff, invitations_directory_path);
248 strcat(invitation_path_buff, ent->d_name);
249 assert(stat(invitation_path_buff, &temp_stat) != -1);
251 if((temp_stat.st_mode & S_IFMT) == S_IFREG) {
260 char *invitation2 = meshlink_invite(mesh, NULL, "bar");
261 assert_int_not_equal(invitation2, NULL);
263 // Key rotate the encrypted_conf storage with new key
265 bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
266 assert_int_equal(keyrotation_status, true);
268 meshlink_close(mesh);
270 // Compare invitation file timestamps of old key with new key
272 assert(stat(invitation_path_buff, &peer_stat) != -1);
273 assert_int_equal(peer_stat.st_mtime, temp_stat.st_mtime);
275 // Change timestamp for @ peer @ node invitation
277 timebuf.actime = peer_stat.st_atime;
278 timebuf.modtime = peer_stat.st_mtime - 604805; // > 1 week
280 assert(utime(invitation_path_buff, &timebuf) != -1);
283 // Reopen the meshlink instance with the new key
285 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
286 assert_int_not_equal(mesh, NULL);
288 mesh1 = meshlink_open("encrypted_conf.1", "peer", "encrypted", DEV_CLASS_BACKBONE);
289 assert_int_not_equal(mesh1, NULL);
291 mesh2 = meshlink_open("encrypted_conf.2", "bar", "encrypted", DEV_CLASS_BACKBONE);
292 assert_int_not_equal(mesh2, NULL);
294 assert(meshlink_start(mesh));
296 join_status = meshlink_join(mesh1, invitation1);
297 assert_int_equal(join_status, false);
299 join_status = meshlink_join(mesh2, invitation2);
300 assert_int_equal(join_status, true);
306 meshlink_close(mesh);
307 meshlink_close(mesh1);
308 meshlink_close(mesh2);
309 meshlink_destroy("encrypted_conf");
310 meshlink_destroy("encrypted_conf.1");
311 meshlink_destroy("encrypted_conf.2");
316 /* Execute key rotation Test Case # 5 - Atomicity test */
317 static void test_case_key_rotation_05(void **state) {
318 execute_test(test_key_rotation_05, state);
321 static int break_stage;
323 static void nop_stage(int stage) {
329 static void debug_probe(int stage) {
331 // Terminate the node at the specified stage (by @ break_stage @ )
332 if(stage == break_stage) {
334 } else if((break_stage < 1) || (break_stage > 3)) {
335 fprintf(stderr, "INVALID stage break\n");
342 /* Test Steps for key rotation Test Case # 5
343 Debug all stages of key rotate API and verify it's atomicity
346 1. Open foo node instance.
347 2. In a loop break meshlink node instance at each stage incrementally
349 3. Reopen node instance post termination.
352 Terminating node instance when meshlink_encrypted_key_rotate function called
353 at any stage should give atomic result when reopened.
355 static bool test_key_rotation_05(void) {
358 meshlink_handle_t *mesh;
359 meshlink_destroy("encrypted_conf");
360 meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
362 assert(signal(SIGINT, SIG_DFL) != SIG_ERR);
363 assert(signal(SIGABRT, SIG_DFL) != SIG_ERR);
365 // Set debug_probe callback
367 devtool_keyrotate_probe = debug_probe;
368 int new_port = 12000;
371 // incrementally debug meshlink_encrypted_key_rotate API atomicity
373 for(break_stage = 1; break_stage <= 3; break_stage += 1) {
374 fprintf(stderr, "Debugging stage %d\n", break_stage);
375 meshlink_destroy("encrypted_conf");
377 assert(pipe(pipefd) != -1);
384 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
386 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
387 meshlink_enable_discovery(mesh, false);
389 assert(meshlink_set_port(mesh, new_port));
391 char *invitation = meshlink_invite(mesh, NULL, "bar");
394 assert(write(pipefd[1], invitation, strlen(invitation) + 1) != -1);
396 meshlink_encrypted_key_rotate(mesh, "newkey", 6);
402 // Wait for child exit and verify which signal terminated it
404 assert(waitpid(pid, &status, 0) != -1);
405 assert_int_equal(WIFSIGNALED(status), true);
406 assert_int_equal(WTERMSIG(status), SIGINT);
408 // Reopen the node with invalid key other than old and new key should fail and should not affect
409 // the existing confbase
411 fprintf(stderr, "Opening mesh with invalid key\n");
412 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "invalidkey", 9);
413 assert_int_equal(mesh, NULL);
415 // Reopen the node with the "newkey", if it failed to open with "newkey" then
416 // opening with the "oldkey" should succeed
418 fprintf(stderr, "Opening mesh with new-key\n");
419 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
422 fprintf(stderr, "Opening mesh with new-key failed trying to open with old-key\n");
423 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
424 assert_int_not_equal(mesh, NULL);
427 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
428 meshlink_enable_discovery(mesh, false);
430 // Verify the newly set port and generated invitation
432 int get_port = meshlink_get_port(mesh);
433 assert_int_equal(get_port, new_port);
435 char invitation[200];
436 assert(read(pipefd[0], invitation, sizeof(invitation)) != -1);
438 assert(meshlink_start(mesh));
440 meshlink_destroy("encrypted_conf.1");
442 meshlink_handle_t *mesh2 = meshlink_open("encrypted_conf.1", "bar", "bar", DEV_CLASS_BACKBONE);
445 meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb);
446 meshlink_enable_discovery(mesh2, false);
448 assert_int_equal(meshlink_join(mesh2, invitation), true);
452 meshlink_close(mesh);
453 meshlink_close(mesh2);
460 meshlink_destroy("encrypted_conf");
461 meshlink_destroy("encrypted_conf.1");
462 devtool_keyrotate_probe = nop_stage;
466 int test_meshlink_encrypted_key_rotation(void) {
467 /* State structures for key rotation Test Cases */
468 black_box_state_t test_case_key_rotation_01_state = {
469 .test_case_name = "test_case_key_rotation_01",
471 black_box_state_t test_case_key_rotation_02_state = {
472 .test_case_name = "test_case_key_rotation_02",
474 black_box_state_t test_case_key_rotation_03_state = {
475 .test_case_name = "test_case_key_rotation_03",
477 black_box_state_t test_case_key_rotation_04_state = {
478 .test_case_name = "test_case_key_rotation_04",
480 black_box_state_t test_case_key_rotation_05_state = {
481 .test_case_name = "test_case_key_rotation_05",
484 const struct CMUnitTest blackbox_status_tests[] = {
485 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_01, NULL, NULL,
486 (void *)&test_case_key_rotation_01_state),
487 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_02, NULL, NULL,
488 (void *)&test_case_key_rotation_02_state),
489 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_03, NULL, NULL,
490 (void *)&test_case_key_rotation_03_state),
491 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_04, NULL, NULL,
492 (void *)&test_case_key_rotation_04_state),
493 cmocka_unit_test_prestate_setup_teardown(test_case_key_rotation_05, NULL, NULL,
494 (void *)&test_case_key_rotation_05_state),
496 total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]);
498 return cmocka_run_group_tests(blackbox_status_tests, NULL, NULL);