generic: test adding filesystem-level fscrypt key via key_id
authorEric Biggers <ebiggers@google.com>
Mon, 3 Feb 2020 18:18:55 +0000 (10:18 -0800)
committerEryu Guan <guaneryu@gmail.com>
Mon, 17 Feb 2020 13:05:00 +0000 (21:05 +0800)
Add a test which tests adding a key to a filesystem's fscrypt keyring
via an "fscrypt-provisioning" keyring key.  This is an alternative to
the normal method where the raw key is given directly.

For more details, see kernel commit 93edd392cad7 ("fscrypt: support
passing a keyring key to FS_IOC_ADD_ENCRYPTION_KEY").

This test depends on an xfs_io patch which adds the '-k' option to the
'add_enckey' command, e.g.:

xfs_io -c "add_enckey -k KEY_ID" MOUNTPOINT

This test is skipped if the needed kernel or xfs_io support is absent.

This has been tested on ext4, f2fs, and ubifs.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
common/encrypt
tests/generic/593 [new file with mode: 0755]
tests/generic/593.out [new file with mode: 0644]
tests/generic/group

index 98a407ce31f42728b13999b1b2b17879bb74d8fa..5695a12307e64237ecb75614bba8e7260e207fc0 100644 (file)
@@ -227,6 +227,28 @@ _generate_raw_encryption_key()
        echo $raw
 }
 
        echo $raw
 }
 
+# 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()
+{
+       local value=$1
+       local nbytes=$2
+       local i
+       local big_endian=$(echo -ne '\x11' | od -tx2 | head -1 | \
+                          cut -f2 -d' ' | cut -c1)
+
+       if (( big_endian )); then
+               for (( i = 0; i < nbytes; i++ )); do
+                       printf '\\x%02x' $(((value >> (8*(nbytes-1-i))) & 0xff))
+               done
+       else
+               for (( i = 0; i < nbytes; i++ )); do
+                       printf '\\x%02x' $(((value >> (8*i)) & 0xff))
+               done
+       fi
+}
+
 # Add the specified raw encryption key to the session keyring, using the
 # specified key descriptor.
 _add_session_encryption_key()
 # Add the specified raw encryption key to the session keyring, using the
 # specified key descriptor.
 _add_session_encryption_key()
@@ -237,12 +259,12 @@ _add_session_encryption_key()
        #
        # Add the key to the session keyring.  The required structure is:
        #
        #
        # Add the key to the session keyring.  The required structure is:
        #
-       #       #define FS_MAX_KEY_SIZE 64
+       #       #define FSCRYPT_MAX_KEY_SIZE 64
        #       struct fscrypt_key {
        #       struct fscrypt_key {
-       #               u32 mode;
-       #               u8 raw[FS_MAX_KEY_SIZE];
-       #               u32 size;
-       #       } __packed;
+       #               __u32 mode;
+       #               __u8 raw[FSCRYPT_MAX_KEY_SIZE];
+       #               __u32 size;
+       #       };
        #
        # The kernel ignores 'mode' but requires that 'size' be 64.
        #
        #
        # The kernel ignores 'mode' but requires that 'size' be 64.
        #
@@ -253,15 +275,8 @@ _add_session_encryption_key()
        # nice to use the common key prefix, but for now use the filesystem-
        # specific prefix to make it possible to test older kernels...
        #
        # nice to use the common key prefix, but for now use the filesystem-
        # specific prefix to make it possible to test older kernels...
        #
-       local big_endian=$(echo -ne '\x11' | od -tx2 | head -1 | \
-                          cut -f2 -d' ' | cut -c1 )
-       if (( big_endian )); then
-               local mode='\x00\x00\x00\x00'
-               local size='\x00\x00\x00\x40'
-       else
-               local mode='\x00\x00\x00\x00'
-               local size='\x40\x00\x00\x00'
-       fi
+       local mode=$(_num_to_hex 0 4)
+       local size=$(_num_to_hex 64 4)
        echo -n -e "${mode}${raw}${size}" |
                $KEYCTL_PROG padd logon $FSTYP:$keydesc @s >>$seqres.full
 }
        echo -n -e "${mode}${raw}${size}" |
                $KEYCTL_PROG padd logon $FSTYP:$keydesc @s >>$seqres.full
 }
@@ -389,6 +404,44 @@ _user_do_enckey_status()
        _user_do "$XFS_IO_PROG -c \"enckey_status $* $keyspec\" \"$mnt\""
 }
 
        _user_do "$XFS_IO_PROG -c \"enckey_status $* $keyspec\" \"$mnt\""
 }
 
