From patchwork Fri Dec 13 05:28:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Biggers X-Patchwork-Id: 13906528 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7D5B4188704; Fri, 13 Dec 2024 05:28:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734067736; cv=none; b=M9A+EaykNe9/8wmvvYxOqlV4GTsjnw78bt1u5RvdFTv81sxX/ZzgH8xnXxb8al935ItabKlXD/NQMfwM96HrsuNT68EwslO1/HTfuekTlmn6foIxTu2TtXzhSF+vuhKvxV0/aNTNqbnZgxMb7B4C2o/qGQAdebetuZD+4qYxiho= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734067736; c=relaxed/simple; bh=4lVwDJfz8o6ckrb9c9DVbwZVt61mRQPBQylprA81kXw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NbbkikRT0bPthimYT3QbisfJn8Tg6xXKA/s7wh1qrwixmqrEpBIee8d6QnuQWMkT/MljEwSOZs/7E/AdsNmJraefH7J6VJddo1ZhMv0VuNZuXTYzUbH7N++cISfizHZqmUa3Mruwq6BeuucLkjrePj3GKBPPQUOz1kROM0RBXUA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=j+X9Lt3T; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="j+X9Lt3T" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E069EC4CED6; Fri, 13 Dec 2024 05:28:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1734067736; bh=4lVwDJfz8o6ckrb9c9DVbwZVt61mRQPBQylprA81kXw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=j+X9Lt3TPrCnSqCrTDyk7iGyB0vcZPcnz8ypHQwwem70T34aKYTyOrC35m/VTRqC+ qY2AQ4eIVeNZL8eXDxWEWRqsn0VciCCPCWCIZD4dISZoJppOxDfu0E7MhFXyDAb6rT ZU/EavFxnJNDqeBlEmmKTBWzmwKBftg+cAec2v1duK7ZcHBt+nj7VuK8NZdhfXVjM/ AKO+l0CbFZR12MxuAslqQfW2lfJAo18JuomuhblEMALsuvAQF0+38W9/wHDzbMJbDn czw71DA9Sk2dj5LelvGGQh2qlHHeVLj6sC4ZXEgLkYDIQkWEYHb+k6YTVL/8Ei9aQI GBQW80wZ0PbYQ== From: Eric Biggers To: fstests@vger.kernel.org Cc: linux-fscrypt@vger.kernel.org, Bartosz Golaszewski , Gaurav Kashyap Subject: [PATCH v2 1/3] fscrypt-crypt-util: add hardware KDF support Date: Thu, 12 Dec 2024 21:28:37 -0800 Message-ID: <20241213052840.314921-2-ebiggers@kernel.org> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20241213052840.314921-1-ebiggers@kernel.org> References: <20241213052840.314921-1-ebiggers@kernel.org> Precedence: bulk X-Mailing-List: linux-fscrypt@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Eric Biggers Add support to fscrypt-crypt-util for replicating the extra KDF (Key Derivation Function) step that is required when a hardware-wrapped inline encryption key is used. This step normally occurs in hardware, but we need to replicate it for testing purposes. Note, some care was needed to handle the fact that both inlinecrypt_key and sw_secret can be needed in a single run of fscrypt-crypt-util. Namely, with --iv-ino-lblk-32, inlinecrypt_key is needed for the en/decryption while sw_secret is needed for hash_inode_number(). Signed-off-by: Eric Biggers --- src/fscrypt-crypt-util.c | 251 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 240 insertions(+), 11 deletions(-) diff --git a/src/fscrypt-crypt-util.c b/src/fscrypt-crypt-util.c index a1b5005d..4dde1d4a 100644 --- a/src/fscrypt-crypt-util.c +++ b/src/fscrypt-crypt-util.c @@ -68,10 +68,14 @@ static void usage(FILE *fp) " --decrypt Decrypt instead of encrypt\n" " --direct-key Use the format where the IVs include the file\n" " nonce and the same key is shared across files.\n" " --dump-key-identifier Instead of encrypting/decrypting data, just\n" " compute and dump the key identifier.\n" +" --enable-hw-kdf Apply the hardware KDF (replicated in software)\n" +" to the key before using it. Use this to\n" +" replicate the en/decryption that is done when\n" +" the filesystem is given a hardware-wrapped key.\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" @@ -86,10 +90,14 @@ static void usage(FILE *fp) " HKDF-SHA512, or none. Default: none\n" " --mode-num=NUM The encryption mode number. This may be required\n" " for key derivation, depending on other options.\n" " --padding=PADDING If last data unit is partial, zero-pad it to next\n" " PADDING-byte boundary. Default: DUSIZE\n" +" --use-inlinecrypt-key In combination with --enable-hw-kdf, this causes\n" +" the en/decryption to be done with the \"inline\n" +" encryption key\" rather than with a key derived\n" +" from the \"software secret\".\n" , fp); } /*----------------------------------------------------------------------------* * Utilities * @@ -347,10 +355,15 @@ static inline u32 gf2_8_mul_x_4way(u32 w) typedef struct { __le64 lo; __le64 hi; } ble128; +typedef struct { + __be64 hi; + __be64 lo; +} bbe128; + /* Multiply a GF(2^128) element by the polynomial 'x' */ static inline void gf2_128_mul_x_xts(ble128 *t) { u64 lo = le64_to_cpu(t->lo); u64 hi = le64_to_cpu(t->hi); @@ -368,10 +381,19 @@ static inline void gf2_128_mul_x_polyval(ble128 *t) t->hi = cpu_to_le64(((hi << 1) | (lo >> 63)) ^ hi_reducer); t->lo = cpu_to_le64((lo << 1) ^ lo_reducer); } +static inline void gf2_128_mul_x_cmac(bbe128 *t) +{ + u64 lo = be64_to_cpu(t->lo); + u64 hi = be64_to_cpu(t->hi); + + t->hi = cpu_to_be64((hi << 1) | (lo >> 63)); + t->lo = cpu_to_be64((lo << 1) ^ ((hi & (1ULL << 63)) ? 0x87 : 0)); +} + static void gf2_128_mul_polyval(ble128 *r, const ble128 *b) { int i; ble128 p; u64 lo = le64_to_cpu(b->lo); @@ -1494,10 +1516,71 @@ static void test_aes_256_hctr2(void) } close(algfd); } #endif /* ENABLE_ALG_TESTS */ +static void aes_256_cmac(const u8 key[AES_256_KEY_SIZE], + const u8 *msg, size_t msglen, u8 mac[AES_BLOCK_SIZE]) +{ + const size_t partial = msglen % AES_BLOCK_SIZE; + const size_t full_blocks = msglen ? (msglen - 1) / AES_BLOCK_SIZE : 0; + struct aes_key k; + bbe128 subkey; + size_t i; + + aes_setkey(&k, key, AES_256_KEY_SIZE); + memset(&subkey, 0, sizeof(subkey)); + aes_encrypt(&k, (u8 *)&subkey, (u8 *)&subkey); + gf2_128_mul_x_cmac(&subkey); + + memset(mac, 0, AES_BLOCK_SIZE); + for (i = 0; i < full_blocks * AES_BLOCK_SIZE; i += AES_BLOCK_SIZE) { + xor(mac, mac, &msg[i], AES_BLOCK_SIZE); + aes_encrypt(&k, mac, mac); + } + xor(mac, mac, &msg[i], msglen - i); + if (partial != 0 || msglen == 0) { + mac[msglen - i] ^= 0x80; + gf2_128_mul_x_cmac(&subkey); + } + xor(mac, mac, (u8 *)&subkey, AES_BLOCK_SIZE); + aes_encrypt(&k, mac, mac); +} + +#ifdef ENABLE_ALG_TESTS +#include +static void test_aes_256_cmac(void) +{ + unsigned long num_tests = NUM_ALG_TEST_ITERATIONS; + CMAC_CTX *ctx = CMAC_CTX_new(); + + ASSERT(ctx != NULL); + while (num_tests--) { + u8 key[AES_256_KEY_SIZE]; + u8 msg[128]; + u8 mac[AES_BLOCK_SIZE]; + u8 ref_mac[sizeof(mac)]; + const size_t msglen = 1 + (rand() % sizeof(msg)); + size_t out_len = 0; + + rand_bytes(key, sizeof(key)); + rand_bytes(msg, msglen); + + aes_256_cmac(key, msg, msglen, mac); + + ASSERT(ctx != NULL); + ASSERT(CMAC_Init(ctx, key, sizeof(key), EVP_aes_256_cbc(), + NULL) == 1); + ASSERT(CMAC_Update(ctx, msg, msglen) == 1); + ASSERT(CMAC_Final(ctx, ref_mac, &out_len)); + ASSERT(out_len == sizeof(mac)); + ASSERT(memcmp(mac, ref_mac, sizeof(mac)) == 0); + } + CMAC_CTX_free(ctx); +} +#endif /* ENABLE_ALG_TESTS */ + /*----------------------------------------------------------------------------* * XChaCha12 stream cipher * *----------------------------------------------------------------------------*/ /* @@ -2060,13 +2143,21 @@ static u8 parse_mode_number(const char *arg) die("Invalid mode number: %s", arg); return num; } struct key_and_iv_params { + /* + * If enable_hw_kdf=true, then master_key and sw_secret will differ. + * Otherwise they will be the same. + */ u8 master_key[MAX_KEY_SIZE]; int master_key_size; + u8 sw_secret[MAX_KEY_SIZE]; + int sw_secret_size; enum kdf_algorithm kdf; + bool enable_hw_kdf; + bool use_inlinecrypt_key; u8 mode_num; u8 file_nonce[FILE_NONCE_SIZE]; bool file_nonce_specified; bool direct_key; bool iv_ino_lblk_64; @@ -2075,17 +2166,18 @@ struct key_and_iv_params { u64 inode_number; u8 fs_uuid[UUID_SIZE]; bool fs_uuid_specified; }; -#define HKDF_CONTEXT_KEY_IDENTIFIER 1 +#define HKDF_CONTEXT_KEY_IDENTIFIER_FOR_RAW_KEY 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 +#define HKDF_CONTEXT_KEY_IDENTIFIER_FOR_HW_WRAPPED_KEY 8 /* 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"; @@ -2096,42 +2188,157 @@ static u32 hash_inode_number(const struct key_and_iv_params *params) info[8] = HKDF_CONTEXT_INODE_HASH_KEY; if (params->kdf != KDF_HKDF_SHA512) die("--iv-ino-lblk-32 requires --kdf=HKDF-SHA512"); - hkdf_sha512(params->master_key, params->master_key_size, + hkdf_sha512(params->sw_secret, params->sw_secret_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); } +/* + * Replicate the hardware KDF, given the raw master key and the context + * indicating which type of key to derive. + * + * Detailed explanation: + * + * With hardware-wrapped keys, an extra level is inserted into fscrypt's key + * hierarchy, above what was previously the root: + * + * master_key + * | + * ------------- + * | | + * | | + * inlinecrypt_key sw_secret + * | + * | + * (everything else) + * + * From the master key, the "inline encryption key" (inlinecrypt_key) and + * "software secret" (sw_secret) are derived. The inlinecrypt_key is used to + * encrypt file contents. The sw_secret is used just like the old master key, + * except that it isn't used to derive the file contents key. (I.e., it's used + * to derive filenames encryption keys, key identifiers, inode hash keys, etc.) + * + * Normally, software only sees master_key in "wrapped" form, and can never see + * inlinecrypt_key at all. Only specialized hardware can access the raw + * master_key to derive the subkeys, in a step we call the "HW KDF". + * + * However, the HW KDF is a well-specified algorithm, and when a + * hardware-wrapped key is initially created, software can choose to import a + * raw key. This allows software to test the feature by replicating the HW KDF. + * + * This is what this function does; it will derive either the inlinecrypt_key + * key or the sw_secret, depending on the KDF context passed. + */ +static void hw_kdf(const u8 *master_key, size_t master_key_size, + const u8 *ctx, size_t ctx_size, + u8 *derived_key, size_t derived_key_size) +{ + static const u8 label[11] = "\0\0\x40\0\0\0\0\0\0\0\x20"; + u8 info[128]; + size_t i; + + if (master_key_size != AES_256_KEY_SIZE) + die("--hw-kdf requires a 32-byte master key"); + ASSERT(derived_key_size % AES_BLOCK_SIZE == 0); + + /* + * This is NIST SP 800-108 "KDF in Counter Mode" with AES-256-CMAC as + * the PRF and a particular choice of labels and contexts. + */ + for (i = 0; i < derived_key_size; i += AES_BLOCK_SIZE) { + u8 *p = info; + + ASSERT(sizeof(__be32) + sizeof(label) + 1 + ctx_size + + sizeof(__be32) <= sizeof(info)); + + put_unaligned_be32(1 + (i / AES_BLOCK_SIZE), p); + p += sizeof(__be32); + memcpy(p, label, sizeof(label)); + p += sizeof(label); + *p++ = 0; + memcpy(p, ctx, ctx_size); + p += ctx_size; + put_unaligned_be32(derived_key_size * 8, p); + p += sizeof(__be32); + + aes_256_cmac(master_key, info, p - info, &derived_key[i]); + } +} + +#define INLINECRYPT_KEY_SIZE 64 +#define SW_SECRET_SIZE 32 + +static void derive_inline_encryption_key(const u8 *master_key, + size_t master_key_size, + u8 inlinecrypt_key[INLINECRYPT_KEY_SIZE]) +{ + static const u8 ctx[36] = + "inline encryption key\0\0\0\0\0\0\x03\x43\0\x82\x50\0\0\0\0"; + + hw_kdf(master_key, master_key_size, ctx, sizeof(ctx), + inlinecrypt_key, INLINECRYPT_KEY_SIZE); +} + +static void derive_sw_secret(const u8 *master_key, size_t master_key_size, + u8 sw_secret[SW_SECRET_SIZE]) +{ + static const u8 ctx[28] = + "raw secret\0\0\0\0\0\0\0\0\0\x03\x17\0\x80\x50\0\0\0\0"; + + hw_kdf(master_key, master_key_size, ctx, sizeof(ctx), + sw_secret, SW_SECRET_SIZE); +} + static void derive_real_key(const struct key_and_iv_params *params, u8 *real_key, size_t real_key_size) { struct aes_key aes_key; u8 info[8 + 1 + 1 + UUID_SIZE] = "fscrypt"; size_t infolen = 8; size_t i; - ASSERT(real_key_size <= params->master_key_size); + if (params->use_inlinecrypt_key) { + /* + * With --use-inlinecrypt-key, we need to use the "hardware KDF" + * rather than the normal fscrypt KDF. Note that the fscrypt + * KDF might still be used elsewhere, e.g. hash_inode_number() + * -- it just won't be used for the actual encryption key. + */ + if (!params->enable_hw_kdf) + die("--use-inlinecrypt-key requires --enable-hw-kdf"); + if (!params->iv_ino_lblk_64 && !params->iv_ino_lblk_32) + die("--use-inlinecrypt-key requires one of --iv-ino-lblk-{64,32}"); + if (real_key_size != INLINECRYPT_KEY_SIZE) + die("cipher not compatible with --use-inlinecrypt-key"); + derive_inline_encryption_key(params->master_key, + params->master_key_size, real_key); + return; + } + + if (params->sw_secret_size < real_key_size) + die("Master key is too short for cipher"); switch (params->kdf) { case KDF_NONE: - memcpy(real_key, params->master_key, real_key_size); + memcpy(real_key, params->sw_secret, real_key_size); break; case KDF_AES_128_ECB: if (!params->file_nonce_specified) die("--kdf=AES-128-ECB requires --file-nonce"); STATIC_ASSERT(FILE_NONCE_SIZE == AES_128_KEY_SIZE); ASSERT(real_key_size % AES_BLOCK_SIZE == 0); aes_setkey(&aes_key, params->file_nonce, AES_128_KEY_SIZE); for (i = 0; i < real_key_size; i += AES_BLOCK_SIZE) - aes_encrypt(&aes_key, ¶ms->master_key[i], + aes_encrypt(&aes_key, ¶ms->sw_secret[i], &real_key[i]); break; case KDF_HKDF_SHA512: if (params->direct_key) { if (params->mode_num == 0) @@ -2162,11 +2369,11 @@ static void derive_real_key(const struct key_and_iv_params *params, info[infolen++] = HKDF_CONTEXT_PER_FILE_ENC_KEY; memcpy(&info[infolen], params->file_nonce, FILE_NONCE_SIZE); infolen += FILE_NONCE_SIZE; } - hkdf_sha512(params->master_key, params->master_key_size, + hkdf_sha512(params->sw_secret, params->sw_secret_size, NULL, 0, info, infolen, real_key, real_key_size); break; default: ASSERT(0); } @@ -2230,15 +2437,18 @@ static void do_dump_key_identifier(const struct key_and_iv_params *params) { u8 info[9] = "fscrypt"; u8 key_identifier[16]; int i; - info[8] = HKDF_CONTEXT_KEY_IDENTIFIER; + if (params->enable_hw_kdf) + info[8] = HKDF_CONTEXT_KEY_IDENTIFIER_FOR_HW_WRAPPED_KEY; + else + info[8] = HKDF_CONTEXT_KEY_IDENTIFIER_FOR_RAW_KEY; if (params->kdf != KDF_HKDF_SHA512) die("--dump-key-identifier requires --kdf=HKDF-SHA512"); - hkdf_sha512(params->master_key, params->master_key_size, + hkdf_sha512(params->sw_secret, params->sw_secret_size, NULL, 0, info, sizeof(info), key_identifier, sizeof(key_identifier)); for (i = 0; i < sizeof(key_identifier); i++) printf("%02x", key_identifier[i]); @@ -2248,44 +2458,59 @@ static void parse_master_key(const char *arg, struct key_and_iv_params *params) { params->master_key_size = hex2bin(arg, params->master_key, MAX_KEY_SIZE); if (params->master_key_size < 0) die("Invalid master_key: %s", arg); + + /* Derive sw_secret from master_key, if needed. */ + if (params->enable_hw_kdf) { + derive_sw_secret(params->master_key, params->master_key_size, + params->sw_secret); + params->sw_secret_size = SW_SECRET_SIZE; + } else { + memcpy(params->sw_secret, params->master_key, + params->master_key_size); + params->sw_secret_size = params->master_key_size; + } } enum { OPT_DATA_UNIT_INDEX, OPT_DATA_UNIT_SIZE, OPT_DECRYPT, OPT_DIRECT_KEY, OPT_DUMP_KEY_IDENTIFIER, + OPT_ENABLE_HW_KDF, 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, + OPT_USE_INLINECRYPT_KEY, }; static const struct option longopts[] = { { "data-unit-index", required_argument, NULL, OPT_DATA_UNIT_INDEX }, { "data-unit-size", required_argument, NULL, OPT_DATA_UNIT_SIZE }, { "decrypt", no_argument, NULL, OPT_DECRYPT }, { "direct-key", no_argument, NULL, OPT_DIRECT_KEY }, { "dump-key-identifier", no_argument, NULL, OPT_DUMP_KEY_IDENTIFIER }, + { "enable-hw-kdf", no_argument, NULL, OPT_ENABLE_HW_KDF }, { "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 }, + { "use-inlinecrypt-key", no_argument, NULL, OPT_USE_INLINECRYPT_KEY }, { NULL, 0, NULL, 0 }, }; int main(int argc, char *argv[]) { @@ -2308,10 +2533,11 @@ int main(int argc, char *argv[]) test_aes(); test_sha2(); test_hkdf_sha512(); test_aes_256_xts(); test_aes_256_cts_cbc(); + test_aes_256_cmac(); test_adiantum(); test_aes_256_hctr2(); #endif while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) { @@ -2335,10 +2561,13 @@ int main(int argc, char *argv[]) params.direct_key = true; break; case OPT_DUMP_KEY_IDENTIFIER: dump_key_identifier = true; break; + case OPT_ENABLE_HW_KDF: + params.enable_hw_kdf = true; + break; case OPT_FILE_NONCE: if (hex2bin(optarg, params.file_nonce, FILE_NONCE_SIZE) != FILE_NONCE_SIZE) die("Invalid file nonce: %s", optarg); params.file_nonce_specified = true; @@ -2374,10 +2603,13 @@ int main(int argc, char *argv[]) padding = strtoul(optarg, &tmp, 10); if (padding <= 0 || *tmp || !is_power_of_2(padding) || padding > INT_MAX) die("Invalid padding amount: %s", optarg); break; + case OPT_USE_INLINECRYPT_KEY: + params.use_inlinecrypt_key = true; + break; default: usage(stderr); return 2; } } @@ -2406,13 +2638,10 @@ int main(int argc, char *argv[]) die("Data unit size of %zu bytes is too small for cipher %s", data_unit_size, cipher->name); parse_master_key(argv[1], ¶ms); - if (params.master_key_size < cipher->keysize) - die("Master key is too short for cipher %s", cipher->name); - get_key_and_iv(¶ms, real_key, cipher->keysize, &iv); crypt_loop(cipher, real_key, &iv, decrypting, data_unit_size, padding, params.iv_ino_lblk_64 || params.iv_ino_lblk_32); return 0; From patchwork Fri Dec 13 05:28:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Biggers X-Patchwork-Id: 13906529 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A057D1684AC; Fri, 13 Dec 2024 05:28:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734067736; cv=none; b=ALUWc77fTZip2t7/hQeukkfaMnA5D8XMp8ytorwlEw8sQoVJgrK25LpKXLToqzxjgeG6rzimrwsw8P/y+ieuICC0ontzaNbVX4EvjeJyzu84vXz86i5ZzVPCXLFE4XIbXND23g0Kbfr8OcfWVubV8eYL6LmOpjhX9pZ8vZ7t3jQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734067736; c=relaxed/simple; bh=KhZZX0QH8y/SjG56Xj6WzianHbNyw3Wx43N5KCDD9JI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=iNIVICCFF8WF6w9FmsIz7aOfED+gGzW9AO9fyxujtKiu9qo9m2sLQLfmiaApFDg9cWgpyqU16JxqWAxC1cbuLRspWuNj6GJaJ2xVu0IQA3InOTMOKZ54kAgqjhrn2XJTL3hqhvQwmNnCuB4Ftz8B+lisYIjiNPcQ5cCSFOY70eg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=OuMLih/+; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="OuMLih/+" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3118EC4CED7; Fri, 13 Dec 2024 05:28:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1734067736; bh=KhZZX0QH8y/SjG56Xj6WzianHbNyw3Wx43N5KCDD9JI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OuMLih/+A2gexeeS3Kqq8i6ExUAXxdhiOGmisbrhqiScJOEOeB5CBgCzsPJlHSVVV E+FgLTaBasH3oazKZ5PfhRDRmEgSoHFe2OXftUSpw2pi/L4rPVUFnVrqiG2gMW28Mg tEI6nej5KgC1eJ3lrubYcVQUqUa59jXSs1wla9HfAxREb5iQD3fqhfKzHeM7ZH+4x7 8InnMcSo+nXUrBR6Y1hHyO/sJdYYpc8NVEUg/0frWBT7YykmJeqnTi0NFz9u6vfj/+ 4FTNdk8Glgz90UyyARzMWvglySLt5cOlbQFXXEp0QNyLLXgwvQm15vurkQ0TxreD86 +9kHIYC0Ovk2w== From: Eric Biggers To: fstests@vger.kernel.org Cc: linux-fscrypt@vger.kernel.org, Bartosz Golaszewski , Gaurav Kashyap Subject: [PATCH v2 2/3] common/encrypt: support hardware-wrapped key testing Date: Thu, 12 Dec 2024 21:28:38 -0800 Message-ID: <20241213052840.314921-3-ebiggers@kernel.org> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20241213052840.314921-1-ebiggers@kernel.org> References: <20241213052840.314921-1-ebiggers@kernel.org> Precedence: bulk X-Mailing-List: linux-fscrypt@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Eric Biggers To support testing the kernel's support for hardware-wrapped inline encryption keys, update _verify_ciphertext_for_encryption_policy() to support a hw_wrapped_key option. Signed-off-by: Eric Biggers --- common/config | 1 + common/encrypt | 80 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/common/config b/common/config index fcff0660..091405b3 100644 --- a/common/config +++ b/common/config @@ -233,10 +233,11 @@ export BLKZONE_PROG="$(type -P blkzone)" export GZIP_PROG="$(type -P gzip)" export BTRFS_IMAGE_PROG="$(type -P btrfs-image)" export BTRFS_MAP_LOGICAL_PROG=$(type -P btrfs-map-logical) export PARTED_PROG="$(type -P parted)" export XFS_PROPERTY_PROG="$(type -P xfs_property)" +export FSCRYPTCTL_PROG="$(type -P fscryptctl)" # use 'udevadm settle' or 'udevsettle' to wait for lv to be settled. # newer systems have udevadm command but older systems like RHEL5 don't. # But if neither one is available, just set it to "sleep 1" to wait for lv to # be settled diff --git a/common/encrypt b/common/encrypt index d90a566a..1caca767 100644 --- a/common/encrypt +++ b/common/encrypt @@ -150,10 +150,46 @@ _require_encryption_policy_support() $KEYCTL_PROG clear $TEST_KEYRING_ID fi rm -r $dir } +# Require that the scratch filesystem accepts the "inlinecrypt" mount option. +# +# This does not check whether the scratch block device has any specific inline +# encryption capabilities. +_require_scratch_inlinecrypt() +{ + _require_scratch + _scratch_mkfs &>> $seqres.full + if ! _try_scratch_mount -o inlinecrypt &>> $seqres.full; then + _notrun "filesystem doesn't support -o inlinecrypt" + fi +} + +# Require that the given block device supports hardware-wrapped inline +# encryption keys, and require that a command-line tool that supports +# importing/generating/preparing them is available. +_require_hw_wrapped_key_support() +{ + local dev=$1 + + echo "Checking for HW-wrapped key support on $dev" >> $seqres.full + local sysfs_dir=$(_sysfs_dev $dev) + if [ ! -e $sysfs_dir/queue ]; then + sysfs_dir=$sysfs_dir/.. + fi + if [ ! -e $sysfs_dir/queue/crypto/hw_wrapped_keys ]; then + _notrun "$dev doesn't support hardware-wrapped inline encryption keys" + fi + + echo "Checking for fscryptctl support for HW-wrapped keys" >> $seqres.full + _require_command "$FSCRYPTCTL_PROG" fscryptctl + if ! "$FSCRYPTCTL_PROG" --help | grep -q "import_hw_wrapped_key"; then + _notrun "fscryptctl too old; doesn't support hardware-wrapped inline encryption keys" + fi +} + _scratch_mkfs_encrypted() { case $FSTYP in ext4|f2fs) _scratch_mkfs -O encrypt @@ -249,18 +285,21 @@ _generate_key_descriptor() } # Generate a raw encryption key, but don't add it to any keyring yet. _generate_raw_encryption_key() { + local size=${1:-64} local raw="" local i - for ((i = 0; i < 64; i++)); do + for ((i = 0; i < $size; i++)); do raw="${raw}\\x$(printf "%02x" $(( $RANDOM % 256 )))" done echo $raw } +RAW_HW_KEY_SIZE=32 + # Serialize an integer into a CPU-endian bytestring of the given length, and # print it as a string where each byte is hex-escaped. For example, # `_num_to_hex 1000 4` == "\xe8\x03\x00\x00" if the CPU is little endian. _num_to_hex() { @@ -405,10 +444,25 @@ _add_enckey() shift 2 echo -ne "$raw_key" | $XFS_IO_PROG -c "add_enckey $*" "$mnt" } +# Create a hardware-wrapped key from the given raw key using the given block +# device, add it to the given filesystem, and print the resulting key +# identifier. +_add_hw_wrapped_key() +{ + local dev=$1 + local mnt=$2 + local raw_key=$3 + + echo -ne "$raw_key" | \ + $FSCRYPTCTL_PROG import_hw_wrapped_key "$dev" | \ + $FSCRYPTCTL_PROG prepare_hw_wrapped_key "$dev" | \ + $FSCRYPTCTL_PROG add_key --hw-wrapped-key "$mnt" +} + _user_do_add_enckey() { local mnt=$1 local raw_key=$2 shift 2 @@ -851,19 +905,21 @@ _fscrypt_mode_name_to_num() # 'v2': test a v2 encryption policy # 'direct': test the DIRECT_KEY policy flag # 'iv_ino_lblk_64': test the IV_INO_LBLK_64 policy flag # 'iv_ino_lblk_32': test the IV_INO_LBLK_32 policy flag # 'log2_dusize=N': test the log2_data_unit_size field +# 'hw_wrapped_key': use a hardware-wrapped inline encryption key # _verify_ciphertext_for_encryption_policy() { local contents_encryption_mode=$1 local filenames_encryption_mode=$2 local opt local policy_version=1 local policy_flags=0 local log2_dusize=0 + local hw_wrapped_key=false local set_encpolicy_args="" local crypt_util_args="" local crypt_util_contents_args="" local crypt_util_filename_args="" local expected_identifier @@ -888,10 +944,15 @@ _verify_ciphertext_for_encryption_policy() (( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 )) ;; log2_dusize=*) log2_dusize=$(echo "$opt" | sed 's/^log2_dusize=//') ;; + hw_wrapped_key) + hw_wrapped_key=true + crypt_util_args+=" --enable-hw-kdf" + crypt_util_contents_args+=" --use-inlinecrypt-key" + ;; *) _fail "Unknown option '$opt' passed to ${FUNCNAME[0]}" ;; esac done @@ -927,10 +988,13 @@ _verify_ciphertext_for_encryption_policy() fi fi set_encpolicy_args=${set_encpolicy_args# } _require_scratch_encryption $set_encpolicy_args -f $policy_flags + if $hw_wrapped_key; then + _require_hw_wrapped_key_support $SCRATCH_DEV + fi _require_test_program "fscrypt-crypt-util" _require_xfs_io_command "fiemap" _require_get_encryption_nonce_support _require_get_ciphertext_filename_support if (( policy_version == 1 )); then @@ -956,15 +1020,23 @@ _verify_ciphertext_for_encryption_policy() crypt_util_contents_args+="$crypt_util_args" crypt_util_filename_args+="$crypt_util_args" echo "Generating encryption key" >> $seqres.full - local raw_key=$(_generate_raw_encryption_key) if (( policy_version > 1 )); then - local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" \ - | awk '{print $NF}') + if $hw_wrapped_key; then + local raw_key=$(_generate_raw_encryption_key \ + $RAW_HW_KEY_SIZE) + local keyspec=$(_add_hw_wrapped_key $SCRATCH_DEV \ + $SCRATCH_MNT "$raw_key") + else + local raw_key=$(_generate_raw_encryption_key) + local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" | \ + awk '{print $NF}') + fi else + local raw_key=$(_generate_raw_encryption_key) local keyspec=$(_generate_key_descriptor) _init_session_keyring _add_session_encryption_key $keyspec $raw_key fi local raw_key_hex=$(echo "$raw_key" | tr -d '\\x') From patchwork Fri Dec 13 05:28:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Biggers X-Patchwork-Id: 13906530 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D6B7918893C; Fri, 13 Dec 2024 05:28:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734067736; cv=none; b=JKezI1sb6oJ+agxJHHL/s5Wifm6IpKU6oiEMHxNI9ISLWo9FGSV/NGrdNK8IMJiu3c8ZC+HAlgYZiDpD7UlowyhJktFWtAaZHKqxCmz61Z7LWzWwby8hqqEI284NWXidF4a1fvjUeQGy+CkNpN8aeYG8luWsTy0PCoJODs3k+98= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734067736; c=relaxed/simple; bh=LMj6vrPETXA4uFxujgB3lvJ+v75BVZ8LjhTWGIMzEX4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ApUqPy3TuYVv8ITJolQB33sthh42yuJ8QNifCipcBsMRvZXar9R7n3F9YQd8KhfZj3gq1BG7kb0vEQDLde686DnpGBIHvudqVUn5cKjmCqzyJ8U00cHx0gJiP2GMRo+hd14FR5Qkjhv/gROksL7aKiejsCrgsF/povHojGWvMes= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=agsu2Sev; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="agsu2Sev" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 76266C4CED1; Fri, 13 Dec 2024 05:28:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1734067736; bh=LMj6vrPETXA4uFxujgB3lvJ+v75BVZ8LjhTWGIMzEX4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=agsu2SevZPOqZATvUxhRUeJ/daRcx+vLXJNsk6if3W4IlhzhEmKyK/P5VmP5plBj3 4G0Z5Rkp786TXT3P3IkozY3BOIyrrL4Ia5bHeF29cQddmHm7L0kt4qLniMGs4xVV6f 3YIib6/yGBiNhSPY3bZ/nTI19hvAJDxatMhk1lFpuqJxCC+RIYJG3qBxOjtck9HUET TEV9Zq5+SocxgPHxmlSWzDDR3neqDNBT2Fp1qiG5YYQX4MPRjjGeNioRRW6WG8Xrxn 4yx434wjpjB09qeOEgdj4SssZxrImpPOqieztfdthuOaDyc+0aohbvUUmEE72HZiiT hvcFyvJAsi0TA== From: Eric Biggers To: fstests@vger.kernel.org Cc: linux-fscrypt@vger.kernel.org, Bartosz Golaszewski , Gaurav Kashyap Subject: [PATCH v2 3/3] generic: verify ciphertext with hardware-wrapped keys Date: Thu, 12 Dec 2024 21:28:39 -0800 Message-ID: <20241213052840.314921-4-ebiggers@kernel.org> X-Mailer: git-send-email 2.47.1 In-Reply-To: <20241213052840.314921-1-ebiggers@kernel.org> References: <20241213052840.314921-1-ebiggers@kernel.org> Precedence: bulk X-Mailing-List: linux-fscrypt@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Eric Biggers Add two tests which verify that encrypted files are encrypted correctly when a hardware-wrapped inline encryption key is used. The two tests are identical except that one uses FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 and the other uses FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32. These cover both of the settings where hardware-wrapped keys may be used. Signed-off-by: Eric Biggers --- tests/generic/900 | 24 ++++++++++++++++++++++++ tests/generic/900.out | 6 ++++++ tests/generic/901 | 24 ++++++++++++++++++++++++ tests/generic/901.out | 6 ++++++ 4 files changed, 60 insertions(+) create mode 100755 tests/generic/900 create mode 100644 tests/generic/900.out create mode 100755 tests/generic/901 create mode 100644 tests/generic/901.out diff --git a/tests/generic/900 b/tests/generic/900 new file mode 100755 index 00000000..8da8007e --- /dev/null +++ b/tests/generic/900 @@ -0,0 +1,24 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2024 Google LLC +# +# FS QA Test No. 900 +# +# Verify the ciphertext for encryption policies that use a hardware-wrapped +# inline encryption key, the IV_INO_LBLK_64 flag, and AES-256-XTS. +# +. ./common/preamble +_begin_fstest auto quick encrypt + +. ./common/filter +. ./common/encrypt + +# Hardware-wrapped keys require the inlinecrypt mount option. +_require_scratch_inlinecrypt +export MOUNT_OPTIONS="$MOUNT_OPTIONS -o inlinecrypt" + +_verify_ciphertext_for_encryption_policy AES-256-XTS AES-256-CTS-CBC \ + v2 iv_ino_lblk_64 hw_wrapped_key + +status=0 +exit diff --git a/tests/generic/900.out b/tests/generic/900.out new file mode 100644 index 00000000..9edc012c --- /dev/null +++ b/tests/generic/900.out @@ -0,0 +1,6 @@ +QA output created by 900 + +Verifying ciphertext with parameters: + contents_encryption_mode: AES-256-XTS + filenames_encryption_mode: AES-256-CTS-CBC + options: v2 iv_ino_lblk_64 hw_wrapped_key diff --git a/tests/generic/901 b/tests/generic/901 new file mode 100755 index 00000000..bfc63bde --- /dev/null +++ b/tests/generic/901 @@ -0,0 +1,24 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2024 Google LLC +# +# FS QA Test No. 901 +# +# Verify the ciphertext for encryption policies that use a hardware-wrapped +# inline encryption key, the IV_INO_LBLK_32 flag, and AES-256-XTS. +# +. ./common/preamble +_begin_fstest auto quick encrypt + +. ./common/filter +. ./common/encrypt + +# Hardware-wrapped keys require the inlinecrypt mount option. +_require_scratch_inlinecrypt +export MOUNT_OPTIONS="$MOUNT_OPTIONS -o inlinecrypt" + +_verify_ciphertext_for_encryption_policy AES-256-XTS AES-256-CTS-CBC \ + v2 iv_ino_lblk_32 hw_wrapped_key + +status=0 +exit diff --git a/tests/generic/901.out b/tests/generic/901.out new file mode 100644 index 00000000..2f928465 --- /dev/null +++ b/tests/generic/901.out @@ -0,0 +1,6 @@ +QA output created by 901 + +Verifying ciphertext with parameters: + contents_encryption_mode: AES-256-XTS + filenames_encryption_mode: AES-256-CTS-CBC + options: v2 iv_ino_lblk_32 hw_wrapped_key