Skip to contentSkip to conversation

xfstests: fscrypt test cleanups

PATCH New Eric Biggers 1 participant 6 messages Mar 13, 2022 fscrypt: file system level encryption support

lore
Share & tools
lore

Eric Biggers

author maintainer
#0 lore raw

v2 cover xfstests: fscrypt test cleanups

This series makes some minor improvements to the fscrypt ciphertext verification tests. I've split these out from my series "[RFC PATCH 0/8] xfstests: test the fscrypt hardware-wrapped key support"
(https://lore.kernel.org/fstests/[email protected]/T/#u)
and updated them slightly. These can be applied now.

I've verified that all the 'encrypt' group tests still pass with this series applied.

Eric Biggers (5):
fscrypt-crypt-util: use an explicit --direct-key option
fscrypt-crypt-util: refactor get_key_and_iv()
fscrypt-crypt-util: add support for dumping key identifier common/encrypt: log full ciphertext verification params common/encrypt: verify the key identifiers

 common/encrypt           |  34 +++++--
 src/fscrypt-crypt-util.c | 193 ++++++++++++++++++++++++++-------------
 2 files changed, 159 insertions(+), 68 deletions(-)
Signature · 1 line
2.35.1
author maintainer
#1 lore raw

PATCH v2 1/5 fscrypt-crypt-util: use an explicit --direct-key option

From: Eric Biggers <[email protected]>

Make fscrypt-crypt-util use an option --direct-key to specify the use of the DIRECT_KEY method for key derivation and IV generation. Previously, this method was implicitly detected via --mode-num being given without either --iv-ino-lblk-64 or --iv-ino-lblk-32, or --kdf=none being given in combination with --file-nonce.

The benefit of this change is that it makes the various options to fscrypt-crypt-util behave more consistently. --direct-key, --iv-ino-lblk-64, and --iv-ino-lblk-32 now all work similarly (they select a key derivation and IV generation method); likewise, --mode-num, --file-nonce, --inode-number, and --fs-uuid now all work similarly (they provide information that key derivation and IV generation may need).

---
 common/encrypt           | 10 ++++----
 src/fscrypt-crypt-util.c | 52 ++++++++++++++++++++++++----------------
 2 files changed, 36 insertions(+), 26 deletions(-)
common/encrypt +4 -6
index f90c4ef0..2cf02ca0 100644
@@ -842,27 +842,25 @@ _verify_ciphertext_for_encryption_policy()
 
 	set_encpolicy_args+=" -c $contents_mode_num"
 	set_encpolicy_args+=" -n $filenames_mode_num"
+	crypt_util_contents_args+=" --mode-num=$contents_mode_num"
+	crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
 
 	if (( policy_version > 1 )); then
 		set_encpolicy_args+=" -v 2"
 		crypt_util_args+=" --kdf=HKDF-SHA512"
 		if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
-			crypt_util_args+=" --mode-num=$contents_mode_num"
+			crypt_util_args+=" --direct-key"
 		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
 			crypt_util_args+=" --iv-ino-lblk-64"
-			crypt_util_contents_args+=" --mode-num=$contents_mode_num"
-			crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
 		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 )); then
 			crypt_util_args+=" --iv-ino-lblk-32"
-			crypt_util_contents_args+=" --mode-num=$contents_mode_num"
-			crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
 		fi
 	else
 		if (( policy_flags & ~FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
 			_fail "unsupported flags for v1 policy: $policy_flags"
 		fi
 		if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
-			crypt_util_args+=" --kdf=none"
+			crypt_util_args+=" --direct-key --kdf=none"
 		else
 			crypt_util_args+=" --kdf=AES-128-ECB"
 		fi
src/fscrypt-crypt-util.c +32 -20

138 lines not on this page · raw message

Signature · 1 line
2.35.1
author maintainer
#2 lore raw

PATCH v2 2/5 fscrypt-crypt-util: refactor get_key_and_iv()

In reply to Eric Biggers

From: Eric Biggers <[email protected]>

Split get_key_and_iv() into two distinct parts: (1) deriving the key and
(2) generating the IV. Also, check for the presence of needed options just before they are used rather than doing it all up-front.

These changes should make this code much easier to understand.

---
 src/fscrypt-crypt-util.c | 124 ++++++++++++++++++++++-----------------
 1 file changed, 70 insertions(+), 54 deletions(-)
src/fscrypt-crypt-util.c +70 -54
index e5992275..124eb23f 100644
@@ -1818,6 +1818,9 @@ static u32 hash_inode_number(const struct key_and_iv_params *params)
 	} hash_key;
 
 	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,
 		    NULL, 0, info, sizeof(info),
 		    hash_key.bytes, sizeof(hash_key));