+# Require support for adding a key to a filesystem's fscrypt keyring via an
+# "fscrypt-provisioning" keyring key.
+_require_add_enckey_by_key_id()
+{
+       local mnt=$1
+
+       # Userspace support
+       _require_xfs_io_command "add_enckey" "-k"
+
+       # Kernel support
+       if $XFS_IO_PROG -c "add_enckey -k 12345" "$mnt" \
+               |& grep -q 'Invalid argument'; then
+               _notrun "Kernel doesn't support key_id parameter to FS_IOC_ADD_ENCRYPTION_KEY"
+       fi
+}
+
+# Add a key of type "fscrypt-provisioning" to the session keyring and print the
+# resulting key ID.
+_add_fscrypt_provisioning_key()
+{
+       local desc=$1
+       local type=$2
+       local raw=$3
+
+       # The format of the key payload must be:
+       #
+       #       struct fscrypt_provisioning_key_payload {
+       #               __u32 type;
+       #               __u32 __reserved;
+       #               __u8 raw[];
+       #       };
+       #
+       local type_hex=$(_num_to_hex $type 4)
+       local reserved=$(_num_to_hex 0 4)
+       echo -n -e "${type_hex}${reserved}${raw}" |
+               $KEYCTL_PROG padd fscrypt-provisioning "$desc" @s
+}
+
 # Retrieve the encryption nonce of the given inode as a hex string.  The nonce
 # was randomly generated by the filesystem and isn't exposed directly to
 # userspace.  But it can be read using the filesystem's debugging tools.
 # Retrieve the encryption nonce of the given inode as a hex string.  The nonce
 # was randomly generated by the filesystem and isn't exposed directly to
 # userspace.  But it can be read using the filesystem's debugging tools.
@@ -717,6 +770,9 @@ FSCRYPT_MODE_ADIANTUM=9
 FSCRYPT_POLICY_FLAG_DIRECT_KEY=0x04
 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64=0x08
 
 FSCRYPT_POLICY_FLAG_DIRECT_KEY=0x04
 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64=0x08
 
+FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR=1
+FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER=2
+
 _fscrypt_mode_name_to_num()
 {
        local name=$1
 _fscrypt_mode_name_to_num()
 {
        local name=$1
diff --git a/tests/generic/593 b/tests/generic/593
new file mode 100755 (executable)
index 0000000..042f515
--- /dev/null
@@ -0,0 +1,155 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2019 Google LLC
+#
+# FS QA Test No. 593
+#
+# Test adding a key to a filesystem's fscrypt keyring via an
+# "fscrypt-provisioning" keyring key.  This is an alternative to the normal
+# method where the raw key is given directly.
+#
+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
+_require_scratch_encryption -v 2
+_require_command "$KEYCTL_PROG" keyctl
+
+_new_session_keyring
+_scratch_mkfs_encrypted &>> $seqres.full
+_scratch_mount
+_require_add_enckey_by_key_id $SCRATCH_MNT
+
+test_with_policy_version()
+{
+       local vers=$1
+       local dir=$SCRATCH_MNT/dir
+       local keyid
+
+       echo
+       echo "# =========================="
+       echo "# Test with policy version $vers"
+       echo "# =========================="
+
+       case $vers in
+       1)
+               local keytype=$FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR
+               local keyspec=$TEST_KEY_DESCRIPTOR
+               local add_enckey_args="-d $TEST_KEY_DESCRIPTOR"
+               ;;
+       2)
+               local keytype=$FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER
+               local keyspec=$TEST_KEY_IDENTIFIER
+               local add_enckey_args=""
+               ;;
+       *)
+               _fail "Unknown policy version: $vers"
+               ;;
+       esac
+
+       # First add the key in the regular way (raw key given directly), create
+       # an encrypted file with some contents, and remove the key.  After this,
+       # the encrypted file should no longer be readable.
+
+       echo -e "\n# Adding key to filesystem"
+       _add_enckey $SCRATCH_MNT "$TEST_RAW_KEY" $add_enckey_args
+
+       echo -e "\n# Creating encrypted file"
+       mkdir $dir
+       _set_encpolicy $dir $keyspec
+       echo "contents" > $dir/file
+
+       echo -e "\n# Removing key from filesystem"
+       _rm_enckey $SCRATCH_MNT $keyspec
+       cat $dir/file |& _filter_scratch
+
+       # Now we should be able to add the key back via an fscrypt-provisioning
+       # key which contains the raw key, instead of providing the raw key
+       # directly.  After this, the encrypted file should be readable again.
+
+       echo -e "\n# Adding fscrypt-provisioning key"
+       keyid=$(_add_fscrypt_provisioning_key $keyspec $keytype "$TEST_RAW_KEY")
+
+       echo -e "\n# Adding key to filesystem via fscrypt-provisioning key"
+       $XFS_IO_PROG -c "add_enckey -k $keyid $add_enckey_args" $SCRATCH_MNT
+
+       echo -e "\n# Reading encrypted file"
+       cat $dir/file
+
+       echo -e "\n# Cleaning up"
+       rm -rf $dir
+       _scratch_cycle_mount    # Clear all keys
+}
+
+# Test with both v1 and v2 encryption policies.
+test_with_policy_version 1
+test_with_policy_version 2
+
+# Now test that invalid fscrypt-provisioning keys can't be created, that
+# fscrypt-provisioning keys can't be read back by userspace, and that the
+# filesystem only accepts properly matching fscrypt-provisioning keys.
+echo
+echo "# ================"
+echo "# Validation tests"
+echo "# ================"
+
+echo -e "\n# Adding an invalid fscrypt-provisioning key fails"
+echo "# ... bad type"
+_add_fscrypt_provisioning_key desc 0 "$TEST_RAW_KEY"
+echo "# ... bad type"
+_add_fscrypt_provisioning_key desc 10000 "$TEST_RAW_KEY"
+echo "# ... raw key too small"
+_add_fscrypt_provisioning_key desc $FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR ""
+echo "# ... raw key too large"
+_add_fscrypt_provisioning_key desc $FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR \
+       "$TEST_RAW_KEY$TEST_RAW_KEY"
+
+echo -e "\n# keyctl_read() doesn't work on fscrypt-provisioning keys"
+keyid=$(_add_fscrypt_provisioning_key desc $FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR \
+       "$TEST_RAW_KEY")
+$KEYCTL_PROG read $keyid
+$KEYCTL_PROG unlink $keyid @s
+
+echo -e "\n# Only keys with the correct fscrypt_provisioning_key_payload::type field can be added"
+echo "# ... keyring key is v1, filesystem wants v2 key"
+keyid=$(_add_fscrypt_provisioning_key desc $FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR \
+       "$TEST_RAW_KEY")
+$XFS_IO_PROG -c "add_enckey -k $keyid" $SCRATCH_MNT
+$KEYCTL_PROG unlink $keyid @s
+
+echo "# ... keyring key is v2, filesystem wants v1 key"
+keyid=$(_add_fscrypt_provisioning_key desc $FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER \
+       "$TEST_RAW_KEY")
+$XFS_IO_PROG -c "add_enckey -k $keyid -d $TEST_KEY_DESCRIPTOR" $SCRATCH_MNT
+$KEYCTL_PROG unlink $keyid @s
+
+echo -e "\n# Only keys of type fscrypt-provisioning can be added"
+keyid=$(head -c 64 /dev/urandom | $KEYCTL_PROG padd logon foo:desc @s)
+$XFS_IO_PROG -c "add_enckey -k $keyid" $SCRATCH_MNT
+$KEYCTL_PROG unlink $keyid @s
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/593.out b/tests/generic/593.out
new file mode 100644 (file)
index 0000000..904d666
--- /dev/null
@@ -0,0 +1,73 @@
+QA output created by 593
+
+# ==========================
+# Test with policy version 1
+# ==========================
+
+# Adding key to filesystem
+Added encryption key with descriptor 0000111122223333
+
+# Creating encrypted file
+
+# Removing key from filesystem
+Removed encryption key with descriptor 0000111122223333
+cat: SCRATCH_MNT/dir/file: No such file or directory
+
+# Adding fscrypt-provisioning key
+
+# Adding key to filesystem via fscrypt-provisioning key
+Added encryption key with descriptor 0000111122223333
+
+# Reading encrypted file
+contents
+
+# Cleaning up
+
+# ==========================
+# Test with policy version 2
+# ==========================
+
+# Adding key to filesystem
+Added encryption key with identifier 69b2f6edeee720cce0577937eb8a6751
+
+# Creating encrypted file
+
+# Removing key from filesystem
+Removed encryption key with identifier 69b2f6edeee720cce0577937eb8a6751
+cat: SCRATCH_MNT/dir/file: No such file or directory
+
+# Adding fscrypt-provisioning key
+
+# Adding key to filesystem via fscrypt-provisioning key
+Added encryption key with identifier 69b2f6edeee720cce0577937eb8a6751
+
+# Reading encrypted file
+contents
+
+# Cleaning up
+
+# ================
+# Validation tests
+# ================
+
+# Adding an invalid fscrypt-provisioning key fails
+# ... bad type
+add_key: Invalid argument
+# ... bad type
+add_key: Invalid argument
+# ... raw key too small
+add_key: Invalid argument
+# ... raw key too large
+add_key: Invalid argument
+
+# keyctl_read() doesn't work on fscrypt-provisioning keys
+keyctl_read_alloc: Operation not supported
+
+# Only keys with the correct fscrypt_provisioning_key_payload::type field can be added
+# ... keyring key is v1, filesystem wants v2 key
+Error adding encryption key: Key was rejected by service
+# ... keyring key is v2, filesystem wants v1 key
+Error adding encryption key: Key was rejected by service
+
+# Only keys of type fscrypt-provisioning can be added
+Error adding encryption key: Key was rejected by service
index 6fe62505e87746127df1201436730a6768d6216a..32772e27f852214b2b67b32dcfdfa1e3152de7b7 100644 (file)
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
 590 auto prealloc preallocrw
 591 auto quick rw pipe splice
 592 auto quick encrypt
+593 auto quick encrypt