]>
Commit | Line | Data |
---|---|---|
813fb2f6 A |
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 | ||
31 | int g_fd = -1; | |
32 | int g_dir_fd = -1; | |
33 | int g_subdir_fd = -1; | |
34 | int g_passcode_set = 0; | |
35 | ||
36 | char g_test_tempdir[PATH_MAX] = TEMP_DIR_TEMPLATE; | |
37 | char g_filepath[PATH_MAX] = ""; | |
38 | char g_dirpath[PATH_MAX] = ""; | |
39 | char g_subdirpath[PATH_MAX] = ""; | |
40 | ||
41 | int 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 | ); | |
50 | int spawn_proc(char * const command[]); | |
51 | int supports_content_prot(); | |
52 | char* dp_class_num_to_string(int num); | |
53 | int lock_device(void); | |
54 | int unlock_device(char * passcode); | |
55 | int set_passcode(char * new_passcode, char * old_passcode); | |
56 | int clear_passcode(char * passcode); | |
57 | int has_passcode(void); | |
58 | void setup(void); | |
59 | void cleanup(void); | |
60 | ||
61 | T_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 | ||
554 | void | |
555 | setup(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 | ||
617 | void | |
618 | cleanup(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 | ||
669 | int | |
670 | set_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 | ||
766 | int | |
767 | clear_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 | ||
776 | int | |
777 | has_passcode(void) { | |
778 | return set_passcode(NULL, NULL); | |
779 | } | |
780 | ||
781 | int | |
782 | lock_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 | ||
846 | int | |
847 | unlock_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 | */ | |
905 | int | |
906 | supports_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 | */ | |
973 | int | |
974 | apple_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 | ||
1039 | close: | |
1040 | IOServiceClose(apple_key_bag_service); | |
1041 | end: | |
1042 | return(result); | |
1043 | } | |
1044 | ||
1045 | /* | |
1046 | * Just a wrapper around fork/exec for separate | |
1047 | * cmds that may require entitlements. | |
1048 | */ | |
1049 | int | |
1050 | spawn_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 | ||
1101 | char* | |
1102 | dp_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 | |
1124 | int 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. */ | |
1138 | int 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 |