@@ -1828,16 +1831,9 @@ static u32 hash_inode_number(const struct key_and_iv_params *params)
 	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
- * number or file nonce.  Otherwise, the master key is used directly.
- */
-static void get_key_and_iv(const struct key_and_iv_params *params,
-			   u8 *real_key, size_t real_key_size,
-			   union fscrypt_iv *iv)
+static void derive_real_key(const struct key_and_iv_params *params,
+			    u8 *real_key, size_t real_key_size)
 {
-	int iv_methods = 0;
 	struct aes_key aes_key;
 	u8 info[8 + 1 + 1 + UUID_SIZE] = "fscrypt";
 	size_t infolen = 8;
@@ -1845,41 +1841,6 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
 
 	ASSERT(real_key_size <= params->master_key_size);
 
-	memset(iv, 0, sizeof(*iv));
-
-	/* Overridden later for iv_ino_lblk_{64,32} */
-	iv->block_number = cpu_to_le64(params->block_number);
-
-	iv_methods += params->direct_key;
-	iv_methods += params->iv_ino_lblk_64;
-	iv_methods += params->iv_ino_lblk_32;
-	if (iv_methods > 1)
-		die("Conflicting IV methods specified");
-	if (iv_methods > 0 && params->kdf == KDF_AES_128_ECB)
-		die("--kdf=AES-128-ECB is incompatible with IV method options");
-
-	if (params->direct_key) {
-		if (!params->file_nonce_specified)
-			die("--direct-key requires --file-nonce");
-		if (params->kdf != KDF_NONE && params->mode_num == 0)
-			die("--direct-key with KDF requires --mode-num");
-	} else 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->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:
 		memcpy(real_key, params->master_key, real_key_size);
@@ -1896,31 +1857,35 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
 		break;
 	case KDF_HKDF_SHA512:
 		if (params->direct_key) {
+			if (params->mode_num == 0)
+				die("--direct-key with KDF requires --mode-num");
 			info[infolen++] = HKDF_CONTEXT_DIRECT_KEY;
 			info[infolen++] = params->mode_num;
 		} else if (params->iv_ino_lblk_64) {
+			if (params->mode_num == 0)
+				die("--iv-ino-lblk-64 with KDF requires --mode-num");
+			if (!params->fs_uuid_specified)
+				die("--iv-ino-lblk-64 with KDF requires --fs-uuid");
 			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) {
+			if (params->mode_num == 0)
+				die("--iv-ino-lblk-32 with KDF requires --mode-num");
+			if (!params->fs_uuid_specified)
+				die("--iv-ino-lblk-32 with KDF requires --fs-uuid");
 			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->file_nonce_specified) {
+		} else {
+			if (!params->file_nonce_specified)
+				die("--kdf=HKDF-SHA512 requires --file-nonce or --iv-ino-lblk-{64,32}");
 			info[infolen++] = HKDF_CONTEXT_PER_FILE_ENC_KEY;
 			memcpy(&info[infolen], params->file_nonce,
 			       FILE_NONCE_SIZE);
 			infolen += FILE_NONCE_SIZE;
-		} else {
-			die("--kdf=HKDF-SHA512 requires --file-nonce or --iv-ino-lblk-{64,32}");
 		}
 		hkdf_sha512(params->master_key, params->master_key_size,
 			    NULL, 0, info, infolen, real_key, real_key_size);
@@ -1928,9 +1893,60 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
 	default:
 		ASSERT(0);
 	}
+}
 
