[<prev] [next>] [day] [month] [year] [list]
Message-Id: <20200604022501.425267-1-ebiggers@kernel.org>
Date: Wed, 3 Jun 2020 19:25:01 -0700
From: Eric Biggers <ebiggers@...nel.org>
To: fstests@...r.kernel.org
Cc: linux-fscrypt@...r.kernel.org, linux-ext4@...r.kernel.org,
linux-f2fs-devel@...ts.sourceforge.net
Subject: [xfstests PATCH] generic: verify ciphertext of IV_INO_LBLK_32 encryption policies
From: Eric Biggers <ebiggers@...gle.com>
Verify the ciphertext for v2 encryption policies that use the
IV_INO_LBLK_32 flag and that use AES-256-XTS to encrypt file contents
and AES-256-CTS-CBC to encrypt file names.
The IV_INO_LBLK_32 encryption policy flag modifies the IV generation and
key derivation to be optimized for use with inline encryption hardware
that only accepts 32-bit IVs. It is similar to IV_INO_LBLK_64 (which is
tested by generic/592), but it uses a trick to get the IV down to 32
bits. For more information, see kernel commit e3b1078bedd3 ("fscrypt:
add support for IV_INO_LBLK_32 policies").
This test required adding SipHash support to fscrypt-crypt-util.
Running this test requires a kernel containing the above commit, e.g.
the latest mainline (which will become v5.8 and later). For ext4, it
also needs an e2fsprogs version that supports the stable_inodes feature,
e.g. the latest git master branch (which will become v1.46 and later).
Signed-off-by: Eric Biggers <ebiggers@...gle.com>
---
common/encrypt | 18 ++++--
src/fscrypt-crypt-util.c | 121 ++++++++++++++++++++++++++++++++++-----
tests/generic/900 | 43 ++++++++++++++
tests/generic/900.out | 6 ++
tests/generic/group | 1 +
5 files changed, 171 insertions(+), 18 deletions(-)
create mode 100755 tests/generic/900
create mode 100644 tests/generic/900.out
diff --git a/common/encrypt b/common/encrypt
index 5695a123..c4cc2d83 100644
--- a/common/encrypt
+++ b/common/encrypt
@@ -97,7 +97,8 @@ _require_encryption_policy_support()
echo "Checking whether kernel supports encryption policy: $set_encpolicy_args" \
>> $seqres.full
- if (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
+ if (( policy_flags & (FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 |
+ FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) )); then
_scratch_unmount
_scratch_mkfs_stable_inodes_encrypted &>> $seqres.full
_scratch_mount
@@ -769,6 +770,7 @@ FSCRYPT_MODE_ADIANTUM=9
FSCRYPT_POLICY_FLAG_DIRECT_KEY=0x04
FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64=0x08
+FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32=0x10
FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR=1
FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER=2
@@ -797,6 +799,7 @@ _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
#
_verify_ciphertext_for_encryption_policy()
{
@@ -826,6 +829,9 @@ _verify_ciphertext_for_encryption_policy()
iv_ino_lblk_64)
(( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 ))
;;
+ iv_ino_lblk_32)
+ (( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 ))
+ ;;
*)
_fail "Unknown option '$opt' passed to ${FUNCNAME[0]}"
;;
@@ -841,14 +847,15 @@ _verify_ciphertext_for_encryption_policy()
set_encpolicy_args+=" -v 2"
crypt_util_args+=" --kdf=HKDF-SHA512"
if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
- if (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
- _fail "'direct' and 'iv_ino_lblk_64' options are mutually exclusive"
- fi
crypt_util_args+=" --mode-num=$contents_mode_num"
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
@@ -872,7 +879,8 @@ _verify_ciphertext_for_encryption_policy()
fi
echo "Creating encryption-capable filesystem" >> $seqres.full
- if (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
+ if (( policy_flags & (FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 |
+ FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) )); then
_scratch_mkfs_stable_inodes_encrypted &>> $seqres.full
else
_scratch_mkfs_encrypted &>> $seqres.full
diff --git a/src/fscrypt-crypt-util.c b/src/fscrypt-crypt-util.c
index 1bf8f95c..ce9da85d 100644
--- a/src/fscrypt-crypt-util.c
+++ b/src/fscrypt-crypt-util.c
@@ -63,10 +63,14 @@ static void usage(FILE *fp)
" --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"
-" Only required for --iv-ino-lblk-64.\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. Only required for\n"
-" --iv-ino-lblk-64.\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"
@@ -143,6 +147,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));
@@ -1579,6 +1588,50 @@ 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 *
*----------------------------------------------------------------------------*/
@@ -1723,15 +1776,39 @@ struct key_and_iv_params {
u8 file_nonce[FILE_NONCE_SIZE];
bool file_nonce_specified;
bool iv_ino_lblk_64;
+ bool iv_ino_lblk_32;
u32 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_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.
@@ -1752,8 +1829,20 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
memset(iv, 0, sizeof(*iv));
- if (params->iv_ino_lblk_64 && params->kdf != KDF_HKDF_SHA512)
- die("--iv-ino-lblk-64 requires --kdf=HKDF-SHA512");
+ 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);
+ }
switch (params->kdf) {
case KDF_NONE:
@@ -1776,23 +1865,24 @@ static void get_key_and_iv(const struct key_and_iv_params *params,
break;
case KDF_HKDF_SHA512:
if (params->iv_ino_lblk_64) {
- if (!params->fs_uuid_specified)
- die("--iv-ino-lblk-64 requires --fs-uuid");
- if (params->inode_number == 0)
- die("--iv-ino-lblk-64 requires --inode-number");
- if (params->mode_num == 0)
- die("--iv-ino-lblk-64 requires --mode-num");
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;
put_unaligned_le32(params->inode_number, &iv->bytes[4]);
+ } 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;
+ put_unaligned_le32(hash_inode_number(params),
+ iv->bytes);
} 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;
@@ -1817,6 +1907,7 @@ enum {
OPT_FS_UUID,
OPT_HELP,
OPT_INODE_NUMBER,
+ OPT_IV_INO_LBLK_32,
OPT_IV_INO_LBLK_64,
OPT_KDF,
OPT_MODE_NUM,
@@ -1830,6 +1921,7 @@ static const struct option longopts[] = {
{ "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 },
@@ -1890,6 +1982,9 @@ int main(int argc, char *argv[])
case OPT_INODE_NUMBER:
params.inode_number = parse_inode_number(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;
diff --git a/tests/generic/900 b/tests/generic/900
new file mode 100755
index 00000000..dc2a2225
--- /dev/null
+++ b/tests/generic/900
@@ -0,0 +1,43 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2020 Google LLC
+#
+# FS QA Test No. 900
+#
+# Verify ciphertext for v2 encryption policies that use the IV_INO_LBLK_32 flag
+# and use AES-256-XTS to encrypt file contents and AES-256-CTS-CBC to encrypt
+# file names.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1 # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+ cd /
+ rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/encrypt
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+_supported_fs generic
+_supported_os Linux
+
+_verify_ciphertext_for_encryption_policy AES-256-XTS AES-256-CTS-CBC \
+ v2 iv_ino_lblk_32
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/900.out b/tests/generic/900.out
new file mode 100644
index 00000000..8fbb34cc
--- /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_32
diff --git a/tests/generic/group b/tests/generic/group
index c6ce029c..d3501ccc 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -604,3 +604,4 @@
599 auto quick remount shutdown
600 auto quick quota
601 auto quick quota
+900 auto quick encrypt
--
2.26.2
Powered by blists - more mailing lists