generic: test adding filesystem-level fscrypt key via key_id

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>
This commit is contained in:
Eric Biggers
2020-02-03 10:18:55 -08:00
committed by Eryu Guan
parent 0d9ca511e1
commit 0ea2b67b09
4 changed files with 299 additions and 14 deletions
+70 -14
View 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 # Add the specified raw encryption key to the session keyring, using the
# specified key descriptor. # specified key descriptor.
_add_session_encryption_key() _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; # __u32 mode;
# u8 raw[FS_MAX_KEY_SIZE]; # __u8 raw[FSCRYPT_MAX_KEY_SIZE];
# u32 size; # __u32 size;
# } __packed; # };
# #
# 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- # nice to use the common key prefix, but for now use the filesystem-
# specific prefix to make it possible to test older kernels... # specific prefix to make it possible to test older kernels...
# #
local big_endian=$(echo -ne '\x11' | od -tx2 | head -1 | \ local mode=$(_num_to_hex 0 4)
cut -f2 -d' ' | cut -c1 ) local size=$(_num_to_hex 64 4)
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
echo -n -e "${mode}${raw}${size}" | echo -n -e "${mode}${raw}${size}" |
$KEYCTL_PROG padd logon $FSTYP:$keydesc @s >>$seqres.full $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 # 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 # was randomly generated by the filesystem and isn't exposed directly to
# userspace. But it can be read using the filesystem's debugging tools. # 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_DIRECT_KEY=0x04
FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64=0x08 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() _fscrypt_mode_name_to_num()
{ {
local name=$1 local name=$1
+155
View File
@@ -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
+73
View File
@@ -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
+1
View File
@@ -595,3 +595,4 @@
590 auto prealloc preallocrw 590 auto prealloc preallocrw
591 auto quick rw pipe splice 591 auto quick rw pipe splice
592 auto quick encrypt 592 auto quick encrypt
593 auto quick encrypt