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