X-Git-Url: http://git.apps.os.sepia.ceph.com/?p=xfstests-dev.git;a=blobdiff_plain;f=src%2Ffscrypt-crypt-util.c;h=03cc3c4a12997734195a455c1bfd2ba249525c5c;hp=bafc15e0660405451622b885047a3e72fdfa3988;hb=609bb3562241f9fc877aff7309d62af6cb10795d;hpb=80bc5f9d790b92f84de376a17b5d90689f1173a2 diff --git a/src/fscrypt-crypt-util.c b/src/fscrypt-crypt-util.c index bafc15e0..03cc3c4a 100644 --- a/src/fscrypt-crypt-util.c +++ b/src/fscrypt-crypt-util.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -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,8 +1790,42 @@ 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_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. * If a KDF was specified, a subkey is derived from the master key and the mode @@ -1710,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; @@ -1722,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) @@ -1742,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++] = 3; /* 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++] = 2; /* 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; @@ -1762,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 }, @@ -1794,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; @@ -1813,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: @@ -1827,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; @@ -1872,6 +2068,7 @@ int main(int argc, char *argv[]) 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; }