]>
Commit | Line | Data |
---|---|---|
1 | #include <darwintest.h> | |
2 | #include <darwintest_utils.h> | |
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 | ||
32 | int g_fd = -1; | |
33 | int g_dir_fd = -1; | |
34 | int g_subdir_fd = -1; | |
35 | int g_passcode_set = 0; | |
36 | ||
37 | char g_test_tempdir[PATH_MAX] = TEMP_DIR_TEMPLATE; | |
38 | char g_filepath[PATH_MAX] = ""; | |
39 | char g_dirpath[PATH_MAX] = ""; | |
40 | char g_subdirpath[PATH_MAX] = ""; | |
41 | ||
42 | int 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 | |
50 | ); | |
51 | int spawn_proc(char * const command[]); | |
52 | int supports_content_prot(void); | |
53 | char* dp_class_num_to_string(int num); | |
54 | int lock_device(void); | |
55 | int unlock_device(char * passcode); | |
56 | int set_passcode(char * new_passcode, char * old_passcode); | |
57 | int clear_passcode(char * passcode); | |
58 | int has_passcode(void); | |
59 | void setup(void); | |
60 | void cleanup(void); | |
61 | ||
62 | T_DECL(data_protection, | |
63 | "Verify behavior of the various data protection classes") { | |
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 | */ | |
77 | for ( | |
78 | new_prot_class = PROTECTION_CLASS_A; | |
79 | new_prot_class <= PROTECTION_CLASS_F; | |
80 | new_prot_class++ | |
81 | ) { | |
82 | T_ASSERT_NE( | |
83 | old_prot_class = GET_PROT_CLASS(g_fd), | |
84 | -1, | |
85 | "Get protection class when locked" | |
86 | ); | |
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) | |
95 | ); | |
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" | |
108 | ); | |
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( | |
122 | g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC), | |
123 | 0, | |
124 | "Recreate test file" | |
125 | ); | |
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" | |
136 | ); | |
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" | |
146 | ); | |
147 | ||
148 | close(g_fd); | |
149 | ||
150 | T_WITH_ERRNO; | |
151 | T_ASSERT_GE( | |
152 | g_fd = open(g_filepath, O_RDWR | O_CLOEXEC), | |
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 | ||
162 | while (current_byte < CPT_IO_SIZE) { | |
163 | local_result = pwrite( | |
164 | g_fd, | |
165 | &wr_buffer[current_byte], | |
166 | CPT_IO_SIZE - current_byte, | |
167 | current_byte | |
168 | ); | |
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" | |
176 | ); | |
177 | ||
178 | current_byte += local_result; | |
179 | } | |
180 | ||
181 | current_byte = 0; | |
182 | ||
183 | while (current_byte < CPT_IO_SIZE) { | |
184 | local_result = pread( | |
185 | g_fd, | |
186 | &rd_buffer[current_byte], | |
187 | CPT_IO_SIZE - current_byte, | |
188 | current_byte | |
189 | ); | |
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" | |
197 | ); | |
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" | |
212 | ); | |
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" | |
218 | ); | |
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" | |
225 | ); | |
226 | ||
227 | /* Try to open and truncate the file. */ | |
228 | close(g_fd); | |
229 | ||
230 | T_ASSERT_EQ( | |
231 | g_fd = open(g_filepath, O_RDWR | O_TRUNC | O_CLOEXEC), | |
232 | -1, | |
233 | "Should not be able to open and truncate " | |
234 | "a class A file when locked" | |
235 | ); | |
236 | ||
237 | /* Try to open the file */ | |
238 | T_ASSERT_EQ( | |
239 | g_fd = open(g_filepath, O_RDWR | O_CLOEXEC), | |
240 | -1, | |
241 | "Should not be able to open a class A file when locked" | |
242 | ); | |
243 | ||
244 | /* What about class B files? */ | |
245 | T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***"); | |
246 | ||
247 | T_ASSERT_GE( | |
248 | g_fd = open(g_filepath, O_RDWR | O_CLOEXEC), | |
249 | 0, | |
250 | "Should be able to open a class A file when unlocked" | |
251 | ); | |
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" | |
259 | ); | |
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" | |
269 | ); | |
270 | ||
271 | T_ASSERT_EQ( | |
272 | GET_PROT_CLASS(g_fd), | |
273 | PROTECTION_CLASS_B, | |
274 | "File should now have class B protection" | |
275 | ); | |
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 | ||
283 | while (current_byte < CPT_IO_SIZE) { | |
284 | local_result = pwrite( | |
285 | g_fd, | |
286 | &wr_buffer[current_byte], | |
287 | CPT_IO_SIZE - current_byte, | |
288 | current_byte | |
289 | ); | |
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" | |
297 | ); | |
298 | ||
299 | current_byte += local_result; | |
300 | } | |
301 | ||
302 | current_byte = 0; | |
303 | ||
304 | while (current_byte < CPT_IO_SIZE) { | |
305 | local_result = pread( | |
306 | g_fd, | |
307 | &rd_buffer[current_byte], | |
308 | CPT_IO_SIZE - current_byte, | |
309 | current_byte | |
310 | ); | |
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" | |
317 | ); | |
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( | |
326 | g_fd = open(g_filepath, O_RDWR | O_CLOEXEC), | |
327 | -1, | |
328 | "Should not be able to open a class B file when locked" | |
329 | ); | |
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" | |
339 | ); | |
340 | ||
341 | /* The newly created directory should not have a protection class. */ | |
342 | T_ASSERT_NE( | |
343 | g_dir_fd = open(g_dirpath, O_RDONLY | O_CLOEXEC), | |
344 | -1, | |
345 | "Should be able to open an unclassed directory when locked" | |
346 | ); | |
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" | |
352 | ); | |
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" | |
359 | ); | |
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" | |
366 | ); | |
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" | |
377 | ); | |
378 | T_ASSERT_LT( | |
379 | strlcat(g_filepath, "test_file", PATH_MAX), | |
380 | PATH_MAX, | |
381 | "Construct path for file in the directory" | |
382 | ); | |
383 | T_SETUPEND; | |
384 | ||
385 | T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***"); | |
386 | ||
387 | for ( | |
388 | new_prot_class = PROTECTION_CLASS_A; | |
389 | new_prot_class <= PROTECTION_CLASS_D; | |
390 | new_prot_class++ | |
391 | ) { | |
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" | |
399 | ); | |
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) | |
409 | ); | |
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" | |
415 | ); | |
416 | ||
417 | T_WITH_ERRNO; | |
418 | T_ASSERT_GE( | |
419 | g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777), | |
420 | 0, | |
421 | "Should be able to create file in " | |
422 | "%s directory when unlocked", | |
423 | dp_class_num_to_string(new_prot_class) | |
424 | ); | |
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" | |
431 | ); | |
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) | |
438 | ); | |
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" | |
449 | ); | |
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" | |
461 | ); | |
462 | ||
463 | T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***"); | |
464 | ||
465 | T_ASSERT_EQ( | |
466 | g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777), | |
467 | -1, | |
468 | "Should not be able to create a new file " | |
469 | "in a class A directory when locked" | |
470 | ); | |
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" | |
480 | ); | |
481 | ||
482 | T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***"); | |
483 | ||
484 | T_ASSERT_GE( | |
485 | g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777), | |
486 | 0, | |
487 | "Should be able to create a new file " | |
488 | "in class B directory when locked" | |
489 | ); | |
490 | ||
491 | T_ASSERT_NE( | |
492 | local_result = GET_PROT_CLASS(g_fd), | |
493 | -1, | |
494 | "Get the new file's protection class" | |
495 | ); | |
496 | ||
497 | T_ASSERT_EQ( | |
498 | local_result, | |
499 | PROTECTION_CLASS_B, | |
500 | "File should inherit protection class of class B directory" | |
501 | ); | |
502 | ||
503 | /* What happens when we try to create new subdirectories? */ | |
504 | T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***"); | |
505 | ||
506 | for ( | |
507 | new_prot_class = PROTECTION_CLASS_A; | |
508 | new_prot_class <= PROTECTION_CLASS_D; | |
509 | new_prot_class++ | |
510 | ) { | |
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) | |
517 | ); | |
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) | |
525 | ); | |
526 | ||
527 | T_WITH_ERRNO; | |
528 | T_ASSERT_NE( | |
529 | g_subdir_fd = open(g_subdirpath, O_RDONLY | O_CLOEXEC), | |
530 | -1, | |
531 | "Should be able to open subdirectory in %s directory", | |
532 | dp_class_num_to_string(new_prot_class) | |
533 | ); | |
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) | |
541 | ); | |
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) | |
548 | ); | |
549 | ||
550 | close(g_subdir_fd); | |
551 | rmdir(g_subdirpath); | |
552 | } | |
553 | } | |
554 | ||
555 | void | |
556 | setup(void) | |
557 | { | |
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" | |
569 | ); | |
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" | |
576 | ); | |
577 | ||
578 | if (local_result == 0) { | |
579 | T_SKIP("Data protection not supported on this system"); | |
580 | } | |
581 | ||
582 | T_ASSERT_EQ( | |
583 | has_passcode(), | |
584 | 0, | |
585 | "Device should not have existing passcode" | |
586 | ); | |
587 | ||
588 | T_ASSERT_EQ( | |
589 | set_passcode(TEST_PASSCODE, NULL), | |
590 | 0, | |
591 | "Set test passcode" | |
592 | ); | |
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( | |
611 | g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777), | |
612 | 0, | |
613 | "Create test file" | |
614 | ); | |
615 | ||
616 | T_SETUPEND; | |
617 | } | |
618 | ||
619 | void | |
620 | cleanup(void) | |
621 | { | |
622 | T_LOG("Cleaning up…"); | |
623 | ||
624 | if (g_subdir_fd >= 0) { | |
625 | T_LOG("Cleanup: closing fd %d", g_subdir_fd); | |
626 | close(g_subdir_fd); | |
627 | } | |
628 | ||
629 | if (g_subdirpath[0]) { | |
630 | T_LOG("Cleanup: removing %s", g_subdirpath); | |
631 | rmdir(g_subdirpath); | |
632 | } | |
633 | ||
634 | if (g_fd >= 0) { | |
635 | T_LOG("Cleanup: closing fd %d", g_fd); | |
636 | close(g_fd); | |
637 | } | |
638 | ||
639 | if (g_filepath[0]) { | |
640 | T_LOG("Cleanup: removing %s", g_filepath); | |
641 | unlink(g_filepath); | |
642 | } | |
643 | ||
644 | if (g_dir_fd >= 0) { | |
645 | T_LOG("Cleanup: closing fd %d", g_dir_fd); | |
646 | close(g_dir_fd); | |
647 | } | |
648 | ||
649 | if (g_dirpath[0]) { | |
650 | T_LOG("Cleanup: removing %s", g_dirpath); | |
651 | rmdir(g_dirpath); | |
652 | } | |
653 | ||
654 | if (strcmp(g_test_tempdir, TEMP_DIR_TEMPLATE)) { | |
655 | T_LOG("Cleanup: removing %s", g_test_tempdir); | |
656 | rmdir(g_test_tempdir); | |
657 | } | |
658 | ||
659 | if (g_passcode_set) { | |
660 | T_LOG("Cleanup: unlocking device"); | |
661 | if (unlock_device(TEST_PASSCODE)) { | |
662 | T_LOG("Warning: failed to unlock device in cleanup"); | |
663 | } | |
664 | ||
665 | T_LOG("Cleanup: clearing passcode"); | |
666 | if (clear_passcode(TEST_PASSCODE)) { | |
667 | T_LOG("Warning: failed to clear passcode in cleanup"); | |
668 | } | |
669 | } | |
670 | } | |
671 | ||
672 | int | |
673 | set_passcode(char * new_passcode, char * old_passcode) | |
674 | { | |
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 | ||
693 | if ((old_passcode == NULL) || (old_passcode_len == CPT_MAX_PASS_LEN)) { | |
694 | old_passcode = ""; | |
695 | old_passcode_len = 0; | |
696 | } | |
697 | if ((new_passcode == NULL) || (new_passcode_len == CPT_MAX_PASS_LEN)) { | |
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) & | |
710 | ~(sizeof(uint32_t) - 1)); | |
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) & | |
717 | ~(sizeof(uint32_t) - 1)); | |
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 | |
730 | ); | |
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 | ||
738 | if ( | |
739 | (old_passcode == NULL) || | |
740 | (strnlen(old_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN) | |
741 | ) { | |
742 | old_passcode = ""; | |
743 | } | |
744 | ||
745 | if ( | |
746 | (new_passcode == NULL) || | |
747 | (strnlen(new_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN) | |
748 | ) { | |
749 | new_passcode = ""; | |
750 | } | |
751 | ||
752 | char * const keystorectl_args[] = { | |
753 | KEYBAGDTEST_PATH, | |
754 | "syspass", | |
755 | old_passcode, | |
756 | new_passcode, | |
757 | NULL | |
758 | }; | |
759 | result = spawn_proc(keystorectl_args); | |
760 | #endif /* KEYBAG_ENTITLEMENTS */ | |
761 | if (result == 0 && new_passcode != NULL) { | |
762 | g_passcode_set = 1; | |
763 | } else if (result == 0 && new_passcode == NULL) { | |
764 | g_passcode_set = 0; | |
765 | } | |
766 | ||
767 | return result; | |
768 | } | |
769 | ||
770 | int | |
771 | clear_passcode(char * passcode) | |
772 | { | |
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 | ||
781 | int | |
782 | has_passcode(void) | |
783 | { | |
784 | return set_passcode(NULL, NULL); | |
785 | } | |
786 | ||
787 | int | |
788 | lock_device(void) | |
789 | { | |
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); | |
805 | if (result) { | |
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 | ||
816 | while (1) { | |
817 | int dp_fd; | |
818 | ||
819 | dp_fd = open_dprotected_np( | |
820 | "/private/var/foo_test_file", | |
821 | O_RDWR | O_CREAT, | |
822 | PROTECTION_CLASS_A, | |
823 | 0 | |
824 | ); | |
825 | ||
826 | if (dp_fd >= 0) { | |
827 | /* delete it and sleep */ | |
828 | close(dp_fd); | |
829 | result = unlink("/private/var/foo_test_file"); | |
830 | ||
831 | if (result) { | |
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 | ||
853 | int | |
854 | unlock_device(char * passcode) | |
855 | { | |
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); | |
867 | if ((passcode == NULL) || (input_struct_count == CPT_MAX_PASS_LEN)) { | |
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 | |
880 | ); | |
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 | ||
888 | if ( | |
889 | (passcode == NULL) || | |
890 | (strnlen(passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN) | |
891 | ) { | |
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 | ||
902 | return result; | |
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 | */ | |
913 | int | |
914 | supports_content_prot(void) | |
915 | { | |
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" | |
927 | ); | |
928 | ||
929 | if (defaults == IO_OBJECT_NULL) { | |
930 | /* Assume data protection is unsupported */ | |
931 | T_LOG( | |
932 | "%s(): no defaults entry in IORegistry", | |
933 | __func__ | |
934 | ); | |
935 | return 0; | |
936 | } | |
937 | ||
938 | k_result = IORegistryEntryGetProperty( | |
939 | defaults, | |
940 | "content-protect", | |
941 | buffer, | |
942 | &buffer_size | |
943 | ); | |
944 | ||
945 | if (k_result != KERN_SUCCESS) { | |
946 | /* Assume data protection is unsupported */ | |
947 | T_LOG( | |
948 | "%s(): no content-protect property in IORegistry", | |
949 | __func__ | |
950 | ); | |
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 | ||
961 | if (local_result == -1) { | |
962 | T_LOG( | |
963 | "%s(): failed to statfs the test directory, errno = %s", | |
964 | __func__, strerror(errno) | |
965 | ); | |
966 | return -1; | |
967 | } else if (statfs_results.f_flags & MNT_CPROTECT) { | |
968 | return 1; | |
969 | } else { | |
970 | T_LOG( | |
971 | "%s(): filesystem not formatted for data protection", | |
972 | __func__ | |
973 | ); | |
974 | return 0; | |
975 | } | |
976 | } | |
977 | ||
978 | /* | |
979 | * Shamelessly ripped from keystorectl routines; | |
980 | * a wrapper for invoking the AKS user client. | |
981 | */ | |
982 | int | |
983 | apple_key_store(uint32_t command, | |
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 | { | |
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) | |
1000 | ); | |
1001 | if (apple_key_bag_service == IO_OBJECT_NULL) { | |
1002 | T_LOG( | |
1003 | "%s: failed to match kAppleKeyStoreServiceName", | |
1004 | __func__ | |
1005 | ); | |
1006 | goto end; | |
1007 | } | |
1008 | ||
1009 | k_result = IOServiceOpen( | |
1010 | apple_key_bag_service, | |
1011 | mach_task_self(), | |
1012 | 0, | |
1013 | &connection | |
1014 | ); | |
1015 | if (k_result != KERN_SUCCESS) { | |
1016 | T_LOG( | |
1017 | "%s: failed to open AppleKeyStore: " | |
1018 | "IOServiceOpen() returned %d", | |
1019 | __func__, k_result | |
1020 | ); | |
1021 | goto end; | |
1022 | } | |
1023 | ||
1024 | k_result = IOConnectCallMethod( | |
1025 | connection, | |
1026 | kAppleKeyStoreUserClientOpen, | |
1027 | NULL, 0, NULL, 0, NULL, NULL, NULL, NULL | |
1028 | ); | |
1029 | if (k_result != KERN_SUCCESS) { | |
1030 | T_LOG( | |
1031 | "%s: call to AppleKeyStore method " | |
1032 | "kAppleKeyStoreUserClientOpen failed", | |
1033 | __func__ | |
1034 | ); | |
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 | |
1041 | ); | |
1042 | if (io_result != kIOReturnSuccess) { | |
1043 | T_LOG("%s: call to AppleKeyStore method %d failed", __func__, command); | |
1044 | goto close; | |
1045 | } | |
1046 | ||
1047 | result = 0; | |
1048 | ||
1049 | close: | |
1050 | IOServiceClose(apple_key_bag_service); | |
1051 | end: | |
1052 | return result; | |
1053 | } | |
1054 | ||
1055 | /* | |
1056 | * Helper function for launching tools | |
1057 | */ | |
1058 | int | |
1059 | spawn_proc(char * const command[]) | |
1060 | { | |
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]); | |
1070 | if (launch_tool_ret != 0) { | |
1071 | return 1; | |
1072 | } | |
1073 | ||
1074 | waitpid_ret = dt_waitpid(pid, &status, &signal, timeout); | |
1075 | T_EXPECT_TRUE(waitpid_ret, "%s should succeed", command[0]); | |
1076 | if (waitpid_ret == false) { | |
1077 | if (status != 0) { | |
1078 | T_LOG("%s exited %d", command[0], status); | |
1079 | } | |
1080 | if (signal != 0) { | |
1081 | T_LOG("%s received signal %d", command[0], signal); | |
1082 | } | |
1083 | return 1; | |
1084 | } | |
1085 | ||
1086 | return 0; | |
1087 | } | |
1088 | ||
1089 | char* | |
1090 | dp_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>"; | |
1109 | } | |
1110 | } | |
1111 | ||
1112 | #if 0 | |
1113 | int | |
1114 | device_lock_state(void) | |
1115 | { | |
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 | ||
1125 | return result; | |
1126 | } | |
1127 | ||
1128 | /* Determines if we will try to test class C semanatics. */ | |
1129 | int | |
1130 | unlocked_since_boot() | |
1131 | { | |
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 | ||
1142 | return result; | |
1143 | } | |
1144 | #endif |