generic: verify ciphertext of IV_INO_LBLK_32 encryption policies
authorEric Biggers <ebiggers@google.com>
Thu, 4 Jun 2020 02:25:01 +0000 (19:25 -0700)
committerEryu Guan <guaneryu@gmail.com>
Sun, 21 Jun 2020 14:21:29 +0000 (22:21 +0800)
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@google.com>
common/encrypt
src/fscrypt-crypt-util.c
tests/generic/602 [new file with mode: 0755]
tests/generic/602.out [new file with mode: 0644]
tests/generic/group

index 5695a12307e64237ecb75614bba8e7260e207fc0..c4cc2d835ffad669cdb6cb8112dd8da1f4f33dff 100644 (file)
@@ -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
index 1bf8f95c20662e1c3e9330cb1de9fcc5b0b989cb..ce9da85d6280f4911305335571befe0876c91597 100644 (file)
@@ -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/602 b/tests/generic/602
new file mode 100755 (executable)
index 0000000..ac8f2c7
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2020 Google LLC
+#
+# FS QA Test No. 602
+#
+# 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/602.out b/tests/generic/602.out
new file mode 100644 (file)
index 0000000..697d3d3
--- /dev/null
@@ -0,0 +1,6 @@
+QA output created by 602
+
+Verifying ciphertext with parameters:
+       contents_encryption_mode: AES-256-XTS
+       filenames_encryption_mode: AES-256-CTS-CBC
+       options: v2 iv_ino_lblk_32
index c6ce029cb82a1a168f764731f69d77684c657841..d9ab9a31f2ed1daffc2a727424def903e4096e82 100644 (file)
 599 auto quick remount shutdown
 600 auto quick quota
 601 auto quick quota
+602 auto quick encrypt