#include <sys/cprotect.h>
#include <sys/malloc.h>
#include <sys/mount_internal.h>
#include <sys/filio.h>
#include <sys/content_protection.h>
#include <libkern/crypto/sha1.h>
#include <libkern/libkern.h>
#define PTR_ADD(type, base, offset) (type)((uintptr_t)(base) + (offset))
typedef uint32_t cpx_flags_t;
enum {
CPX_SEP_WRAPPEDKEY = 0x01,
CPX_IV_AES_CTX_INITIALIZED = 0x02,
CPX_USE_OFFSET_FOR_IV = 0x04,
CPX_IV_AES_CTX_VFS = 0x08,
CPX_SYNTHETIC_OFFSET_FOR_IV = 0x10,
};
struct cpx {
#if DEBUG
uint32_t cpx_magic1;
#endif
cpx_flags_t cpx_flags;
uint16_t cpx_max_key_len;
uint16_t cpx_key_len;
aes_encrypt_ctx cpx_iv_aes_ctx; uint8_t cpx_cached_key[];
} __attribute__((packed));
size_t cpx_size(size_t key_size)
{
size_t size = sizeof(struct cpx) + key_size;
#if DEBUG
size += 4; #endif
return size;
}
size_t cpx_sizex(const struct cpx *cpx)
{
return cpx_size(cpx->cpx_max_key_len);
}
cpx_t cpx_alloc(size_t key_len)
{
cpx_t cpx;
MALLOC(cpx, cpx_t, cpx_size(key_len), M_TEMP, M_WAITOK);
cpx_init(cpx, key_len);
return cpx;
}
#if DEBUG
static const uint32_t cpx_magic1 = 0x7b787063; static const uint32_t cpx_magic2 = 0x7870637d; #endif
void cpx_free(cpx_t cpx)
{
#if DEBUG
assert(cpx->cpx_magic1 == cpx_magic1);
assert(*PTR_ADD(uint32_t *, cpx, cpx_sizex(cpx) - 4) == cpx_magic2);
#endif
bzero(cpx->cpx_cached_key, cpx->cpx_max_key_len);
FREE(cpx, M_TEMP);
}
void cpx_init(cpx_t cpx, size_t key_len)
{
#if DEBUG
cpx->cpx_magic1 = cpx_magic1;
*PTR_ADD(uint32_t *, cpx, cpx_size(key_len) - 4) = cpx_magic2;
#endif
cpx->cpx_flags = 0;
cpx->cpx_key_len = 0;
cpx->cpx_max_key_len = key_len;
}
bool cpx_is_sep_wrapped_key(const struct cpx *cpx)
{
return ISSET(cpx->cpx_flags, CPX_SEP_WRAPPEDKEY);
}
void cpx_set_is_sep_wrapped_key(struct cpx *cpx, bool v)
{
if (v)
SET(cpx->cpx_flags, CPX_SEP_WRAPPEDKEY);
else
CLR(cpx->cpx_flags, CPX_SEP_WRAPPEDKEY);
}
bool cpx_use_offset_for_iv(const struct cpx *cpx)
{
return ISSET(cpx->cpx_flags, CPX_USE_OFFSET_FOR_IV);
}
void cpx_set_use_offset_for_iv(struct cpx *cpx, bool v)
{
if (v)
SET(cpx->cpx_flags, CPX_USE_OFFSET_FOR_IV);
else
CLR(cpx->cpx_flags, CPX_USE_OFFSET_FOR_IV);
}
bool cpx_synthetic_offset_for_iv(const struct cpx *cpx)
{
return ISSET(cpx->cpx_flags, CPX_SYNTHETIC_OFFSET_FOR_IV);
}
void cpx_set_synthetic_offset_for_iv(struct cpx *cpx, bool v)
{
if (v)
SET(cpx->cpx_flags, CPX_SYNTHETIC_OFFSET_FOR_IV);
else
CLR(cpx->cpx_flags, CPX_SYNTHETIC_OFFSET_FOR_IV);
}
uint16_t cpx_max_key_len(const struct cpx *cpx)
{
return cpx->cpx_max_key_len;
}
uint16_t cpx_key_len(const struct cpx *cpx)
{
return cpx->cpx_key_len;
}
void cpx_set_key_len(struct cpx *cpx, uint16_t key_len)
{
cpx->cpx_key_len = key_len;
if (ISSET(cpx->cpx_flags, CPX_IV_AES_CTX_VFS)) {
CLR(cpx->cpx_flags, CPX_IV_AES_CTX_INITIALIZED | CPX_IV_AES_CTX_VFS);
}
}
bool cpx_has_key(const struct cpx *cpx)
{
return cpx->cpx_key_len > 0;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-qual"
void *cpx_key(const struct cpx *cpx)
{
return (void *)cpx->cpx_cached_key;
}
#pragma clang diagnostic pop
void cpx_set_aes_iv_key(struct cpx *cpx, void *iv_key)
{
aes_encrypt_key128(iv_key, &cpx->cpx_iv_aes_ctx);
SET(cpx->cpx_flags, CPX_IV_AES_CTX_INITIALIZED | CPX_USE_OFFSET_FOR_IV);
CLR(cpx->cpx_flags, CPX_IV_AES_CTX_VFS);
}
aes_encrypt_ctx *cpx_iv_aes_ctx(struct cpx *cpx)
{
if (ISSET(cpx->cpx_flags, CPX_IV_AES_CTX_INITIALIZED))
return &cpx->cpx_iv_aes_ctx;
SHA1_CTX sha1ctxt;
uint8_t digest[SHA_DIGEST_LENGTH];
SHA1Init(&sha1ctxt);
SHA1Update(&sha1ctxt, cpx->cpx_cached_key, cpx->cpx_key_len);
SHA1Final(digest, &sha1ctxt);
cpx_set_aes_iv_key(cpx, digest);
SET(cpx->cpx_flags, CPX_IV_AES_CTX_VFS);
return &cpx->cpx_iv_aes_ctx;
}
void cpx_flush(cpx_t cpx)
{
bzero(cpx->cpx_cached_key, cpx->cpx_max_key_len);
bzero(&cpx->cpx_iv_aes_ctx, sizeof(cpx->cpx_iv_aes_ctx));
cpx->cpx_flags = 0;
cpx->cpx_key_len = 0;
}
bool cpx_can_copy(const struct cpx *src, const struct cpx *dst)
{
return src->cpx_key_len <= dst->cpx_max_key_len;
}
void cpx_copy(const struct cpx *src, cpx_t dst)
{
uint16_t key_len = cpx_key_len(src);
cpx_set_key_len(dst, key_len);
memcpy(cpx_key(dst), cpx_key(src), key_len);
dst->cpx_flags = src->cpx_flags;
if (ISSET(dst->cpx_flags, CPX_IV_AES_CTX_INITIALIZED))
dst->cpx_iv_aes_ctx = src->cpx_iv_aes_ctx;
}
static struct cp_wrap_func g_cp_wrap_func = {};
static int
cp_lock_vfs_callback(mount_t mp, void *arg)
{
VFS_IOCTL(mp, FIODEVICELOCKED, arg, 0, vfs_context_kernel());
return 0;
}
int
cp_key_store_action(cp_key_store_action_t action)
{
switch (action) {
case CP_ACTION_LOCKED:
case CP_ACTION_UNLOCKED:;
cp_lock_state_t state = (action == CP_ACTION_LOCKED
? CP_LOCKED_STATE : CP_UNLOCKED_STATE);
return vfs_iterate(0, cp_lock_vfs_callback, (void *)(uintptr_t)state);
default:
return -1;
}
}
int
cp_register_wraps(cp_wrap_func_t key_store_func)
{
g_cp_wrap_func.new_key = key_store_func->new_key;
g_cp_wrap_func.unwrapper = key_store_func->unwrapper;
g_cp_wrap_func.rewrapper = key_store_func->rewrapper;
g_cp_wrap_func.invalidater = key_store_func->invalidater;
g_cp_wrap_func.backup_key = key_store_func->backup_key;
return 0;
}
int cp_rewrap_key(cp_cred_t access, uint32_t dp_class,
const cp_wrapped_key_t wrapped_key_in,
cp_wrapped_key_t wrapped_key_out)
{
if (!g_cp_wrap_func.rewrapper)
return ENXIO;
return g_cp_wrap_func.rewrapper(access, dp_class, wrapped_key_in,
wrapped_key_out);
}
int cp_new_key(cp_cred_t access, uint32_t dp_class, cp_raw_key_t key_out,
cp_wrapped_key_t wrapped_key_out)
{
if (!g_cp_wrap_func.new_key)
return ENXIO;
return g_cp_wrap_func.new_key(access, dp_class, key_out, wrapped_key_out);
}
int cp_unwrap_key(cp_cred_t access, const cp_wrapped_key_t wrapped_key_in,
cp_raw_key_t key_out)
{
if (!g_cp_wrap_func.unwrapper)
return ENXIO;
return g_cp_wrap_func.unwrapper(access, wrapped_key_in, key_out);
}
int cp_get_backup_key(cp_cred_t access, const cp_wrapped_key_t wrapped_key_in,
cp_wrapped_key_t wrapped_key_out)
{
if (!g_cp_wrap_func.backup_key)
return ENXIO;
return g_cp_wrap_func.backup_key(access, wrapped_key_in, wrapped_key_out);
}
int
cp_is_valid_class(int isdir, int32_t protectionclass)
{
if (isdir) {
return ((protectionclass >= PROTECTION_CLASS_DIR_NONE) &&
(protectionclass <= PROTECTION_CLASS_D));
}
else {
return ((protectionclass >= PROTECTION_CLASS_A) &&
(protectionclass <= PROTECTION_CLASS_F));
}
}
static cp_key_os_version_t
parse_os_version(const char *vers)
{
const char *p = vers;
int a = 0;
while (*p >= '0' && *p <= '9') {
a = a * 10 + *p - '0';
++p;
}
if (!a)
return 0;
int b = *p++;
if (!b)
return 0;
int c = 0;
while (*p >= '0' && *p <= '9') {
c = c * 10 + *p - '0';
++p;
}
if (!c)
return 0;
return (a & 0xff) << 24 | b << 16 | (c & 0xffff);
}
cp_key_os_version_t
cp_os_version(void)
{
static cp_key_os_version_t cp_os_version;
if (cp_os_version)
return cp_os_version;
if (!osversion[0])
return 0;
cp_os_version = parse_os_version(osversion);
if (!cp_os_version) {
printf("cp_os_version: unable to parse osversion `%s'\n", osversion);
cp_os_version = 1;
}
return cp_os_version;
}