-	if (params->direct_key)
+static void generate_iv(const struct key_and_iv_params *params,
+			union fscrypt_iv *iv)
+{
+	memset(iv, 0, sizeof(*iv));
+	if (params->direct_key) {
+		if (!params->file_nonce_specified)
+			die("--direct-key requires --file-nonce");
+		iv->block_number = cpu_to_le64(params->block_number);
 		memcpy(iv->nonce, params->file_nonce, FILE_NONCE_SIZE);
+	} else if (params->iv_ino_lblk_64) {
+		if (params->block_number > UINT32_MAX)
+			die("iv-ino-lblk-64 can't use --block-number > UINT32_MAX");
+		if (params->inode_number == 0)
+			die("iv-ino-lblk-64 requires --inode-number");
+		if (params->inode_number > UINT32_MAX)
+			die("iv-ino-lblk-64 can't use --inode-number > UINT32_MAX");
+		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) {
+		if (params->block_number > UINT32_MAX)
+			die("iv-ino-lblk-32 can't use --block-number > UINT32_MAX");
+		if (params->inode_number == 0)
+			die("iv-ino-lblk-32 requires --inode-number");
+		iv->block_number32 = cpu_to_le32(hash_inode_number(params) +
+						 params->block_number);
+	} else {
+		iv->block_number = cpu_to_le64(params->block_number);
+	}
+}
+
+/*
+ * Get the key and starting IV with which the encryption will actually be done.
+ * If a KDF was specified, then a subkey is derived from the master key.
+ * Otherwise, the master key is used directly.
+ */
+static void get_key_and_iv(const struct key_and_iv_params *params,
+			   u8 *real_key, size_t real_key_size,
+			   union fscrypt_iv *iv)
+{
+	int iv_methods = 0;
+
+	iv_methods += params->direct_key;
+	iv_methods += params->iv_ino_lblk_64;
+	iv_methods += params->iv_ino_lblk_32;
+	if (iv_methods > 1)
+		die("Conflicting IV methods specified");
+	if (iv_methods > 0 && params->kdf == KDF_AES_128_ECB)
+		die("--kdf=AES-128-ECB is incompatible with IV method options");
+
+	derive_real_key(params, real_key, real_key_size);
+
+	generate_iv(params, iv);
 }
 
 enum {
Signature · 1 line
2.35.1
author maintainer
#3 lore raw

PATCH v2 3/5 fscrypt-crypt-util: add support for dumping key identifier

In reply to Eric Biggers

From: Eric Biggers <[email protected]>

Add an option to fscrypt-crypt-util to make it compute the key identifier for the given key. This will allow testing the correctness of the filesystem's key identifier computation.

---
 src/fscrypt-crypt-util.c | 51 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 46 insertions(+), 5 deletions(-)
src/fscrypt-crypt-util.c +46 -5
index 124eb23f..ffb9534d 100644
@@ -46,7 +46,7 @@
 static void usage(FILE *fp)
 {
 	fputs(
-"Usage: " PROGRAM_NAME " [OPTION]... CIPHER MASTER_KEY\n"
+"Usage: " PROGRAM_NAME " [OPTION]... [CIPHER | --dump-key-identifier] MASTER_KEY\n"
 "\n"
 "Utility for verifying fscrypt-encrypted data.  This program encrypts\n"
 "(or decrypts) the data on stdin using the given CIPHER with the given\n"
@@ -66,6 +66,8 @@ 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"
 "  --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"
@@ -1949,11 +1951,38 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
 	generate_iv(params, iv);
 }
 
+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->kdf != KDF_HKDF_SHA512)
+		die("--dump-key-identifier requires --kdf=HKDF-SHA512");
+	hkdf_sha512(params->master_key, params->master_key_size,
+		    NULL, 0, info, sizeof(info),
+		    key_identifier, sizeof(key_identifier));
+
+	for (i = 0; i < sizeof(key_identifier); i++)
+		printf("%02x", key_identifier[i]);
+}
+
+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);
+}
+
 enum {
 	OPT_BLOCK_NUMBER,
 	OPT_BLOCK_SIZE,
 	OPT_DECRYPT,
 	OPT_DIRECT_KEY,
+	OPT_DUMP_KEY_IDENTIFIER,
 	OPT_FILE_NONCE,
 	OPT_FS_UUID,
 	OPT_HELP,
@@ -1970,6 +1999,7 @@ static const struct option longopts[] = {
 	{ "block-size",      required_argument, NULL, OPT_BLOCK_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 },
 	{ "file-nonce",      required_argument, NULL, OPT_FILE_NONCE },
 	{ "fs-uuid",         required_argument, NULL, OPT_FS_UUID },
 	{ "help",            no_argument,       NULL, OPT_HELP },
@@ -1986,6 +2016,7 @@ int main(int argc, char *argv[])
 {
 	size_t block_size = 4096;
 	bool decrypting = false;
+	bool dump_key_identifier = false;
 	struct key_and_iv_params params;
 	size_t padding = 0;
 	const struct fscrypt_cipher *cipher;
@@ -2027,6 +2058,9 @@ int main(int argc, char *argv[])
 		case OPT_DIRECT_KEY:
 			params.direct_key = true;
 			break;
+		case OPT_DUMP_KEY_IDENTIFIER:
+			dump_key_identifier = true;
+			break;
 		case OPT_FILE_NONCE:
 			if (hex2bin(optarg, params.file_nonce, FILE_NONCE_SIZE)
 			    != FILE_NONCE_SIZE)
@@ -2074,6 +2108,15 @@ int main(int argc, char *argv[])
 	argc -= optind;
 	argv += optind;
 
+	if (dump_key_identifier) {
+		if (argc != 1) {
+			usage(stderr);
+			return 2;
+		}
+		parse_master_key(argv[0], &params);
+		do_dump_key_identifier(&params);
+		return 0;
+	}
 	if (argc != 2) {
 		usage(stderr);
 		return 2;
@@ -2087,10 +2130,8 @@ int main(int argc, char *argv[])
 		die("Block size of %zu bytes is too small for cipher %s",
 		    block_size, cipher->name);
 
-	params.master_key_size = hex2bin(argv[1], params.master_key,
-					 MAX_KEY_SIZE);
-	if (params.master_key_size < 0)
-		die("Invalid master_key: %s", argv[1]);
+	parse_master_key(argv[1], &params);
+
 	if (params.master_key_size < cipher->keysize)
 		die("Master key is too short for cipher %s", cipher->name);
 
Signature · 1 line
2.35.1
author maintainer
#4 lore raw

PATCH v2 4/5 common/encrypt: log full ciphertext verification params

In reply to Eric Biggers

From: Eric Biggers <[email protected]>

To help with debugging, log some additional information to $seqres.full.

---
 common/encrypt | 11 +++++++++++
 1 file changed, 11 insertions(+)
common/encrypt +11 -0
index 2cf02ca0..cf402570 100644
@@ -908,6 +908,17 @@ _verify_ciphertext_for_encryption_policy()
 	echo -e "\tfilenames_encryption_mode: $filenames_encryption_mode"
 	[ $# -ne 0 ] && echo -e "\toptions: $*"
 
+	cat >> $seqres.full <<EOF
+Full ciphertext verification parameters:
+  contents_encryption_mode = $contents_encryption_mode
+  filenames_encryption_mode = $filenames_encryption_mode
+  policy_flags = $policy_flags
+  set_encpolicy_args = $set_encpolicy_args
+  keyspec = $keyspec
+  raw_key_hex = $raw_key_hex
+  crypt_util_contents_args = $crypt_util_contents_args
+  crypt_util_filename_args = $crypt_util_filename_args
+EOF
 	_do_verify_ciphertext_for_encryption_policy \
 		"$contents_encryption_mode" \
 		"$filenames_encryption_mode" \
Signature · 1 line
2.35.1
author maintainer
#5 lore raw

PATCH v2 5/5 common/encrypt: verify the key identifiers

In reply to Eric Biggers

From: Eric Biggers <[email protected]>

As part of all the ciphertext verification tests, verify that the filesystem correctly computed the key identifier from the key the test generated. This uses fscrypt-crypt-util to compute the key identifier.

Previously this was only being tested indirectly, via the tests that happen to use the hardcoded $TEST_RAW_KEY and $TEST_KEY_IDENTIFIER. The new check provides better coverage.

---
 common/encrypt | 13 +++++++++++++
 1 file changed, 13 insertions(+)
common/encrypt +13 -0
index cf402570..78a574bd 100644
@@ -812,6 +812,7 @@ _verify_ciphertext_for_encryption_policy()
 	local crypt_util_args=""
 	local crypt_util_contents_args=""
 	local crypt_util_filename_args=""
+	local expected_identifier
 
 	shift 2
 	for opt; do
@@ -902,6 +903,18 @@ _verify_ciphertext_for_encryption_policy()
 	fi
 	local raw_key_hex=$(echo "$raw_key" | tr -d '\\x')
 
+	if (( policy_version > 1 )); then
+		echo "Verifying key identifier" >> $seqres.full
+		expected_identifier=$($here/src/fscrypt-crypt-util  \
+				      --dump-key-identifier "$raw_key_hex" \
+				      $crypt_util_args)
+		if [ "$expected_identifier" != "$keyspec" ]; then
+			echo "KEY IDENTIFIER MISMATCH!"
+			echo "    Expected: $expected_identifier"
+			echo "    Actual: $keyspec"
+		fi
+	fi
+
 	echo
 	echo -e "Verifying ciphertext with parameters:"
 	echo -e "\tcontents_encryption_mode: $contents_encryption_mode"
Signature · 1 line
2.35.1