mirror of
https://github.com/darlinghq/darling-xnu.git
synced 2024-11-23 12:39:55 +00:00
1145 lines
25 KiB
C
1145 lines
25 KiB
C
#include <darwintest.h>
|
|
#include <darwintest_utils.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
|
|
#include <Kernel/sys/content_protection.h>
|
|
|
|
#define CPT_IO_SIZE 4096
|
|
#define CPT_AKS_BUF_SIZE 256
|
|
#define CPT_MAX_PASS_LEN 64
|
|
|
|
#define GET_PROT_CLASS(fd) \
|
|
fcntl((fd), F_GETPROTECTIONCLASS)
|
|
|
|
#define SET_PROT_CLASS(fd, prot_class) \
|
|
fcntl((fd), F_SETPROTECTIONCLASS, (prot_class))
|
|
|
|
#define KEYSTORECTL_PATH "/usr/local/bin/keystorectl"
|
|
#define KEYBAGDTEST_PATH "/usr/local/bin/keybagdTest"
|
|
#define TEMP_DIR_TEMPLATE "/tmp/data_protection_test.XXXXXXXX"
|
|
#define TEST_PASSCODE "IAmASecurePassword"
|
|
|
|
int g_fd = -1;
|
|
int g_dir_fd = -1;
|
|
int g_subdir_fd = -1;
|
|
int g_passcode_set = 0;
|
|
|
|
char g_test_tempdir[PATH_MAX] = TEMP_DIR_TEMPLATE;
|
|
char g_filepath[PATH_MAX] = "";
|
|
char g_dirpath[PATH_MAX] = "";
|
|
char g_subdirpath[PATH_MAX] = "";
|
|
|
|
int apple_key_store(
|
|
uint32_t command,
|
|
uint64_t * inputs,
|
|
uint32_t input_count,
|
|
void * input_structs,
|
|
size_t input_struct_count,
|
|
uint64_t * outputs,
|
|
uint32_t * output_count
|
|
);
|
|
int spawn_proc(char * const command[]);
|
|
int supports_content_prot(void);
|
|
char* dp_class_num_to_string(int num);
|
|
int lock_device(void);
|
|
int unlock_device(char * passcode);
|
|
int set_passcode(char * new_passcode, char * old_passcode);
|
|
int clear_passcode(char * passcode);
|
|
int has_passcode(void);
|
|
void setup(void);
|
|
void cleanup(void);
|
|
|
|
T_DECL(data_protection,
|
|
"Verify behavior of the various data protection classes") {
|
|
int local_result = -1;
|
|
int new_prot_class = -1;
|
|
int old_prot_class = -1;
|
|
int current_byte = 0;
|
|
char rd_buffer[CPT_IO_SIZE];
|
|
char wr_buffer[CPT_IO_SIZE];
|
|
|
|
setup();
|
|
|
|
/*
|
|
* Ensure we can freely read and change
|
|
* protection classes when unlocked.
|
|
*/
|
|
for (
|
|
new_prot_class = PROTECTION_CLASS_A;
|
|
new_prot_class <= PROTECTION_CLASS_F;
|
|
new_prot_class++
|
|
) {
|
|
T_ASSERT_NE(
|
|
old_prot_class = GET_PROT_CLASS(g_fd),
|
|
-1,
|
|
"Get protection class when locked"
|
|
);
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
SET_PROT_CLASS(g_fd, new_prot_class),
|
|
-1,
|
|
"Should be able to change protection "
|
|
"from %s to %s while unlocked",
|
|
dp_class_num_to_string(old_prot_class),
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
}
|
|
|
|
/* Query the filesystem for the default CP level (Is it C?) */
|
|
#ifndef F_GETDEFAULTPROTLEVEL
|
|
#define F_GETDEFAULTPROTLEVEL 79
|
|
#endif
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
old_prot_class = fcntl(g_fd, F_GETDEFAULTPROTLEVEL),
|
|
-1,
|
|
"Get default protection level for filesystem"
|
|
);
|
|
|
|
/* XXX: Do we want to do anything with the level? What should it be? */
|
|
|
|
/*
|
|
* files are allowed to move into F, but not out of it. They can also
|
|
* only do so when they do not have content.
|
|
*/
|
|
close(g_fd);
|
|
unlink(g_filepath);
|
|
|
|
/* re-create the file */
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_GE(
|
|
g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC),
|
|
0,
|
|
"Recreate test file"
|
|
);
|
|
|
|
/* Try making a class A file while locked. */
|
|
T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_fd, PROTECTION_CLASS_A),
|
|
-1,
|
|
"Should not be able to change protection "
|
|
"from class D to class A when locked"
|
|
);
|
|
T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
|
|
|
|
/* Attempt opening/IO to a class A file while unlocked. */
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_fd, PROTECTION_CLASS_A),
|
|
0,
|
|
"Should be able to change protection "
|
|
"from class D to class A when unlocked"
|
|
);
|
|
|
|
close(g_fd);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_GE(
|
|
g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
|
|
0,
|
|
"Should be able to open a class A file when unlocked");
|
|
|
|
/*
|
|
* TODO: Write specific data we can check for. If we're going to do
|
|
* that, the write scheme should be deliberately ugly.
|
|
*/
|
|
current_byte = 0;
|
|
|
|
while (current_byte < CPT_IO_SIZE) {
|
|
local_result = pwrite(
|
|
g_fd,
|
|
&wr_buffer[current_byte],
|
|
CPT_IO_SIZE - current_byte,
|
|
current_byte
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
local_result,
|
|
-1,
|
|
"Should be able to write to "
|
|
"a class A file when unlocked"
|
|
);
|
|
|
|
current_byte += local_result;
|
|
}
|
|
|
|
current_byte = 0;
|
|
|
|
while (current_byte < CPT_IO_SIZE) {
|
|
local_result = pread(
|
|
g_fd,
|
|
&rd_buffer[current_byte],
|
|
CPT_IO_SIZE - current_byte,
|
|
current_byte
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
local_result,
|
|
-1,
|
|
"Should be able to read from "
|
|
"a class A file when unlocked"
|
|
);
|
|
|
|
current_byte += local_result;
|
|
}
|
|
|
|
/*
|
|
* Again, but now while locked; and try to change the file class
|
|
* as well.
|
|
*/
|
|
T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
|
|
|
|
T_ASSERT_LE(
|
|
pread(g_fd, rd_buffer, CPT_IO_SIZE, 0),
|
|
0,
|
|
"Should not be able to read from a class A file when locked"
|
|
);
|
|
|
|
T_ASSERT_LE(
|
|
pwrite(g_fd, wr_buffer, CPT_IO_SIZE, 0),
|
|
0,
|
|
"Should not be able to write to a class A file when locked"
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_fd, PROTECTION_CLASS_D),
|
|
-1,
|
|
"Should not be able to change protection "
|
|
"from class A to class D when locked"
|
|
);
|
|
|
|
/* Try to open and truncate the file. */
|
|
close(g_fd);
|
|
|
|
T_ASSERT_EQ(
|
|
g_fd = open(g_filepath, O_RDWR | O_TRUNC | O_CLOEXEC),
|
|
-1,
|
|
"Should not be able to open and truncate "
|
|
"a class A file when locked"
|
|
);
|
|
|
|
/* Try to open the file */
|
|
T_ASSERT_EQ(
|
|
g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
|
|
-1,
|
|
"Should not be able to open a class A file when locked"
|
|
);
|
|
|
|
/* What about class B files? */
|
|
T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
|
|
|
|
T_ASSERT_GE(
|
|
g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
|
|
0,
|
|
"Should be able to open a class A file when unlocked"
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_fd, PROTECTION_CLASS_D),
|
|
0,
|
|
"Should be able to change protection "
|
|
"class from A to D when unlocked"
|
|
);
|
|
|
|
T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
|
|
|
|
/* Can we create a class B file while locked? */
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_fd, PROTECTION_CLASS_B),
|
|
0,
|
|
"Should be able to change protection "
|
|
"class from D to B when locked"
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
GET_PROT_CLASS(g_fd),
|
|
PROTECTION_CLASS_B,
|
|
"File should now have class B protection"
|
|
);
|
|
|
|
/*
|
|
* We should also be able to read/write to the
|
|
* file descriptor while it is open.
|
|
*/
|
|
current_byte = 0;
|
|
|
|
while (current_byte < CPT_IO_SIZE) {
|
|
local_result = pwrite(
|
|
g_fd,
|
|
&wr_buffer[current_byte],
|
|
CPT_IO_SIZE - current_byte,
|
|
current_byte
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
local_result,
|
|
-1,
|
|
"Should be able to write to a "
|
|
"new class B file when locked"
|
|
);
|
|
|
|
current_byte += local_result;
|
|
}
|
|
|
|
current_byte = 0;
|
|
|
|
while (current_byte < CPT_IO_SIZE) {
|
|
local_result = pread(
|
|
g_fd,
|
|
&rd_buffer[current_byte],
|
|
CPT_IO_SIZE - current_byte,
|
|
current_byte
|
|
);
|
|
|
|
T_ASSERT_NE(
|
|
local_result,
|
|
-1,
|
|
"Should be able to read from a "
|
|
"new class B file when locked"
|
|
);
|
|
|
|
current_byte += local_result;
|
|
}
|
|
|
|
/* We should not be able to open a class B file under lock. */
|
|
close(g_fd);
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
g_fd = open(g_filepath, O_RDWR | O_CLOEXEC),
|
|
-1,
|
|
"Should not be able to open a class B file when locked"
|
|
);
|
|
|
|
unlink(g_filepath);
|
|
|
|
/* We still need to test directory semantics. */
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
mkdir(g_dirpath, 0x0777),
|
|
-1,
|
|
"Should be able to create a new directory when locked"
|
|
);
|
|
|
|
/* The newly created directory should not have a protection class. */
|
|
T_ASSERT_NE(
|
|
g_dir_fd = open(g_dirpath, O_RDONLY | O_CLOEXEC),
|
|
-1,
|
|
"Should be able to open an unclassed directory when locked"
|
|
);
|
|
|
|
T_ASSERT_TRUE(
|
|
GET_PROT_CLASS(g_dir_fd) == PROTECTION_CLASS_D ||
|
|
GET_PROT_CLASS(g_dir_fd) == PROTECTION_CLASS_DIR_NONE,
|
|
"Directory protection class sholud be D or NONE"
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_A),
|
|
0,
|
|
"Should be able to change a directory from "
|
|
"class D to class A while locked"
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_D),
|
|
0,
|
|
"Should be able to change a directory from "
|
|
"class A to class D while locked"
|
|
);
|
|
|
|
/*
|
|
* Do all files created in the directory properly inherit the
|
|
* directory's protection class?
|
|
*/
|
|
T_SETUPBEGIN;
|
|
T_ASSERT_LT(
|
|
strlcpy(g_filepath, g_dirpath, PATH_MAX),
|
|
PATH_MAX,
|
|
"Construct path for file in the directory"
|
|
);
|
|
T_ASSERT_LT(
|
|
strlcat(g_filepath, "test_file", PATH_MAX),
|
|
PATH_MAX,
|
|
"Construct path for file in the directory"
|
|
);
|
|
T_SETUPEND;
|
|
|
|
T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
|
|
|
|
for (
|
|
new_prot_class = PROTECTION_CLASS_A;
|
|
new_prot_class <= PROTECTION_CLASS_D;
|
|
new_prot_class++
|
|
) {
|
|
int getclass_dir;
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
old_prot_class = GET_PROT_CLASS(g_dir_fd),
|
|
-1,
|
|
"Get protection class for the directory"
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_dir_fd, new_prot_class),
|
|
0,
|
|
"Should be able to change directory "
|
|
"protection from %s to %s",
|
|
dp_class_num_to_string(old_prot_class),
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
T_EXPECT_EQ(
|
|
getclass_dir = GET_PROT_CLASS(g_dir_fd),
|
|
new_prot_class,
|
|
"Get protection class for the directory"
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_GE(
|
|
g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777),
|
|
0,
|
|
"Should be able to create file in "
|
|
"%s directory when unlocked",
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
local_result = GET_PROT_CLASS(g_fd),
|
|
-1,
|
|
"Get the new file's protection class"
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
local_result,
|
|
new_prot_class,
|
|
"File should have %s protection",
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
close(g_fd);
|
|
unlink(g_filepath);
|
|
}
|
|
|
|
/* Do we disallow creation of a class F directory? */
|
|
T_ASSERT_NE(
|
|
SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_F),
|
|
0,
|
|
"Should not be able to create class F directory"
|
|
);
|
|
|
|
/*
|
|
* Are class A and class B semantics followed for when
|
|
* we create these files during lock?
|
|
*/
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_A),
|
|
0,
|
|
"Should be able to change protection "
|
|
"from class F to class A when unlocked"
|
|
);
|
|
|
|
T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
|
|
|
|
T_ASSERT_EQ(
|
|
g_fd = open(g_filepath, O_CREAT | O_EXCL | O_CLOEXEC, 0777),
|
|
-1,
|
|
"Should not be able to create a new file "
|
|
"in a class A directory when locked"
|
|
);
|
|
|
|
T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_dir_fd, PROTECTION_CLASS_B),
|
|
0,
|
|
"Should be able to change directory "
|
|
"from class A to class B when unlocked"
|
|
);
|
|
|
|
T_ASSERT_EQ(lock_device(), 0, "*** Lock device ***");
|
|
|
|
T_ASSERT_GE(
|
|
g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777),
|
|
0,
|
|
"Should be able to create a new file "
|
|
"in class B directory when locked"
|
|
);
|
|
|
|
T_ASSERT_NE(
|
|
local_result = GET_PROT_CLASS(g_fd),
|
|
-1,
|
|
"Get the new file's protection class"
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
local_result,
|
|
PROTECTION_CLASS_B,
|
|
"File should inherit protection class of class B directory"
|
|
);
|
|
|
|
/* What happens when we try to create new subdirectories? */
|
|
T_ASSERT_EQ(unlock_device(TEST_PASSCODE), 0, "*** Unlock device ***");
|
|
|
|
for (
|
|
new_prot_class = PROTECTION_CLASS_A;
|
|
new_prot_class <= PROTECTION_CLASS_D;
|
|
new_prot_class++
|
|
) {
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_EQ(
|
|
SET_PROT_CLASS(g_dir_fd, new_prot_class),
|
|
0,
|
|
"Change directory to %s",
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
mkdir(g_subdirpath, 0x0777),
|
|
-1,
|
|
"Create subdirectory in %s directory",
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NE(
|
|
g_subdir_fd = open(g_subdirpath, O_RDONLY | O_CLOEXEC),
|
|
-1,
|
|
"Should be able to open subdirectory in %s directory",
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
T_ASSERT_NE(
|
|
local_result = GET_PROT_CLASS(g_subdir_fd),
|
|
-1,
|
|
"Get protection class of new subdirectory "
|
|
"of %s directory",
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
local_result,
|
|
new_prot_class,
|
|
"New subdirectory should have same class as %s parent",
|
|
dp_class_num_to_string(new_prot_class)
|
|
);
|
|
|
|
close(g_subdir_fd);
|
|
rmdir(g_subdirpath);
|
|
}
|
|
}
|
|
|
|
void
|
|
setup(void)
|
|
{
|
|
int ret = 0;
|
|
int local_result = -1;
|
|
|
|
T_SETUPBEGIN;
|
|
|
|
T_ATEND(cleanup);
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_NOTNULL(
|
|
mkdtemp(g_test_tempdir),
|
|
"Create temporary directory for test"
|
|
);
|
|
T_LOG("Test temp dir: %s", g_test_tempdir);
|
|
|
|
T_ASSERT_NE(
|
|
local_result = supports_content_prot(),
|
|
-1,
|
|
"Get content protection support status"
|
|
);
|
|
|
|
if (local_result == 0) {
|
|
T_SKIP("Data protection not supported on this system");
|
|
}
|
|
|
|
T_ASSERT_EQ(
|
|
has_passcode(),
|
|
0,
|
|
"Device should not have existing passcode"
|
|
);
|
|
|
|
T_ASSERT_EQ(
|
|
set_passcode(TEST_PASSCODE, NULL),
|
|
0,
|
|
"Set test passcode"
|
|
);
|
|
|
|
bzero(g_filepath, PATH_MAX);
|
|
bzero(g_dirpath, PATH_MAX);
|
|
bzero(g_subdirpath, PATH_MAX);
|
|
|
|
ret |= (strlcat(g_filepath, g_test_tempdir, PATH_MAX) == PATH_MAX);
|
|
ret |= (strlcat(g_filepath, "/", PATH_MAX) == PATH_MAX);
|
|
ret |= (strlcpy(g_dirpath, g_filepath, PATH_MAX) == PATH_MAX);
|
|
ret |= (strlcat(g_filepath, "test_file", PATH_MAX) == PATH_MAX);
|
|
ret |= (strlcat(g_dirpath, "test_dir/", PATH_MAX) == PATH_MAX);
|
|
ret |= (strlcpy(g_subdirpath, g_dirpath, PATH_MAX) == PATH_MAX);
|
|
ret |= (strlcat(g_subdirpath, "test_subdir/", PATH_MAX) == PATH_MAX);
|
|
|
|
T_QUIET;
|
|
T_ASSERT_EQ(ret, 0, "Initialize test path strings");
|
|
|
|
T_WITH_ERRNO;
|
|
T_ASSERT_GE(
|
|
g_fd = open(g_filepath, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0777),
|
|
0,
|
|
"Create test file"
|
|
);
|
|
|
|
T_SETUPEND;
|
|
}
|
|
|
|
void
|
|
cleanup(void)
|
|
{
|
|
T_LOG("Cleaning up…");
|
|
|
|
if (g_subdir_fd >= 0) {
|
|
T_LOG("Cleanup: closing fd %d", g_subdir_fd);
|
|
close(g_subdir_fd);
|
|
}
|
|
|
|
if (g_subdirpath[0]) {
|
|
T_LOG("Cleanup: removing %s", g_subdirpath);
|
|
rmdir(g_subdirpath);
|
|
}
|
|
|
|
if (g_fd >= 0) {
|
|
T_LOG("Cleanup: closing fd %d", g_fd);
|
|
close(g_fd);
|
|
}
|
|
|
|
if (g_filepath[0]) {
|
|
T_LOG("Cleanup: removing %s", g_filepath);
|
|
unlink(g_filepath);
|
|
}
|
|
|
|
if (g_dir_fd >= 0) {
|
|
T_LOG("Cleanup: closing fd %d", g_dir_fd);
|
|
close(g_dir_fd);
|
|
}
|
|
|
|
if (g_dirpath[0]) {
|
|
T_LOG("Cleanup: removing %s", g_dirpath);
|
|
rmdir(g_dirpath);
|
|
}
|
|
|
|
if (strcmp(g_test_tempdir, TEMP_DIR_TEMPLATE)) {
|
|
T_LOG("Cleanup: removing %s", g_test_tempdir);
|
|
rmdir(g_test_tempdir);
|
|
}
|
|
|
|
if (g_passcode_set) {
|
|
T_LOG("Cleanup: unlocking device");
|
|
if (unlock_device(TEST_PASSCODE)) {
|
|
T_LOG("Warning: failed to unlock device in cleanup");
|
|
}
|
|
|
|
T_LOG("Cleanup: clearing passcode");
|
|
if (clear_passcode(TEST_PASSCODE)) {
|
|
T_LOG("Warning: failed to clear passcode in cleanup");
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
set_passcode(char * new_passcode, char * old_passcode)
|
|
{
|
|
int result = -1;
|
|
|
|
#ifdef KEYBAG_ENTITLEMENTS
|
|
/* If we're entitled, we can set the passcode ourselves. */
|
|
uint64_t inputs[] = {device_keybag_handle};
|
|
uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
|
|
void * input_structs = NULL;
|
|
size_t input_struct_count = 0;
|
|
char buffer[CPT_AKS_BUF_SIZE];
|
|
char * buffer_ptr = buffer;
|
|
uint32_t old_passcode_len = 0;
|
|
uint32_t new_passcode_len = 0;
|
|
|
|
T_LOG("%s(): using keybag entitlements", __func__);
|
|
|
|
old_passcode_len = strnlen(old_passcode, CPT_MAX_PASS_LEN);
|
|
new_passcode_len = strnlen(new_passcode, CPT_MAX_PASS_LEN);
|
|
|
|
if ((old_passcode == NULL) || (old_passcode_len == CPT_MAX_PASS_LEN)) {
|
|
old_passcode = "";
|
|
old_passcode_len = 0;
|
|
}
|
|
if ((new_passcode == NULL) || (new_passcode_len == CPT_MAX_PASS_LEN)) {
|
|
new_passcode = "";
|
|
new_passcode_len = 0;
|
|
}
|
|
|
|
*((uint32_t *) buffer_ptr) = ((uint32_t) 2);
|
|
buffer_ptr += sizeof(uint32_t);
|
|
|
|
*((uint32_t *) buffer_ptr) = old_passcode_len;
|
|
buffer_ptr += sizeof(uint32_t);
|
|
|
|
memcpy(buffer_ptr, old_passcode, old_passcode_len);
|
|
buffer_ptr += ((old_passcode_len + sizeof(uint32_t) - 1) &
|
|
~(sizeof(uint32_t) - 1));
|
|
|
|
*((uint32_t *) buffer_ptr) = new_passcode_len;
|
|
buffer_ptr += sizeof(uint32_t);
|
|
|
|
memcpy(buffer_ptr, new_passcode, new_passcode_len);
|
|
buffer_ptr += ((new_passcode_len + sizeof(uint32_t) - 1) &
|
|
~(sizeof(uint32_t) - 1));
|
|
|
|
input_structs = buffer;
|
|
input_struct_count = (buffer_ptr - buffer);
|
|
|
|
result = apple_key_store(
|
|
kAppleKeyStoreKeyBagSetPasscode,
|
|
inputs,
|
|
input_count,
|
|
input_structs,
|
|
input_struct_count,
|
|
NULL,
|
|
NULL
|
|
);
|
|
#else
|
|
/*
|
|
* If we aren't entitled, we'll need to use
|
|
* keystorectl to set the passcode.
|
|
*/
|
|
T_LOG("%s(): using keystorectl", __func__);
|
|
|
|
if (
|
|
(old_passcode == NULL) ||
|
|
(strnlen(old_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
|
|
) {
|
|
old_passcode = "";
|
|
}
|
|
|
|
if (
|
|
(new_passcode == NULL) ||
|
|
(strnlen(new_passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
|
|
) {
|
|
new_passcode = "";
|
|
}
|
|
|
|
char * const keystorectl_args[] = {
|
|
KEYBAGDTEST_PATH,
|
|
"syspass",
|
|
old_passcode,
|
|
new_passcode,
|
|
NULL
|
|
};
|
|
result = spawn_proc(keystorectl_args);
|
|
#endif /* KEYBAG_ENTITLEMENTS */
|
|
if (result == 0 && new_passcode != NULL) {
|
|
g_passcode_set = 1;
|
|
} else if (result == 0 && new_passcode == NULL) {
|
|
g_passcode_set = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
clear_passcode(char * passcode)
|
|
{
|
|
/*
|
|
* For the moment, this will set the passcode to the empty string
|
|
* (a known value); this will most likely need to change, or running
|
|
* this test may ruin everything™
|
|
*/
|
|
return set_passcode(NULL, passcode);
|
|
}
|
|
|
|
int
|
|
has_passcode(void)
|
|
{
|
|
return set_passcode(NULL, NULL);
|
|
}
|
|
|
|
int
|
|
lock_device(void)
|
|
{
|
|
int result = -1;
|
|
|
|
/*
|
|
* Pass in the path to keybagdTest instead. By doing this, we bypass
|
|
* the shortcut to get in to the keybag via IOKit and instead use the
|
|
* pre-existing command line tool.
|
|
*
|
|
* This also goes through the normal "lock → locking (10s) → locked"
|
|
* flow that would normally occuring during system runtime when the
|
|
* lock button is depressed. To ensure that our single threaded test
|
|
* works properly in this case, poll until we can't create a class A
|
|
* file to be safe.
|
|
*/
|
|
char * const kbd_args[] = {KEYBAGDTEST_PATH, "lock", NULL};
|
|
result = spawn_proc(kbd_args);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Delete the file if it is present. Note that this may fail if the
|
|
* file is actually not there. So don't bomb out if we can't delete
|
|
* this file right now.
|
|
*/
|
|
(void) unlink("/private/var/foo_test_file");
|
|
|
|
while (1) {
|
|
int dp_fd;
|
|
|
|
dp_fd = open_dprotected_np(
|
|
"/private/var/foo_test_file",
|
|
O_RDWR | O_CREAT,
|
|
PROTECTION_CLASS_A,
|
|
0
|
|
);
|
|
|
|
if (dp_fd >= 0) {
|
|
/* delete it and sleep */
|
|
close(dp_fd);
|
|
result = unlink("/private/var/foo_test_file");
|
|
|
|
if (result) {
|
|
return result;
|
|
}
|
|
|
|
sync();
|
|
sleep(1);
|
|
} else {
|
|
/* drop out of our polling loop. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Note that our loop breakout condition is whether or not we can
|
|
* create a class A file, so that loop may execute up to 10 times
|
|
* (due to the 10s grace period). By the time we get here, we assume
|
|
* that we didn't hit any of the error cases above.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
unlock_device(char * passcode)
|
|
{
|
|
int result = -1;
|
|
|
|
#ifdef KEYBAG_ENTITLEMENTS
|
|
/* If we're entitled, we can unlock the device ourselves. */
|
|
uint64_t inputs[] = {device_keybag_handle};
|
|
uint32_t input_count = (sizeof(inputs) / sizeof(*inputs));
|
|
size_t input_struct_count = 0;
|
|
|
|
T_LOG("%s(): using keybag entitlements", __func__);
|
|
|
|
input_struct_count = strnlen(passcode, CPT_MAX_PASS_LEN);
|
|
if ((passcode == NULL) || (input_struct_count == CPT_MAX_PASS_LEN)) {
|
|
passcode = "";
|
|
input_struct_count = 0;
|
|
}
|
|
|
|
result = apple_key_store(
|
|
kAppleKeyStoreKeyBagUnlock,
|
|
inputs,
|
|
input_count,
|
|
passcode,
|
|
input_struct_count,
|
|
NULL,
|
|
NULL
|
|
);
|
|
#else
|
|
/*
|
|
* If we aren't entitled, we'll need to use
|
|
* keystorectl to unlock the device.
|
|
*/
|
|
T_LOG("%s(): using keystorectl", __func__);
|
|
|
|
if (
|
|
(passcode == NULL) ||
|
|
(strnlen(passcode, CPT_MAX_PASS_LEN) == CPT_MAX_PASS_LEN)
|
|
) {
|
|
passcode = "";
|
|
}
|
|
|
|
char * const keystorectl_args[] = {
|
|
KEYSTORECTL_PATH, "unlock", passcode, NULL
|
|
};
|
|
|
|
result = spawn_proc(keystorectl_args);
|
|
#endif /* KEYBAG_ENTITLEMENTS */
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Code based on Mobile Key Bag; specifically
|
|
* MKBDeviceSupportsContentProtection and
|
|
* MKBDeviceFormattedForContentProtection.
|
|
*
|
|
* We want to verify that we support content protection, and that
|
|
* we are formatted for it.
|
|
*/
|
|
int
|
|
supports_content_prot(void)
|
|
{
|
|
int local_result = -1;
|
|
int result = -1;
|
|
uint32_t buffer_size = 1;
|
|
char buffer[buffer_size];
|
|
io_registry_entry_t defaults = IO_OBJECT_NULL;
|
|
kern_return_t k_result = KERN_FAILURE;
|
|
struct statfs statfs_results;
|
|
|
|
defaults = IORegistryEntryFromPath(
|
|
kIOMasterPortDefault,
|
|
kIODeviceTreePlane ":/defaults"
|
|
);
|
|
|
|
if (defaults == IO_OBJECT_NULL) {
|
|
/* Assume data protection is unsupported */
|
|
T_LOG(
|
|
"%s(): no defaults entry in IORegistry",
|
|
__func__
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
k_result = IORegistryEntryGetProperty(
|
|
defaults,
|
|
"content-protect",
|
|
buffer,
|
|
&buffer_size
|
|
);
|
|
|
|
if (k_result != KERN_SUCCESS) {
|
|
/* Assume data protection is unsupported */
|
|
T_LOG(
|
|
"%s(): no content-protect property in IORegistry",
|
|
__func__
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* At this point, we SUPPORT content protection… but are we
|
|
* formatted for it? This is ugly; we should be testing the file
|
|
* system we'll be testing in, not just /tmp/.
|
|
*/
|
|
local_result = statfs(g_test_tempdir, &statfs_results);
|
|
|
|
if (local_result == -1) {
|
|
T_LOG(
|
|
"%s(): failed to statfs the test directory, errno = %s",
|
|
__func__, strerror(errno)
|
|
);
|
|
return -1;
|
|
} else if (statfs_results.f_flags & MNT_CPROTECT) {
|
|
return 1;
|
|
} else {
|
|
T_LOG(
|
|
"%s(): filesystem not formatted for data protection",
|
|
__func__
|
|
);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Shamelessly ripped from keystorectl routines;
|
|
* a wrapper for invoking the AKS user client.
|
|
*/
|
|
int
|
|
apple_key_store(uint32_t command,
|
|
uint64_t * inputs,
|
|
uint32_t input_count,
|
|
void * input_structs,
|
|
size_t input_struct_count,
|
|
uint64_t * outputs,
|
|
uint32_t * output_count)
|
|
{
|
|
int result = -1;
|
|
io_connect_t connection = IO_OBJECT_NULL;
|
|
io_registry_entry_t apple_key_bag_service = IO_OBJECT_NULL;
|
|
kern_return_t k_result = KERN_FAILURE;
|
|
IOReturn io_result = IO_OBJECT_NULL;
|
|
|
|
apple_key_bag_service = IOServiceGetMatchingService(
|
|
kIOMasterPortDefault,
|
|
IOServiceMatching(kAppleKeyStoreServiceName)
|
|
);
|
|
if (apple_key_bag_service == IO_OBJECT_NULL) {
|
|
T_LOG(
|
|
"%s: failed to match kAppleKeyStoreServiceName",
|
|
__func__
|
|
);
|
|
goto end;
|
|
}
|
|
|
|
k_result = IOServiceOpen(
|
|
apple_key_bag_service,
|
|
mach_task_self(),
|
|
0,
|
|
&connection
|
|
);
|
|
if (k_result != KERN_SUCCESS) {
|
|
T_LOG(
|
|
"%s: failed to open AppleKeyStore: "
|
|
"IOServiceOpen() returned %d",
|
|
__func__, k_result
|
|
);
|
|
goto end;
|
|
}
|
|
|
|
k_result = IOConnectCallMethod(
|
|
connection,
|
|
kAppleKeyStoreUserClientOpen,
|
|
NULL, 0, NULL, 0, NULL, NULL, NULL, NULL
|
|
);
|
|
if (k_result != KERN_SUCCESS) {
|
|
T_LOG(
|
|
"%s: call to AppleKeyStore method "
|
|
"kAppleKeyStoreUserClientOpen failed",
|
|
__func__
|
|
);
|
|
goto close;
|
|
}
|
|
|
|
io_result = IOConnectCallMethod(
|
|
connection, command, inputs, input_count, input_structs,
|
|
input_struct_count, outputs, output_count, NULL, NULL
|
|
);
|
|
if (io_result != kIOReturnSuccess) {
|
|
T_LOG("%s: call to AppleKeyStore method %d failed", __func__, command);
|
|
goto close;
|
|
}
|
|
|
|
result = 0;
|
|
|
|
close:
|
|
IOServiceClose(apple_key_bag_service);
|
|
end:
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Helper function for launching tools
|
|
*/
|
|
int
|
|
spawn_proc(char * const command[])
|
|
{
|
|
pid_t pid = 0;
|
|
int launch_tool_ret = 0;
|
|
bool waitpid_ret = true;
|
|
int status = 0;
|
|
int signal = 0;
|
|
int timeout = 30;
|
|
|
|
launch_tool_ret = dt_launch_tool(&pid, command, false, NULL, NULL);
|
|
T_EXPECT_EQ(launch_tool_ret, 0, "launch tool: %s", command[0]);
|
|
if (launch_tool_ret != 0) {
|
|
return 1;
|
|
}
|
|
|
|
waitpid_ret = dt_waitpid(pid, &status, &signal, timeout);
|
|
T_EXPECT_TRUE(waitpid_ret, "%s should succeed", command[0]);
|
|
if (waitpid_ret == false) {
|
|
if (status != 0) {
|
|
T_LOG("%s exited %d", command[0], status);
|
|
}
|
|
if (signal != 0) {
|
|
T_LOG("%s received signal %d", command[0], signal);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char*
|
|
dp_class_num_to_string(int num)
|
|
{
|
|
switch (num) {
|
|
case 0:
|
|
return "unclassed";
|
|
case PROTECTION_CLASS_A:
|
|
return "class A";
|
|
case PROTECTION_CLASS_B:
|
|
return "class B";
|
|
case PROTECTION_CLASS_C:
|
|
return "class C";
|
|
case PROTECTION_CLASS_D:
|
|
return "class D";
|
|
case PROTECTION_CLASS_E:
|
|
return "class E";
|
|
case PROTECTION_CLASS_F:
|
|
return "class F";
|
|
default:
|
|
return "<unknown class>";
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
int
|
|
device_lock_state(void)
|
|
{
|
|
/*
|
|
* TODO: Actually implement this.
|
|
*
|
|
* We fail if a passcode already exists, and the methods being used
|
|
* to lock/unlock the device in this test appear to be synchronous…
|
|
* do we need this function?
|
|
*/
|
|
int result = -1;
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Determines if we will try to test class C semanatics. */
|
|
int
|
|
unlocked_since_boot()
|
|
{
|
|
/*
|
|
* TODO: Actually implement this.
|
|
*
|
|
* The actual semantics for CP mean that even with this primative,
|
|
* we would need to set a passcode and then reboot the device in
|
|
* order to test this; this function will probably be rather
|
|
* worthless as a result.
|
|
*/
|
|
int result = 1;
|
|
|
|
return result;
|
|
}
|
|
#endif
|