]> git.saurik.com Git - apple/xnu.git/blame - tests/data_protection.c
xnu-7195.101.1.tar.gz
[apple/xnu.git] / tests / data_protection.c
CommitLineData
813fb2f6 1#include <darwintest.h>
a39ff7e2 2#include <darwintest_utils.h>
813fb2f6
A
3#include <errno.h>
4#include <fcntl.h>
5#include <stdlib.h>
6#include <sys/mount.h>
7#include <sys/wait.h>
8#include <sys/stat.h>
9#include <unistd.h>
10#include <stdio.h>
11#include <string.h>
12
13#include <IOKit/IOKitLib.h>
14#include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
15#include <Kernel/sys/content_protection.h>
16
17#define CPT_IO_SIZE 4096
18#define CPT_AKS_BUF_SIZE 256
19#define CPT_MAX_PASS_LEN 64
20
21#define GET_PROT_CLASS(fd) \
22 fcntl((fd), F_GETPROTECTIONCLASS)
23
24#define SET_PROT_CLASS(fd, prot_class) \
25 fcntl((fd), F_SETPROTECTIONCLASS, (prot_class))
26
27#define KEYSTORECTL_PATH "/usr/local/bin/keystorectl"
28#define KEYBAGDTEST_PATH "/usr/local/bin/keybagdTest"
29#define TEMP_DIR_TEMPLATE "/tmp/data_protection_test.XXXXXXXX"
30#define TEST_PASSCODE "IAmASecurePassword"
31
32int g_fd = -1;
33int g_dir_fd = -1;
34int g_subdir_fd = -1;
35int g_passcode_set = 0;
36
37char g_test_tempdir[PATH_MAX] = TEMP_DIR_TEMPLATE;
38char g_filepath[PATH_MAX] = "";
39char g_dirpath[PATH_MAX] = "";
40char g_subdirpath[PATH_MAX] = "";
41
42int apple_key_store(
43 uint32_t command,
44 uint64_t * inputs,
45 uint32_t input_count,
46 void * input_structs,
47 size_t input_struct_count,
48 uint64_t * outputs,
49 uint32_t * output_count
0a7de745 50 );
813fb2f6 51int spawn_proc(char * const command[]);
5ba3f43e 52int supports_content_prot(void);
813fb2f6
A
53char* dp_class_num_to_string(int num);
54int lock_device(void);
55int unlock_device(char * passcode);
56int set_passcode(char * new_passcode, char * old_passcode);
57int clear_passcode(char * passcode);
58int has_passcode(void);
59void setup(void);
60void cleanup(void);
61
62T_DECL(data_protection,
0a7de745 63 "Verify behavior of the various data protection classes") {
813fb2f6
A
64 int local_result = -1;
65 int new_prot_class = -1;
66 int old_prot_class = -1;
67 int current_byte = 0;
68 char rd_buffer[CPT_IO_SIZE];
69 char wr_buffer[CPT_IO_SIZE];
70
71 setup();
72
73 /*
74 * Ensure we can freely read and change
75 * protection classes when unlocked.
76 */
0a7de745 77 for (
813fb2f6
A
78 new_prot_class = PROTECTION_CLASS_A;
79 new_prot_class <= PROTECTION_CLASS_F;
80 new_prot_class++
0a7de745 81 ) {
813fb2f6
A
82 T_ASSERT_NE(
83 old_prot_class = GET_PROT_CLASS(g_fd),
84 -1,
85 "Get protection class when locked"
0a7de745 86 );
813fb2f6
A
87 T_WITH_ERRNO;
88 T_ASSERT_NE(
89 SET_PROT_CLASS(g_fd, new_prot_class),
90 -1,
91 "Should be able to change protection "
92 "from %s to %s while unlocked",
93 dp_class_num_to_string(old_prot_class),
94 dp_class_num_to_string(new_prot_class)
0a7de745 95 );
813fb2f6
A
96 }
97
98 /* Query the filesystem for the default CP level (Is it C?) */
99#ifndef F_GETDEFAULTPROTLEVEL
100#define F_GETDEFAULTPROTLEVEL 79
101#endif
102
103 T_WITH_ERRNO;
104 T_ASSERT_NE(
105 old_prot_class = fcntl(g_fd, F_GETDEFAULTPROTLEVEL),
106 -1,
107 "Get default protection level for filesystem"
0a7de745 108 );
813fb2f6
A
109
110 /* XXX: Do we want to do anything with the level? What should it be? */
111
112 /*
113 * files are allowed to move into F, but not out of it. They can also
114 * only do so when they do not have content.
115 */
116 close(g_fd);
117 unlink(g_filepath);
118
119 /* re-create the file */
120 T_WITH_ERRNO;
121 T_ASSERT_GE(
0a7de745 122 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC),
813fb2f6
A
123 0,
124 "Recreate test file"
0a7de745 125 );
813fb2f6
A
126
127 /* Try making a class A file while locked. */
128 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
129
130 T_WITH_ERRNO;
131 T_ASSERT_EQ(
132 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_A),
133 -1,
134 "Should not be able to change protection "
135 "from class D to class A when locked"
0a7de745 136 );
813fb2f6
A
137 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
138
139 /* Attempt opening/IO to a class A file while unlocked. */
140 T_WITH_ERRNO;
141 T_ASSERT_EQ(
142 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_A),
143 0,
144 "Should be able to change protection "
145 "from class D to class A when unlocked"
0a7de745 146 );
813fb2f6
A
147
148 close(g_fd);
149
150 T_WITH_ERRNO;
151 T_ASSERT_GE(
0a7de745 152 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
813fb2f6
A
153 0,
154 "Should be able to open a class A file when unlocked");
155
156 /*
157 * TODO: Write specific data we can check for. If we're going to do
158 * that, the write scheme should be deliberately ugly.
159 */
160 current_byte = 0;
161
0a7de745 162 while (current_byte < CPT_IO_SIZE) {
813fb2f6
A
163 local_result = pwrite(
164 g_fd,
165 &wr_buffer[current_byte],
166 CPT_IO_SIZE - current_byte,
167 current_byte
0a7de745 168 );
813fb2f6
A
169
170 T_WITH_ERRNO;
171 T_ASSERT_NE(
172 local_result,
173 -1,
174 "Should be able to write to "
175 "a class A file when unlocked"
0a7de745 176 );
813fb2f6
A
177
178 current_byte += local_result;
179 }
180
181 current_byte = 0;
182
0a7de745 183 while (current_byte < CPT_IO_SIZE) {
813fb2f6
A
184 local_result = pread(
185 g_fd,
186 &rd_buffer[current_byte],
187 CPT_IO_SIZE - current_byte,
188 current_byte
0a7de745 189 );
813fb2f6
A
190
191 T_WITH_ERRNO;
192 T_ASSERT_NE(
193 local_result,
194 -1,
195 "Should be able to read from "
196 "a class A file when unlocked"
0a7de745 197 );
813fb2f6
A
198
199 current_byte += local_result;
200 }
201
202 /*
203 * Again, but now while locked; and try to change the file class
204 * as well.
205 */
206 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
207
208 T_ASSERT_LE(
209 pread(g_fd, rd_buffer, CPT_IO_SIZE, 0),
210 0,
211 "Should not be able to read from a class A file when locked"
0a7de745 212 );
813fb2f6
A
213
214 T_ASSERT_LE(
215 pwrite(g_fd, wr_buffer, CPT_IO_SIZE, 0),
216 0,
217 "Should not be able to write to a class A file when locked"
0a7de745 218 );
813fb2f6
A
219
220 T_ASSERT_EQ(
221 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_D),
222 -1,
223 "Should not be able to change protection "
224 "from class A to class D when locked"
0a7de745 225 );
813fb2f6
A
226
227 /* Try to open and truncate the file. */
228 close(g_fd);
229
230 T_ASSERT_EQ(
0a7de745 231 g_fd = open(g_filepath, O_RDWR | O_TRUNC | O_CLOEXEC),
813fb2f6
A
232 -1,
233 "Should not be able to open and truncate "
234 "a class A file when locked"
0a7de745 235 );
813fb2f6
A
236
237 /* Try to open the file */
238 T_ASSERT_EQ(
0a7de745 239 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
813fb2f6
A
240 -1,
241 "Should not be able to open a class A file when locked"
0a7de745 242 );
813fb2f6
A
243
244 /* What about class B files? */
245 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
246
247 T_ASSERT_GE(
0a7de745 248 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
813fb2f6
A
249 0,
250 "Should be able to open a class A file when unlocked"
0a7de745 251 );
813fb2f6
A
252
253 T_WITH_ERRNO;
254 T_ASSERT_EQ(
255 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_D),
256 0,
257 "Should be able to change protection "
258 "class from A to D when unlocked"
0a7de745 259 );
813fb2f6
A
260
261 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
262
263 /* Can we create a class B file while locked? */
264 T_ASSERT_EQ(
265 SET_PROT_CLASS(g_fd, PROTECTION_CLASS_B),
266 0,
267 "Should be able to change protection "
268 "class from D to B when locked"
0a7de745 269 );
813fb2f6
A
270
271 T_ASSERT_EQ(
272 GET_PROT_CLASS(g_fd),
273 PROTECTION_CLASS_B,
274 "File should now have class B protection"
0a7de745 275 );
813fb2f6
A
276
277 /*
278 * We should also be able to read/write to the
279 * file descriptor while it is open.
280 */
281 current_byte = 0;
282
0a7de745 283 while (current_byte < CPT_IO_SIZE) {
813fb2f6
A
284 local_result = pwrite(
285 g_fd,
286 &wr_buffer[current_byte],
287 CPT_IO_SIZE - current_byte,
288 current_byte
0a7de745 289 );
813fb2f6
A
290
291 T_WITH_ERRNO;
292 T_ASSERT_NE(
293 local_result,
294 -1,
295 "Should be able to write to a "
296 "new class B file when locked"
0a7de745 297 );
813fb2f6
A
298
299 current_byte += local_result;
300 }
301
302 current_byte = 0;
303
0a7de745 304 while (current_byte < CPT_IO_SIZE) {
813fb2f6
A
305 local_result = pread(
306 g_fd,
307 &rd_buffer[current_byte],
308 CPT_IO_SIZE - current_byte,
309 current_byte
0a7de745 310 );
813fb2f6
A
311
312 T_ASSERT_NE(
313 local_result,
314 -1,
315 "Should be able to read from a "
316 "new class B file when locked"
0a7de745 317 );
813fb2f6
A
318
319 current_byte += local_result;
320 }
321
322 /* We should not be able to open a class B file under lock. */
323 close(g_fd);
324 T_WITH_ERRNO;
325 T_ASSERT_EQ(
0a7de745 326 g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
813fb2f6
A
327 -1,
328 "Should not be able to open a class B file when locked"
0a7de745 329 );
813fb2f6
A
330
331 unlink(g_filepath);
332
333 /* We still need to test directory semantics. */
334 T_WITH_ERRNO;
335 T_ASSERT_NE(
336 mkdir(g_dirpath, 0x0777),
337 -1,
338 "Should be able to create a new directory when locked"
0a7de745 339 );
813fb2f6
A
340
341 /* The newly created directory should not have a protection class. */
342 T_ASSERT_NE(
0a7de745 343 g_dir_fd = open(g_dirpath, O_RDONLY | O_CLOEXEC),
813fb2f6
A
344 -1,
345 "Should be able to open an unclassed directory when locked"
0a7de745 346 );
813fb2f6
A
347
348 T_ASSERT_TRUE(
349 GET_PROT_CLASS(g_dir_fd) == PROTECTION_CLASS_D ||
350 GET_PROT_CLASS(g_dir_fd) == PROTECTION_CLASS_DIR_NONE,
351 "Directory protection class sholud be D or NONE"
0a7de745 352 );
813fb2f6
A
353
354 T_ASSERT_EQ(
355 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_A),
356 0,
357 "Should be able to change a directory from "
358 "class D to class A while locked"
0a7de745 359 );
813fb2f6
A
360
361 T_ASSERT_EQ(
362 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_D),
363 0,
364 "Should be able to change a directory from "
365 "class A to class D while locked"
0a7de745 366 );
813fb2f6
A
367
368 /*
369 * Do all files created in the directory properly inherit the
370 * directory's protection class?
371 */
372 T_SETUPBEGIN;
373 T_ASSERT_LT(
374 strlcpy(g_filepath, g_dirpath, PATH_MAX),
375 PATH_MAX,
376 "Construct path for file in the directory"
0a7de745 377 );
813fb2f6
A
378 T_ASSERT_LT(
379 strlcat(g_filepath, "test_file", PATH_MAX),
380 PATH_MAX,
381 "Construct path for file in the directory"
0a7de745 382 );
813fb2f6
A
383 T_SETUPEND;
384
385 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
386
0a7de745 387 for (
813fb2f6
A
388 new_prot_class = PROTECTION_CLASS_A;
389 new_prot_class <= PROTECTION_CLASS_D;
390 new_prot_class++
0a7de745 391 ) {
813fb2f6
A
392 int getclass_dir;
393
394 T_WITH_ERRNO;
395 T_ASSERT_NE(
396 old_prot_class = GET_PROT_CLASS(g_dir_fd),
397 -1,
398 "Get protection class for the directory"
0a7de745 399 );
813fb2f6
A
400
401 T_WITH_ERRNO;
402 T_ASSERT_EQ(
403 SET_PROT_CLASS(g_dir_fd, new_prot_class),
404 0,
405 "Should be able to change directory "
406 "protection from %s to %s",
407 dp_class_num_to_string(old_prot_class),
408 dp_class_num_to_string(new_prot_class)
0a7de745 409 );
813fb2f6
A
410
411 T_EXPECT_EQ(
412 getclass_dir = GET_PROT_CLASS(g_dir_fd),
413 new_prot_class,
414 "Get protection class for the directory"
0a7de745 415 );
813fb2f6
A
416
417 T_WITH_ERRNO;
418 T_ASSERT_GE(
0a7de745 419 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777),
813fb2f6
A
420 0,
421 "Should be able to create file in "
422 "%s directory when unlocked",
423 dp_class_num_to_string(new_prot_class)
0a7de745 424 );
813fb2f6
A
425
426 T_WITH_ERRNO;
427 T_ASSERT_NE(
428 local_result = GET_PROT_CLASS(g_fd),
429 -1,
430 "Get the new file's protection class"
0a7de745 431 );
813fb2f6
A
432
433 T_ASSERT_EQ(
434 local_result,
435 new_prot_class,
436 "File should have %s protection",
437 dp_class_num_to_string(new_prot_class)
0a7de745 438 );
813fb2f6
A
439
440 close(g_fd);
441 unlink(g_filepath);
442 }
443
444 /* Do we disallow creation of a class F directory? */
445 T_ASSERT_NE(
446 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_F),
447 0,
448 "Should not be able to create class F directory"
0a7de745 449 );
813fb2f6
A
450
451 /*
452 * Are class A and class B semantics followed for when
453 * we create these files during lock?
454 */
455 T_WITH_ERRNO;
456 T_ASSERT_EQ(
457 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_A),
458 0,
459 "Should be able to change protection "
460 "from class F to class A when unlocked"
0a7de745 461 );
813fb2f6
A
462
463 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
464
465 T_ASSERT_EQ(
0a7de745 466 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777),
813fb2f6
A
467 -1,
468 "Should not be able to create a new file "
469 "in a class A directory when locked"
0a7de745 470 );
813fb2f6
A
471
472 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
473
474 T_WITH_ERRNO;
475 T_ASSERT_EQ(
476 SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_B),
477 0,
478 "Should be able to change directory "
479 "from class A to class B when unlocked"
0a7de745 480 );
813fb2f6
A
481
482 T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
483
484 T_ASSERT_GE(
0a7de745 485 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777),
813fb2f6
A
486 0,
487 "Should be able to create a new file "
488 "in class B directory when locked"
0a7de745 489 );
813fb2f6
A
490
491 T_ASSERT_NE(
492 local_result = GET_PROT_CLASS(g_fd),
493 -1,
494 "Get the new file's protection class"
0a7de745 495 );
813fb2f6
A
496
497 T_ASSERT_EQ(
498 local_result,
499 PROTECTION_CLASS_B,
500 "File should inherit protection class of class B directory"
0a7de745 501 );
813fb2f6
A
502
503 /* What happens when we try to create new subdirectories? */
504 T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
505
0a7de745 506 for (
813fb2f6
A
507 new_prot_class = PROTECTION_CLASS_A;
508 new_prot_class <= PROTECTION_CLASS_D;
509 new_prot_class++
0a7de745 510 ) {
813fb2f6
A
511 T_WITH_ERRNO;
512 T_ASSERT_EQ(
513 SET_PROT_CLASS(g_dir_fd, new_prot_class),
514 0,
515 "Change directory to %s",
516 dp_class_num_to_string(new_prot_class)
0a7de745 517 );
813fb2f6
A
518
519 T_WITH_ERRNO;
520 T_ASSERT_NE(
521 mkdir(g_subdirpath, 0x0777),
522 -1,
523 "Create subdirectory in %s directory",
524 dp_class_num_to_string(new_prot_class)
0a7de745 525 );
813fb2f6
A
526
527 T_WITH_ERRNO;
528 T_ASSERT_NE(
0a7de745 529 g_subdir_fd = open(g_subdirpath, O_RDONLY | O_CLOEXEC),
813fb2f6
A
530 -1,
531 "Should be able to open subdirectory in %s directory",
532 dp_class_num_to_string(new_prot_class)
0a7de745 533 );
813fb2f6
A
534
535 T_ASSERT_NE(
536 local_result = GET_PROT_CLASS(g_subdir_fd),
537 -1,
538 "Get protection class of new subdirectory "
539 "of %s directory",
540 dp_class_num_to_string(new_prot_class)
0a7de745 541 );
813fb2f6
A
542
543 T_ASSERT_EQ(
544 local_result,
545 new_prot_class,
546 "New subdirectory should have same class as %s parent",
547 dp_class_num_to_string(new_prot_class)
0a7de745 548 );
813fb2f6
A
549
550 close(g_subdir_fd);
551 rmdir(g_subdirpath);
552 }
553}
554
555void
0a7de745
A
556setup(void)
557{
813fb2f6
A
558 int ret = 0;
559 int local_result = -1;
560
561 T_SETUPBEGIN;
562
563 T_ATEND(cleanup);
564
565 T_WITH_ERRNO;
566 T_ASSERT_NOTNULL(
567 mkdtemp(g_test_tempdir),
568 "Create temporary directory for test"
0a7de745 569 );
813fb2f6
A
570 T_LOG("Test temp dir: %s", g_test_tempdir);
571
572 T_ASSERT_NE(
573 local_result = supports_content_prot(),
574 -1,
575 "Get content protection support status"
0a7de745 576 );
813fb2f6 577
0a7de745 578 if (local_result == 0) {
813fb2f6
A
579 T_SKIP("Data protection not supported on this system");
580 }
581
a39ff7e2 582 T_ASSERT_EQ(
813fb2f6 583 has_passcode(),
a39ff7e2
A
584 0,
585 "Device should not have existing passcode"
0a7de745 586 );
813fb2f6
A
587
588 T_ASSERT_EQ(
589 set_passcode(TEST_PASSCODE, NULL),
590 0,
591 "Set test passcode"
0a7de745 592 );
813fb2f6
A
593
594 bzero(g_filepath, PATH_MAX);
595 bzero(g_dirpath, PATH_MAX);
596 bzero(g_subdirpath, PATH_MAX);
597
598 ret |= (strlcat(g_filepath, g_test_tempdir, PATH_MAX) == PATH_MAX);
599 ret |= (strlcat(g_filepath, "/", PATH_MAX) == PATH_MAX);
600 ret |= (strlcpy(g_dirpath, g_filepath, PATH_MAX) == PATH_MAX);
601 ret |= (strlcat(g_filepath, "test_file", PATH_MAX) == PATH_MAX);
602 ret |= (strlcat(g_dirpath, "test_dir/", PATH_MAX) == PATH_MAX);
603 ret |= (strlcpy(g_subdirpath, g_dirpath, PATH_MAX) == PATH_MAX);
604 ret |= (strlcat(g_subdirpath, "test_subdir/", PATH_MAX) == PATH_MAX);
605
606 T_QUIET;
607 T_ASSERT_EQ(ret, 0, "Initialize test path strings");
608
609 T_WITH_ERRNO;
610 T_ASSERT_GE(
0a7de745 611 g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777),
813fb2f6
A
612 0,
613 "Create test file"
0a7de745 614 );
813fb2f6
A
615
616 T_SETUPEND;
617}
618
619void
0a7de745
A
620cleanup(void)
621{
813fb2f6
A
622 T_LOG("Cleaning up…");
623
0a7de745 624 if (g_subdir_fd >= 0) {
813fb2f6
A
625 T_LOG("Cleanup: closing fd %d", g_subdir_fd);
626 close(g_subdir_fd);
627 }
628
0a7de745 629 if (g_subdirpath[0]) {
813fb2f6
A
630 T_LOG("Cleanup: removing %s", g_subdirpath);
631 rmdir(g_subdirpath);
632 }
633
0a7de745 634 if (g_fd >= 0) {
813fb2f6
A
635 T_LOG("Cleanup: closing fd %d", g_fd);
636 close(g_fd);
637 }
638
0a7de745 639 if (g_filepath[0]) {
813fb2f6
A
640 T_LOG("Cleanup: removing %s", g_filepath);
641 unlink(g_filepath);
642 }
643
0a7de745 644 if (g_dir_fd >= 0) {
813fb2f6
A
645 T_LOG("Cleanup: closing fd %d", g_dir_fd);
646 close(g_dir_fd);
647 }
648
0a7de745 649 if (g_dirpath[0]) {
813fb2f6
A
650 T_LOG("Cleanup: removing %s", g_dirpath);
651 rmdir(g_dirpath);
652 }
653
0a7de745 654 if (strcmp(g_test_tempdir, TEMP_DIR_TEMPLATE)) {
813fb2f6
A
655 T_LOG("Cleanup: removing %s", g_test_tempdir);
656 rmdir(g_test_tempdir);
657 }
658
0a7de745 659 if (g_passcode_set) {
813fb2f6 660 T_LOG("Cleanup: unlocking device");
0a7de745 661 if (unlock_device(TEST_PASSCODE)) {
813fb2f6
A
662 T_LOG("Warning: failed to unlock device in cleanup");
663 }
664
665 T_LOG("Cleanup: clearing passcode");
0a7de745 666 if (clear_passcode(TEST_PASSCODE)) {
813fb2f6
A
667 T_LOG("Warning: failed to clear passcode in cleanup");
668 }
669 }
670}
671
672int
0a7de745
A
673set_passcode(char * new_passcode, char * old_passcode)
674{
813fb2f6
A
675 int result = -1;
676
677#ifdef KEYBAG_ENTITLEMENTS
678 /* If we're entitled, we can set the passcode ourselves. */
679 uint64_t inputs[] = {device_keybag_handle};
680 uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
681 void * input_structs = NULL;
682 size_t input_struct_count = 0;
683 char buffer[CPT_AKS_BUF_SIZE];
684 char * buffer_ptr = buffer;
685 uint32_t old_passcode_len = 0;
686 uint32_t new_passcode_len = 0;
687
688 T_LOG("%s(): using keybag entitlements", __func__);
689
690 old_passcode_len = strnlen(old_passcode, CPT_MAX_PASS_LEN);
691 new_passcode_len = strnlen(new_passcode, CPT_MAX_PASS_LEN);
692
0a7de745 693 if ((old_passcode == NULL) || (old_passcode_len == CPT_MAX_PASS_LEN)) {
813fb2f6
A
694 old_passcode = "";
695 old_passcode_len = 0;
696 }
0a7de745 697 if ((new_passcode == NULL) || (new_passcode_len == CPT_MAX_PASS_LEN)) {
813fb2f6
A
698 new_passcode = "";
699 new_passcode_len = 0;
700 }
701
702 *((uint32_t *) buffer_ptr) = ((uint32_t) 2);
703 buffer_ptr += sizeof(uint32_t);
704
705 *((uint32_t *) buffer_ptr) = old_passcode_len;
706 buffer_ptr += sizeof(uint32_t);
707
708 memcpy(buffer_ptr, old_passcode, old_passcode_len);
709 buffer_ptr += ((old_passcode_len + sizeof(uint32_t) - 1) &
0a7de745 710 ~(sizeof(uint32_t) - 1));
813fb2f6
A
711
712 *((uint32_t *) buffer_ptr) = new_passcode_len;
713 buffer_ptr += sizeof(uint32_t);
714
715 memcpy(buffer_ptr, new_passcode, new_passcode_len);
716 buffer_ptr += ((new_passcode_len + sizeof(uint32_t) - 1) &
0a7de745 717 ~(sizeof(uint32_t) - 1));
813fb2f6
A
718
719 input_structs = buffer;
720 input_struct_count = (buffer_ptr - buffer);
721
722 result = apple_key_store(
723 kAppleKeyStoreKeyBagSetPasscode,
724 inputs,
725 input_count,
726 input_structs,
727 input_struct_count,
728 NULL,
729 NULL
0a7de745 730 );
813fb2f6
A
731#else
732 /*
733 * If we aren't entitled, we'll need to use
734 * keystorectl to set the passcode.
735 */
736 T_LOG("%s(): using keystorectl", __func__);
737
0a7de745 738 if (
813fb2f6
A
739 (old_passcode == NULL) ||
740 (strnlen(old_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
0a7de745 741 ) {
813fb2f6
A
742 old_passcode = "";
743 }
744
0a7de745 745 if (
813fb2f6
A
746 (new_passcode == NULL) ||
747 (strnlen(new_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
0a7de745 748 ) {
813fb2f6
A
749 new_passcode = "";
750 }
751
752 char * const keystorectl_args[] = {
cb323159
A
753 KEYBAGDTEST_PATH,
754 "syspass",
813fb2f6
A
755 old_passcode,
756 new_passcode,
757 NULL
758 };
759 result = spawn_proc(keystorectl_args);
760#endif /* KEYBAG_ENTITLEMENTS */
0a7de745 761 if (result == 0 && new_passcode != NULL) {
813fb2f6 762 g_passcode_set = 1;
0a7de745 763 } else if (result == 0 && new_passcode == NULL) {
813fb2f6
A
764 g_passcode_set = 0;
765 }
766
0a7de745 767 return result;
813fb2f6
A
768}
769
770int
0a7de745
A
771clear_passcode(char * passcode)
772{
813fb2f6
A
773 /*
774 * For the moment, this will set the passcode to the empty string
775 * (a known value); this will most likely need to change, or running
776 * this test may ruin everything™
777 */
778 return set_passcode(NULL, passcode);
779}
780
781int
0a7de745
A
782has_passcode(void)
783{
813fb2f6
A
784 return set_passcode(NULL, NULL);
785}
786
787int
0a7de745
A
788lock_device(void)
789{
813fb2f6
A
790 int result = -1;
791
792 /*
793 * Pass in the path to keybagdTest instead. By doing this, we bypass
794 * the shortcut to get in to the keybag via IOKit and instead use the
795 * pre-existing command line tool.
796 *
797 * This also goes through the normal "lock → locking (10s) → locked"
798 * flow that would normally occuring during system runtime when the
799 * lock button is depressed. To ensure that our single threaded test
800 * works properly in this case, poll until we can't create a class A
801 * file to be safe.
802 */
803 char * const kbd_args[] = {KEYBAGDTEST_PATH, "lock", NULL};
804 result = spawn_proc(kbd_args);
0a7de745 805 if (result) {
813fb2f6
A
806 return result;
807 }
808
809 /*
810 * Delete the file if it is present. Note that this may fail if the
811 * file is actually not there. So don't bomb out if we can't delete
812 * this file right now.
813 */
814 (void) unlink("/private/var/foo_test_file");
815
0a7de745 816 while (1) {
813fb2f6
A
817 int dp_fd;
818
819 dp_fd = open_dprotected_np(
820 "/private/var/foo_test_file",
0a7de745 821 O_RDWR | O_CREAT,
813fb2f6
A
822 PROTECTION_CLASS_A,
823 0
0a7de745 824 );
813fb2f6 825
0a7de745 826 if (dp_fd >= 0) {
813fb2f6
A
827 /* delete it and sleep */
828 close(dp_fd);
829 result = unlink("/private/var/foo_test_file");
830
0a7de745 831 if (result) {
813fb2f6
A
832 return result;
833 }
834
835 sync();
836 sleep(1);
837 } else {
838 /* drop out of our polling loop. */
839 break;
840 }
841 }
842
843 /*
844 * Note that our loop breakout condition is whether or not we can
845 * create a class A file, so that loop may execute up to 10 times
846 * (due to the 10s grace period). By the time we get here, we assume
847 * that we didn't hit any of the error cases above.
848 */
849
850 return 0;
851}
852
853int
0a7de745
A
854unlock_device(char * passcode)
855{
813fb2f6
A
856 int result = -1;
857
858#ifdef KEYBAG_ENTITLEMENTS
859 /* If we're entitled, we can unlock the device ourselves. */
860 uint64_t inputs[] = {device_keybag_handle};
861 uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
862 size_t input_struct_count = 0;
863
864 T_LOG("%s(): using keybag entitlements", __func__);
865
866 input_struct_count = strnlen(passcode, CPT_MAX_PASS_LEN);
0a7de745 867 if ((passcode == NULL) || (input_struct_count == CPT_MAX_PASS_LEN)) {
813fb2f6
A
868 passcode = "";
869 input_struct_count = 0;
870 }
871
872 result = apple_key_store(
873 kAppleKeyStoreKeyBagUnlock,
874 inputs,
875 input_count,
876 passcode,
877 input_struct_count,
878 NULL,
879 NULL
0a7de745 880 );
813fb2f6
A
881#else
882 /*
883 * If we aren't entitled, we'll need to use
884 * keystorectl to unlock the device.
885 */
886 T_LOG("%s(): using keystorectl", __func__);
887
0a7de745 888 if (
813fb2f6
A
889 (passcode == NULL) ||
890 (strnlen(passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
0a7de745 891 ) {
813fb2f6
A
892 passcode = "";
893 }
894
895 char * const keystorectl_args[] = {
896 KEYSTORECTL_PATH, "unlock", passcode, NULL
897 };
898
899 result = spawn_proc(keystorectl_args);
900#endif /* KEYBAG_ENTITLEMENTS */
901
0a7de745 902 return result;
813fb2f6
A
903}
904
905/*
906 * Code based on Mobile Key Bag; specifically
907 * MKBDeviceSupportsContentProtection and
908 * MKBDeviceFormattedForContentProtection.
909 *
910 * We want to verify that we support content protection, and that
911 * we are formatted for it.
912 */
913int
0a7de745
A
914supports_content_prot(void)
915{
813fb2f6
A
916 int local_result = -1;
917 int result = -1;
918 uint32_t buffer_size = 1;
919 char buffer[buffer_size];
920 io_registry_entry_t defaults = IO_OBJECT_NULL;
921 kern_return_t k_result = KERN_FAILURE;
922 struct statfs statfs_results;
923
924 defaults = IORegistryEntryFromPath(
925 kIOMasterPortDefault,
926 kIODeviceTreePlane ":/defaults"
0a7de745 927 );
813fb2f6 928
0a7de745 929 if (defaults == IO_OBJECT_NULL) {
813fb2f6
A
930 /* Assume data protection is unsupported */
931 T_LOG(
932 "%s(): no defaults entry in IORegistry",
933 __func__
0a7de745 934 );
813fb2f6
A
935 return 0;
936 }
937
938 k_result = IORegistryEntryGetProperty(
939 defaults,
940 "content-protect",
941 buffer,
942 &buffer_size
0a7de745 943 );
813fb2f6 944
0a7de745 945 if (k_result != KERN_SUCCESS) {
813fb2f6
A
946 /* Assume data protection is unsupported */
947 T_LOG(
948 "%s(): no content-protect property in IORegistry",
949 __func__
0a7de745 950 );
813fb2f6
A
951 return 0;
952 }
953
954 /*
955 * At this point, we SUPPORT content protection… but are we
956 * formatted for it? This is ugly; we should be testing the file
957 * system we'll be testing in, not just /tmp/.
958 */
959 local_result = statfs(g_test_tempdir, &statfs_results);
960
0a7de745 961 if (local_result == -1) {
813fb2f6
A
962 T_LOG(
963 "%s(): failed to statfs the test directory, errno = %s",
964 __func__, strerror(errno)
0a7de745 965 );
813fb2f6 966 return -1;
0a7de745 967 } else if (statfs_results.f_flags & MNT_CPROTECT) {
813fb2f6
A
968 return 1;
969 } else {
970 T_LOG(
971 "%s(): filesystem not formatted for data protection",
972 __func__
0a7de745 973 );
813fb2f6
A
974 return 0;
975 }
976}
977
978/*
979 * Shamelessly ripped from keystorectl routines;
980 * a wrapper for invoking the AKS user client.
981 */
982int
983apple_key_store(uint32_t command,
0a7de745
A
984 uint64_t * inputs,
985 uint32_t input_count,
986 void * input_structs,
987 size_t input_struct_count,
988 uint64_t * outputs,
989 uint32_t * output_count)
990{
813fb2f6
A
991 int result = -1;
992 io_connect_t connection = IO_OBJECT_NULL;
993 io_registry_entry_t apple_key_bag_service = IO_OBJECT_NULL;
994 kern_return_t k_result = KERN_FAILURE;
995 IOReturn io_result = IO_OBJECT_NULL;
996
997 apple_key_bag_service = IOServiceGetMatchingService(
998 kIOMasterPortDefault,
999 IOServiceMatching(kAppleKeyStoreServiceName)
0a7de745
A
1000 );
1001 if (apple_key_bag_service == IO_OBJECT_NULL) {
813fb2f6
A
1002 T_LOG(
1003 "%s: failed to match kAppleKeyStoreServiceName",
1004 __func__
0a7de745 1005 );
813fb2f6
A
1006 goto end;
1007 }
1008
1009 k_result = IOServiceOpen(
1010 apple_key_bag_service,
1011 mach_task_self(),
1012 0,
1013 &connection
0a7de745
A
1014 );
1015 if (k_result != KERN_SUCCESS) {
813fb2f6
A
1016 T_LOG(
1017 "%s: failed to open AppleKeyStore: "
1018 "IOServiceOpen() returned %d",
1019 __func__, k_result
0a7de745 1020 );
813fb2f6
A
1021 goto end;
1022 }
1023
1024 k_result = IOConnectCallMethod(
1025 connection,
1026 kAppleKeyStoreUserClientOpen,
1027 NULL, 0, NULL, 0, NULL, NULL, NULL, NULL
0a7de745
A
1028 );
1029 if (k_result != KERN_SUCCESS) {
813fb2f6
A
1030 T_LOG(
1031 "%s: call to AppleKeyStore method "
1032 "kAppleKeyStoreUserClientOpen failed",
1033 __func__
0a7de745 1034 );
813fb2f6
A
1035 goto close;
1036 }
1037
1038 io_result = IOConnectCallMethod(
1039 connection, command, inputs, input_count, input_structs,
1040 input_struct_count, outputs, output_count, NULL, NULL
0a7de745
A
1041 );
1042 if (io_result != kIOReturnSuccess) {
c3c9b80d 1043 T_LOG("%s: call to AppleKeyStore method %d failed", __func__, command);
813fb2f6
A
1044 goto close;
1045 }
1046
1047 result = 0;
1048
1049close:
1050 IOServiceClose(apple_key_bag_service);
1051end:
0a7de745 1052 return result;
813fb2f6
A
1053}
1054
1055/*
a39ff7e2 1056 * Helper function for launching tools
813fb2f6
A
1057 */
1058int
0a7de745
A
1059spawn_proc(char * const command[])
1060{
a39ff7e2
A
1061 pid_t pid = 0;
1062 int launch_tool_ret = 0;
1063 bool waitpid_ret = true;
1064 int status = 0;
1065 int signal = 0;
1066 int timeout = 30;
1067
1068 launch_tool_ret = dt_launch_tool(&pid, command, false, NULL, NULL);
1069 T_EXPECT_EQ(launch_tool_ret, 0, "launch tool: %s", command[0]);
0a7de745 1070 if (launch_tool_ret != 0) {
813fb2f6 1071 return 1;
813fb2f6
A
1072 }
1073
a39ff7e2
A
1074 waitpid_ret = dt_waitpid(pid, &status, &signal, timeout);
1075 T_EXPECT_TRUE(waitpid_ret, "%s should succeed", command[0]);
0a7de745
A
1076 if (waitpid_ret == false) {
1077 if (status != 0) {
a39ff7e2
A
1078 T_LOG("%s exited %d", command[0], status);
1079 }
0a7de745 1080 if (signal != 0) {
a39ff7e2
A
1081 T_LOG("%s received signal %d", command[0], signal);
1082 }
813fb2f6
A
1083 return 1;
1084 }
1085
1086 return 0;
1087}
1088
1089char*
0a7de745
A
1090dp_class_num_to_string(int num)
1091{
1092 switch (num) {
1093 case 0:
1094 return "unclassed";
1095 case PROTECTION_CLASS_A:
1096 return "class A";
1097 case PROTECTION_CLASS_B:
1098 return "class B";
1099 case PROTECTION_CLASS_C:
1100 return "class C";
1101 case PROTECTION_CLASS_D:
1102 return "class D";
1103 case PROTECTION_CLASS_E:
1104 return "class E";
1105 case PROTECTION_CLASS_F:
1106 return "class F";
1107 default:
1108 return "<unknown class>";
813fb2f6
A
1109 }
1110}
1111
1112#if 0
0a7de745
A
1113int
1114device_lock_state(void)
1115{
813fb2f6
A
1116 /*
1117 * TODO: Actually implement this.
1118 *
1119 * We fail if a passcode already exists, and the methods being used
1120 * to lock/unlock the device in this test appear to be synchronous…
1121 * do we need this function?
1122 */
1123 int result = -1;
1124
0a7de745 1125 return result;
813fb2f6
A
1126}
1127
1128/* Determines if we will try to test class C semanatics. */
0a7de745
A
1129int
1130unlocked_since_boot()
1131{
813fb2f6
A
1132 /*
1133 * TODO: Actually implement this.
1134 *
1135 * The actual semantics for CP mean that even with this primative,
1136 * we would need to set a passcode and then reboot the device in
1137 * order to test this; this function will probably be rather
1138 * worthless as a result.
1139 */
1140 int result = 1;
1141
0a7de745 1142 return result;
813fb2f6
A
1143}
1144#endif