]> git.meshlink.io Git - meshlink/blob - test/blackbox/run_blackbox_tests/test_cases_key_rotation.c
Add unit test cases for encrypted storage key rotation
[meshlink] / test / blackbox / run_blackbox_tests / test_cases_key_rotation.c
1 /*
2     test_cases_key_rotation.c -- Execution of specific meshlink black box test cases
3     Copyright (C) 2019  Guus Sliepen <guus@meshlink.io>
4
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.
9
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.
14
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.
18 */
19
20 #include <assert.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <setjmp.h>
25 #include <cmocka.h>
26 #include <pthread.h>
27 #include <stdio.h>
28 #include <sys/types.h>
29 #include <dirent.h>
30 #include <sys/wait.h>
31 #include <signal.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <utime.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"
41
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);
52
53 static void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
54
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",
61         };
62
63         fprintf(stderr, "%s(%s):\x1b[0m %s\n", mesh->name, levelstr[level], text);
64 }
65
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);
69 }
70
71 /* Test Steps for key rotation Test Case # 1
72
73     Test Steps:
74     1. Open encrypted node instance, call encrypted rotate API with
75         invalid input parameters to the call.
76
77     Expected Result:
78     Key rotate should fail when called with invalid parameters.
79 */
80 static bool test_key_rotation_01(void) {
81         meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
82         meshlink_destroy("encrypted_conf");
83
84         // Open a new meshlink instance.
85
86         meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
87         assert_int_not_equal(mesh, NULL);
88
89         // Pass invalid arguments
90
91         bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, NULL, 5);
92         assert_int_equal(keyrotation_status, false);
93
94         keyrotation_status = meshlink_encrypted_key_rotate(NULL, "newkey", 6);
95         assert_int_equal(keyrotation_status, false);
96
97         keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 0);
98         assert_int_equal(keyrotation_status, false);
99
100         // Cleanup
101
102         meshlink_close(mesh);
103         meshlink_destroy("encrypted_conf");
104
105         return true;
106 }
107
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);
111 }
112
113 /* Test Steps for key rotation Test Case # 2
114
115     Test Steps:
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
118
119     Expected Result:
120     Opening encrypted node instance should succeed when tried to open with newkey that's
121     been changed to new by key rotate API.
122 */
123 static bool test_key_rotation_02(void) {
124         meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
125         meshlink_destroy("encrypted_conf");
126
127         // Open a new meshlink instance.
128
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);
132
133         // Set a new port for the mesh
134
135         int port = 0x1000 + (rand() & 0x7fff);
136         assert_int_equal(meshlink_set_port(mesh, port), true);
137
138         // Key rotate the encrypted_conf storage with new key
139
140         bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
141         assert_int_equal(keyrotation_status, true);
142
143         meshlink_close(mesh);
144
145         // Reopen the meshlink instance with the new key
146
147         mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
148         assert_int_not_equal(mesh, NULL);
149
150         // Validate the port number that we changed in the last run.
151
152         assert_int_equal(meshlink_get_port(mesh), port);
153
154         // Cleanup
155
156         meshlink_close(mesh);
157         meshlink_destroy("encrypted_conf");
158
159         return true;
160 }
161
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);
165 }
166
167 /* Test Steps for key rotation Test Case # 3
168
169     Test Steps:
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
172
173     Expected Result:
174     Opening encrypted node instance should fail when tried to open with oldkey that's
175     been changed to new by key rotate API.
176 */
177 static bool test_key_rotation_03(void) {
178         meshlink_destroy("encrypted_conf");
179         meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
180
181         // Open a new meshlink instance.
182
183         meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
184         assert_int_not_equal(mesh, NULL);
185
186         // Key rotate the encrypted_conf storage with new key
187
188         bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
189         assert_int_equal(keyrotation_status, true);
190
191         meshlink_close(mesh);
192
193         // Reopen the meshlink instance with the new key
194
195         mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
196         assert_int_equal(mesh, NULL);
197
198         // Cleanup
199
200         meshlink_destroy("encrypted_conf");
201
202         return true;
203 }
204
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);
208 }
209
210 /* Test Steps for key rotation Test Case # 4
211     Verify whether key rotation API gracefully handles invitations porting from
212     old key to new key.
213
214     Test Steps:
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.
219
220     Expected Result:
221     Key rotation API should never change the any file status attributes of an invitation file.
222 */
223 static bool test_key_rotation_04(void) {
224         meshlink_handle_t *mesh;
225         meshlink_handle_t *mesh1;
226         meshlink_handle_t *mesh2;
227         struct dirent *ent;
228         DIR *d;
229         char invitation_path_buff[500];
230         struct stat temp_stat;
231         struct stat peer_stat;
232         struct utimbuf timebuf;
233         bool join_status;
234         char *invitations_directory_path = "encrypted_conf/current/invitations/";
235
236         meshlink_destroy("encrypted_conf");
237         meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
238
239         // Open a new meshlink instance.
240
241         mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
242         assert_int_not_equal(mesh, NULL);
243
244         // Generate invitations
245
246         char *invitation1 = meshlink_invite(mesh, NULL, "peer");
247         assert_int_not_equal(invitation1, NULL);
248
249         // Read the peer invitation file status structure
250
251         strcpy(invitation_path_buff, invitations_directory_path);
252         d = opendir(invitation_path_buff);
253         assert(d);
254
255         while((ent = readdir(d)) != NULL) {
256                 if(ent->d_name[0] == '.') {
257                         continue;
258                 }
259
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);
263
264                 if((temp_stat.st_mode & S_IFMT) == S_IFREG) {
265                         break;
266                 }
267         }
268
269         assert(ent);
270
271         closedir(d);
272
273         char *invitation2 = meshlink_invite(mesh, NULL, "bar");
274         assert_int_not_equal(invitation2, NULL);
275
276         // Key rotate the encrypted_conf storage with new key
277
278         bool keyrotation_status = meshlink_encrypted_key_rotate(mesh, "newkey", 6);
279         assert_int_equal(keyrotation_status, true);
280
281         meshlink_close(mesh);
282
283         // Compare invitation file timestamps of old key with new key
284
285         assert(stat(invitation_path_buff, &peer_stat) != -1);
286         assert_int_equal(peer_stat.st_mtime, temp_stat.st_mtime);
287
288         // Change timestamp for @ peer @ node invitation
289
290         timebuf.actime = peer_stat.st_atime;
291         timebuf.modtime = peer_stat.st_mtime - 604805; // > 1 week
292
293         assert(utime(invitation_path_buff, &timebuf) != -1);
294
295
296         // Reopen the meshlink instance with the new key
297
298         mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
299         assert_int_not_equal(mesh, NULL);
300
301         mesh1 = meshlink_open("encrypted_conf.1", "peer", "encrypted", DEV_CLASS_BACKBONE);
302         assert_int_not_equal(mesh1, NULL);
303
304         mesh2 = meshlink_open("encrypted_conf.2", "bar", "encrypted", DEV_CLASS_BACKBONE);
305         assert_int_not_equal(mesh2, NULL);
306
307         assert(meshlink_start(mesh));
308
309         join_status = meshlink_join(mesh1, invitation1);
310         assert_int_equal(join_status, false);
311
312         join_status = meshlink_join(mesh2, invitation2);
313         assert_int_equal(join_status, true);
314
315         // Cleanup
316
317         free(invitation1);
318         free(invitation2);
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");
325
326         return true;
327 }
328
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);
332 }
333
334 static int break_stage;
335
336 static void nop_stage(int stage) {
337         return;
338 }
339
340 static void debug_probe(int stage) {
341
342         // Terminate the node at the specified stage (by @ break_stage @ )
343         if(stage == break_stage) {
344                 raise(SIGINT);
345         } else if((break_stage < 1) || (break_stage > 11)) {
346                 fprintf(stderr, "INVALID stage break\n");
347                 raise(SIGABRT);
348         }
349
350         return;
351 }
352
353 /* Test Steps for key rotation Test Case # 5
354     Debug all stages of key rotate API and verify it's atomicity
355
356     Test Steps:
357     1. Open foo node instance.
358     2. In a loop break meshlink node instance at each stage incrementally
359         in a fork process
360     3. Reopen node instance post termination.
361
362     Expected Result:
363     Terminating node instance when meshlink_encrypted_key_rotate function called
364     at any stage should give atomic result when reopened.
365 */
366 static bool test_key_rotation_05(void) {
367         pid_t pid;
368         int status;
369         meshlink_handle_t *mesh;
370         meshlink_destroy("encrypted_conf");
371         meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
372
373         assert(signal(SIGINT, SIG_DFL) != SIG_ERR);
374         assert(signal(SIGABRT, SIG_DFL) != SIG_ERR);
375
376         // Set debug_probe callback
377
378         devtool_keyrotate_probe = debug_probe;
379         int new_port = 12000;
380         int pipefd[2];
381
382         // incrementally debug meshlink_encrypted_key_rotate API atomicity
383
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");
387
388                 assert(pipe(pipefd) != -1);
389
390                 pid = fork();
391                 assert(pid != -1);
392
393                 if(!pid) {
394                         close(pipefd[0]);
395                         mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "oldkey", 6);
396                         assert(mesh);
397                         meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
398                         meshlink_enable_discovery(mesh, false);
399
400                         assert(meshlink_set_port(mesh, new_port));
401
402                         char *invitation = meshlink_invite(mesh, NULL, "bar");
403                         assert(invitation);
404
405                         assert(write(pipefd[1], invitation, strlen(invitation) + 1) != -1);
406
407                         meshlink_encrypted_key_rotate(mesh, "newkey", 6);
408                         raise(SIGABRT);
409                 }
410
411                 close(pipefd[1]);
412
413                 // Wait for child exit and verify which signal terminated it
414
415                 assert(waitpid(pid, &status, 0) != -1);
416                 assert_int_equal(WIFSIGNALED(status), true);
417                 assert_int_equal(WTERMSIG(status), SIGINT);
418
419                 // Reopen the node with invalid key other than old and new key should fail and should not affect
420                 // the existing confbase
421
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);
425
426                 // Reopen the node with the "newkey", if it failed to open with "newkey" then
427                 // opening with the "oldkey" should succeed
428
429                 fprintf(stderr, "Opening mesh with new-key\n");
430                 mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "newkey", 6);
431
432                 if(!mesh) {
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);
436                 }
437
438                 meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
439                 meshlink_enable_discovery(mesh, false);
440
441                 // Verify the newly set port and generated invitation
442
443                 int get_port = meshlink_get_port(mesh);
444                 assert_int_equal(get_port, new_port);
445
446                 char invitation[200];
447                 assert(read(pipefd[0], invitation, sizeof(invitation)) != -1);
448
449                 assert(meshlink_start(mesh));
450
451                 meshlink_destroy("encrypted_conf.1");
452
453                 set_sync_flag(&status_changed_cond, false);
454                 bar_reachable = false;
455
456                 meshlink_handle_t *mesh2 = meshlink_open("encrypted_conf.1", "bar", "bar", DEV_CLASS_BACKBONE);
457                 assert(mesh2);
458
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);
462
463                 assert_int_equal(meshlink_join(mesh2, invitation), true);
464
465                 // cleanup
466
467                 meshlink_close(mesh);
468                 meshlink_close(mesh2);
469
470                 close(pipefd[0]);
471         }
472
473         // Cleanup
474
475         meshlink_destroy("encrypted_conf");
476         meshlink_destroy("encrypted_conf.1");
477         devtool_keyrotate_probe = nop_stage;
478         return true;
479 }
480
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",
485         };
486         black_box_state_t test_case_key_rotation_02_state = {
487                 .test_case_name = "test_case_key_rotation_02",
488         };
489         black_box_state_t test_case_key_rotation_03_state = {
490                 .test_case_name = "test_case_key_rotation_03",
491         };
492         black_box_state_t test_case_key_rotation_04_state = {
493                 .test_case_name = "test_case_key_rotation_04",
494         };
495         black_box_state_t test_case_key_rotation_05_state = {
496                 .test_case_name = "test_case_key_rotation_05",
497         };
498
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),
510         };
511         total_tests += sizeof(blackbox_status_tests) / sizeof(blackbox_status_tests[0]);
512
513         return cmocka_run_group_tests(blackbox_status_tests, NULL, NULL);
514 }