fscrypt-crypt-util: fix maximum IV size
[xfstests-dev.git] / src / fscrypt-crypt-util.c
index 30f5e5851eb8e35837891f193f7b7e3cc51cf0a1..03cc3c4a12997734195a455c1bfd2ba249525c5c 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/types.h>
 #include <stdarg.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -58,11 +59,25 @@ static void usage(FILE *fp)
 "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"
@@ -135,6 +150,11 @@ static inline u32 ror32(u32 v, int 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));
@@ -1571,12 +1591,58 @@ static void test_adiantum(void)
 }
 #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;
@@ -1629,16 +1695,34 @@ static const struct fscrypt_cipher *find_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;
@@ -1661,7 +1745,12 @@ static void crypt_loop(const struct fscrypt_cipher *cipher, const u8 *key,
 
                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);
 }
@@ -1701,11 +1790,41 @@ struct key_and_iv_params {
        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.
@@ -1714,11 +1833,11 @@ struct key_and_iv_params {
  */
 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;
 
@@ -1726,6 +1845,28 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
 
        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)
@@ -1746,12 +1887,28 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
                                    &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;
@@ -1766,24 +1923,34 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
        }
 
        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 },
@@ -1798,7 +1965,7 @@ int main(int argc, char *argv[])
        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;
 
@@ -1817,9 +1984,16 @@ int main(int argc, char *argv[])
 
        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:
@@ -1831,9 +2005,27 @@ int main(int argc, char *argv[])
                                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;
@@ -1876,6 +2068,7 @@ int main(int argc, char *argv[])
 
        get_key_and_iv(&params, 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;
 }