#include <linux/types.h>
#include <stdarg.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
"WARNING: this program is only meant for testing, not for \"real\" use!\n"
"\n"
"Options:\n"
+" --block-number=BNUM Starting block number for IV generation.\n"
+" Default: 0\n"
" --block-size=BLOCK_SIZE Encrypt each BLOCK_SIZE bytes independently.\n"
" Default: 4096 bytes\n"
" --decrypt Decrypt instead of encrypt\n"
" --file-nonce=NONCE File's nonce as a 32-character hex string\n"
+" --fs-uuid=UUID The filesystem UUID as a 32-character hex string.\n"
+" Required for --iv-ino-lblk-32 and\n"
+" --iv-ino-lblk-64; otherwise is unused.\n"
" --help Show this help\n"
+" --inode-number=INUM The file's inode number. Required for\n"
+" --iv-ino-lblk-32 and --iv-ino-lblk-64;\n"
+" otherwise is unused.\n"
+" --iv-ino-lblk-32 Similar to --iv-ino-lblk-64, but selects the\n"
+" 32-bit variant.\n"
+" --iv-ino-lblk-64 Use the format where the IVs include the inode\n"
+" number and the same key is shared across files.\n"
+" Requires --kdf=HKDF-SHA512, --fs-uuid,\n"
+" --inode-number, and --mode-num.\n"
" --kdf=KDF Key derivation function to use: AES-128-ECB,\n"
" HKDF-SHA512, or none. Default: none\n"
" --mode-num=NUM Derive per-mode key using mode number NUM\n"
return (v >> n) | (v << (32 - n));
}
+static inline u64 rol64(u64 v, int n)
+{
+ return (v << n) | (v >> (64 - n));
+}
+
static inline u64 ror64(u64 v, int n)
{
return (v >> n) | (v << (64 - n));
}
#endif /* ENABLE_ALG_TESTS */
+/*----------------------------------------------------------------------------*
+ * SipHash-2-4 *
+ *----------------------------------------------------------------------------*/
+
+/*
+ * Reference: "SipHash: a fast short-input PRF"
+ * https://cr.yp.to/siphash/siphash-20120918.pdf
+ */
+
+#define SIPROUND \
+ do { \
+ v0 += v1; v2 += v3; \
+ v1 = rol64(v1, 13); v3 = rol64(v3, 16); \
+ v1 ^= v0; v3 ^= v2; \
+ v0 = rol64(v0, 32); \
+ v2 += v1; v0 += v3; \
+ v1 = rol64(v1, 17); v3 = rol64(v3, 21); \
+ v1 ^= v2; v3 ^= v0; \
+ v2 = rol64(v2, 32); \
+ } while (0)
+
+/* Compute the SipHash-2-4 of a 64-bit number when formatted as little endian */
+static u64 siphash_1u64(const u64 key[2], u64 data)
+{
+ u64 v0 = key[0] ^ 0x736f6d6570736575ULL;
+ u64 v1 = key[1] ^ 0x646f72616e646f6dULL;
+ u64 v2 = key[0] ^ 0x6c7967656e657261ULL;
+ u64 v3 = key[1] ^ 0x7465646279746573ULL;
+ u64 m[2] = {data, (u64)sizeof(data) << 56};
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(m); i++) {
+ v3 ^= m[i];
+ SIPROUND;
+ SIPROUND;
+ v0 ^= m[i];
+ }
+
+ v2 ^= 0xff;
+ for (i = 0; i < 4; i++)
+ SIPROUND;
+ return v0 ^ v1 ^ v2 ^ v3;
+}
+
/*----------------------------------------------------------------------------*
* Main program *
*----------------------------------------------------------------------------*/
#define FILE_NONCE_SIZE 16
+#define UUID_SIZE 16
#define MAX_KEY_SIZE 64
+#define MAX_IV_SIZE ADIANTUM_IV_SIZE
static const struct fscrypt_cipher {
const char *name;
return NULL;
}
-struct fscrypt_iv {
- union {
- __le64 block_num;
- u8 bytes[32];
+union fscrypt_iv {
+ /* usual IV format */
+ struct {
+ /* logical block number within the file */
+ __le64 block_number;
+
+ /* per-file nonce; only set in DIRECT_KEY mode */
+ u8 nonce[FILE_NONCE_SIZE];
+ };
+ /* IV format for IV_INO_LBLK_* modes */
+ struct {
+ /*
+ * IV_INO_LBLK_64: logical block number within the file
+ * IV_INO_LBLK_32: hashed inode number + logical block number
+ * within the file, mod 2^32
+ */
+ __le32 block_number32;
+
+ /* IV_INO_LBLK_64: inode number */
+ __le32 inode_number;
};
+ /* Any extra bytes up to the algorithm's IV size must be zeroed */
+ u8 bytes[MAX_IV_SIZE];
};
static void crypt_loop(const struct fscrypt_cipher *cipher, const u8 *key,
- struct fscrypt_iv *iv, bool decrypting,
- size_t block_size, size_t padding)
+ union fscrypt_iv *iv, bool decrypting,
+ size_t block_size, size_t padding, bool is_bnum_32bit)
{
u8 *buf = xmalloc(block_size);
size_t res;
full_write(STDOUT_FILENO, buf, crypt_len);
- iv->block_num = cpu_to_le64(le64_to_cpu(iv->block_num) + 1);
+ if (is_bnum_32bit)
+ iv->block_number32 = cpu_to_le32(
+ le32_to_cpu(iv->block_number32) + 1);
+ else
+ iv->block_number = cpu_to_le64(
+ le64_to_cpu(iv->block_number) + 1);
}
free(buf);
}
u8 mode_num;
u8 file_nonce[FILE_NONCE_SIZE];
bool file_nonce_specified;
+ bool iv_ino_lblk_64;
+ bool iv_ino_lblk_32;
+ u64 block_number;
+ u64 inode_number;
+ u8 fs_uuid[UUID_SIZE];
+ bool fs_uuid_specified;
};
#define HKDF_CONTEXT_KEY_IDENTIFIER 1
-#define HKDF_CONTEXT_PER_FILE_KEY 2
-#define HKDF_CONTEXT_PER_MODE_KEY 3
+#define HKDF_CONTEXT_PER_FILE_ENC_KEY 2
+#define HKDF_CONTEXT_DIRECT_KEY 3
+#define HKDF_CONTEXT_IV_INO_LBLK_64_KEY 4
+#define HKDF_CONTEXT_DIRHASH_KEY 5
+#define HKDF_CONTEXT_IV_INO_LBLK_32_KEY 6
+#define HKDF_CONTEXT_INODE_HASH_KEY 7
+
+/* Hash the file's inode number using SipHash keyed by a derived key */
+static u32 hash_inode_number(const struct key_and_iv_params *params)
+{
+ u8 info[9] = "fscrypt";
+ union {
+ u64 words[2];
+ u8 bytes[16];
+ } hash_key;
+
+ info[8] = HKDF_CONTEXT_INODE_HASH_KEY;
+ hkdf_sha512(params->master_key, params->master_key_size,
+ NULL, 0, info, sizeof(info),
+ hash_key.bytes, sizeof(hash_key));
+
+ hash_key.words[0] = get_unaligned_le64(&hash_key.bytes[0]);
+ hash_key.words[1] = get_unaligned_le64(&hash_key.bytes[8]);
+
+ return (u32)siphash_1u64(hash_key.words, params->inode_number);
+}
/*
* Get the key and starting IV with which the encryption will actually be done.
*/
static void get_key_and_iv(const struct key_and_iv_params *params,
u8 *real_key, size_t real_key_size,
- struct fscrypt_iv *iv)
+ union fscrypt_iv *iv)
{
bool file_nonce_in_iv = false;
struct aes_key aes_key;
- u8 info[8 + 1 + FILE_NONCE_SIZE] = "fscrypt";
+ u8 info[8 + 1 + 1 + UUID_SIZE] = "fscrypt";
size_t infolen = 8;
size_t i;
memset(iv, 0, sizeof(*iv));
+ /* Overridden later for iv_ino_lblk_{64,32} */
+ iv->block_number = cpu_to_le64(params->block_number);
+
+ if (params->iv_ino_lblk_64 || params->iv_ino_lblk_32) {
+ const char *opt = params->iv_ino_lblk_64 ? "--iv-ino-lblk-64" :
+ "--iv-ino-lblk-32";
+ if (params->iv_ino_lblk_64 && params->iv_ino_lblk_32)
+ die("--iv-ino-lblk-64 and --iv-ino-lblk-32 are mutually exclusive");
+ if (params->kdf != KDF_HKDF_SHA512)
+ die("%s requires --kdf=HKDF-SHA512", opt);
+ if (!params->fs_uuid_specified)
+ die("%s requires --fs-uuid", opt);
+ if (params->inode_number == 0)
+ die("%s requires --inode-number", opt);
+ if (params->mode_num == 0)
+ die("%s requires --mode-num", opt);
+ if (params->block_number > UINT32_MAX)
+ die("%s can't use --block-number > UINT32_MAX", opt);
+ if (params->inode_number > UINT32_MAX)
+ die("%s can't use --inode-number > UINT32_MAX", opt);
+ }
+
switch (params->kdf) {
case KDF_NONE:
if (params->mode_num != 0)
&real_key[i]);
break;
case KDF_HKDF_SHA512:
- if (params->mode_num != 0) {
- info[infolen++] = HKDF_CONTEXT_PER_MODE_KEY;
+ if (params->iv_ino_lblk_64) {
+ info[infolen++] = HKDF_CONTEXT_IV_INO_LBLK_64_KEY;
+ info[infolen++] = params->mode_num;
+ memcpy(&info[infolen], params->fs_uuid, UUID_SIZE);
+ infolen += UUID_SIZE;
+ iv->block_number32 = cpu_to_le32(params->block_number);
+ iv->inode_number = cpu_to_le32(params->inode_number);
+ } else if (params->iv_ino_lblk_32) {
+ info[infolen++] = HKDF_CONTEXT_IV_INO_LBLK_32_KEY;
+ info[infolen++] = params->mode_num;
+ memcpy(&info[infolen], params->fs_uuid, UUID_SIZE);
+ infolen += UUID_SIZE;
+ iv->block_number32 =
+ cpu_to_le32(hash_inode_number(params) +
+ params->block_number);
+ iv->inode_number = 0;
+ } else if (params->mode_num != 0) {
+ info[infolen++] = HKDF_CONTEXT_DIRECT_KEY;
info[infolen++] = params->mode_num;
file_nonce_in_iv = true;
} else if (params->file_nonce_specified) {
- info[infolen++] = HKDF_CONTEXT_PER_FILE_KEY;
+ info[infolen++] = HKDF_CONTEXT_PER_FILE_ENC_KEY;
memcpy(&info[infolen], params->file_nonce,
FILE_NONCE_SIZE);
infolen += FILE_NONCE_SIZE;
}
if (file_nonce_in_iv && params->file_nonce_specified)
- memcpy(&iv->bytes[8], params->file_nonce, FILE_NONCE_SIZE);
+ memcpy(iv->nonce, params->file_nonce, FILE_NONCE_SIZE);
}
enum {
+ OPT_BLOCK_NUMBER,
OPT_BLOCK_SIZE,
OPT_DECRYPT,
OPT_FILE_NONCE,
+ OPT_FS_UUID,
OPT_HELP,
+ OPT_INODE_NUMBER,
+ OPT_IV_INO_LBLK_32,
+ OPT_IV_INO_LBLK_64,
OPT_KDF,
OPT_MODE_NUM,
OPT_PADDING,
};
static const struct option longopts[] = {
+ { "block-number", required_argument, NULL, OPT_BLOCK_NUMBER },
{ "block-size", required_argument, NULL, OPT_BLOCK_SIZE },
{ "decrypt", no_argument, NULL, OPT_DECRYPT },
{ "file-nonce", required_argument, NULL, OPT_FILE_NONCE },
+ { "fs-uuid", required_argument, NULL, OPT_FS_UUID },
{ "help", no_argument, NULL, OPT_HELP },
+ { "inode-number", required_argument, NULL, OPT_INODE_NUMBER },
+ { "iv-ino-lblk-32", no_argument, NULL, OPT_IV_INO_LBLK_32 },
+ { "iv-ino-lblk-64", no_argument, NULL, OPT_IV_INO_LBLK_64 },
{ "kdf", required_argument, NULL, OPT_KDF },
{ "mode-num", required_argument, NULL, OPT_MODE_NUM },
{ "padding", required_argument, NULL, OPT_PADDING },
size_t padding = 0;
const struct fscrypt_cipher *cipher;
u8 real_key[MAX_KEY_SIZE];
- struct fscrypt_iv iv;
+ union fscrypt_iv iv;
char *tmp;
int c;
while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
switch (c) {
+ case OPT_BLOCK_NUMBER:
+ errno = 0;
+ params.block_number = strtoull(optarg, &tmp, 10);
+ if (*tmp || errno)
+ die("Invalid block number: %s", optarg);
+ break;
case OPT_BLOCK_SIZE:
+ errno = 0;
block_size = strtoul(optarg, &tmp, 10);
- if (block_size <= 0 || *tmp)
+ if (block_size <= 0 || *tmp || errno)
die("Invalid block size: %s", optarg);
break;
case OPT_DECRYPT:
die("Invalid file nonce: %s", optarg);
params.file_nonce_specified = true;
break;
+ case OPT_FS_UUID:
+ if (hex2bin(optarg, params.fs_uuid, UUID_SIZE)
+ != UUID_SIZE)
+ die("Invalid filesystem UUID: %s", optarg);
+ params.fs_uuid_specified = true;
+ break;
case OPT_HELP:
usage(stdout);
return 0;
+ case OPT_INODE_NUMBER:
+ errno = 0;
+ params.inode_number = strtoull(optarg, &tmp, 10);
+ if (params.inode_number <= 0 || *tmp || errno)
+ die("Invalid inode number: %s", optarg);
+ break;
+ case OPT_IV_INO_LBLK_32:
+ params.iv_ino_lblk_32 = true;
+ break;
+ case OPT_IV_INO_LBLK_64:
+ params.iv_ino_lblk_64 = true;
+ break;
case OPT_KDF:
params.kdf = parse_kdf_algorithm(optarg);
break;
get_key_and_iv(¶ms, real_key, cipher->keysize, &iv);
- crypt_loop(cipher, real_key, &iv, decrypting, block_size, padding);
+ crypt_loop(cipher, real_key, &iv, decrypting, block_size, padding,
+ params.iv_ino_lblk_64 || params.iv_ino_lblk_32);
return 0;
}