mirror of
https://github.com/linux-apfs/apfstests.git
synced 2026-05-01 15:01:44 -07:00
f78b6daf26
Update _verify_ciphertext_for_encryption_policy() to support v2 encryption policies. This also required adding HKDF-SHA512 support to fscrypt-crypt-util. Signed-off-by: Eric Biggers <ebiggers@google.com> Reviewed-by: Eryu Guan <guaneryu@gmail.com> Signed-off-by: Eryu Guan <guaneryu@gmail.com>
776 lines
22 KiB
Plaintext
776 lines
22 KiB
Plaintext
##/bin/bash
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
# Copyright (c) 2016 Google, Inc. All Rights Reserved.
|
|
#
|
|
# Functions for setting up and testing file encryption
|
|
|
|
#
|
|
# _require_scratch_encryption [-c CONTENTS_MODE] [-n FILENAMES_MODE]
|
|
# [-v POLICY_VERSION]
|
|
#
|
|
# Require encryption support on the scratch device.
|
|
#
|
|
# This checks for support for the default type of encryption policy (v1 with
|
|
# AES-256-XTS and AES-256-CTS). Options can be specified to also require
|
|
# support for a different type of encryption policy.
|
|
#
|
|
_require_scratch_encryption()
|
|
{
|
|
_require_scratch
|
|
|
|
_require_xfs_io_command "set_encpolicy"
|
|
|
|
# The 'test_dummy_encryption' mount option interferes with trying to use
|
|
# encryption for real, even if we are just trying to get/set policies
|
|
# and never put any keys in the keyring. So skip the real encryption
|
|
# tests if the 'test_dummy_encryption' mount option was specified.
|
|
_exclude_scratch_mount_option "test_dummy_encryption"
|
|
|
|
# Make a filesystem on the scratch device with the encryption feature
|
|
# enabled. If this fails then probably the userspace tools (e.g.
|
|
# e2fsprogs or f2fs-tools) are too old to understand encryption.
|
|
if ! _scratch_mkfs_encrypted &>>$seqres.full; then
|
|
_notrun "$FSTYP userspace tools do not support encryption"
|
|
fi
|
|
|
|
# Try to mount the filesystem. If this fails then either the kernel
|
|
# isn't aware of encryption, or the mkfs options were not compatible
|
|
# with encryption (e.g. ext4 with block size != PAGE_SIZE).
|
|
if ! _try_scratch_mount &>>$seqres.full; then
|
|
_notrun "kernel is unaware of $FSTYP encryption feature," \
|
|
"or mkfs options are not compatible with encryption"
|
|
fi
|
|
|
|
# The kernel may be aware of encryption without supporting it. For
|
|
# example, for ext4 this is the case with kernels configured with
|
|
# CONFIG_EXT4_FS_ENCRYPTION=n. Detect support for encryption by trying
|
|
# to set an encryption policy. (For ext4 we could instead check for the
|
|
# presence of /sys/fs/ext4/features/encryption, but this is broken on
|
|
# some older kernels and is ext4-specific anyway.)
|
|
mkdir $SCRATCH_MNT/tmpdir
|
|
if _set_encpolicy $SCRATCH_MNT/tmpdir 2>&1 >>$seqres.full | \
|
|
egrep -q 'Inappropriate ioctl for device|Operation not supported'
|
|
then
|
|
_notrun "kernel does not support $FSTYP encryption"
|
|
fi
|
|
rmdir $SCRATCH_MNT/tmpdir
|
|
|
|
# If required, check for support for the specific type of encryption
|
|
# policy required by the test.
|
|
if [ $# -ne 0 ]; then
|
|
_require_encryption_policy_support $SCRATCH_MNT "$@"
|
|
fi
|
|
|
|
_scratch_unmount
|
|
}
|
|
|
|
_require_encryption_policy_support()
|
|
{
|
|
local mnt=$1
|
|
local dir=$mnt/tmpdir
|
|
local set_encpolicy_args=""
|
|
local policy_version=1
|
|
local c
|
|
|
|
OPTIND=2
|
|
while getopts "c:n:v:" c; do
|
|
case $c in
|
|
c|n)
|
|
set_encpolicy_args+=" -$c $OPTARG"
|
|
;;
|
|
v)
|
|
set_encpolicy_args+=" -$c $OPTARG"
|
|
policy_version=$OPTARG
|
|
;;
|
|
*)
|
|
_fail "Unrecognized option '$c'"
|
|
;;
|
|
esac
|
|
done
|
|
set_encpolicy_args=${set_encpolicy_args# }
|
|
|
|
echo "Checking whether kernel supports encryption policy: $set_encpolicy_args" \
|
|
>> $seqres.full
|
|
|
|
mkdir $dir
|
|
if (( policy_version > 1 )); then
|
|
_require_xfs_io_command "get_encpolicy" "-t"
|
|
local output=$(_get_encpolicy $dir -t)
|
|
if [ "$output" != "supported" ]; then
|
|
if [ "$output" = "unsupported" ]; then
|
|
_notrun "kernel does not support $FSTYP encryption v2 API"
|
|
fi
|
|
_fail "Unexpected output from 'get_encpolicy -t': $output"
|
|
fi
|
|
# Both the kernel and xfs_io support v2 encryption policies, and
|
|
# therefore also filesystem-level keys -- since that's the only
|
|
# way to provide keys for v2 policies.
|
|
local raw_key=$(_generate_raw_encryption_key)
|
|
local keyspec=$(_add_enckey $mnt "$raw_key" | awk '{print $NF}')
|
|
else
|
|
_require_command "$KEYCTL_PROG" keyctl
|
|
_new_session_keyring
|
|
local keyspec=$(_generate_session_encryption_key)
|
|
fi
|
|
if _set_encpolicy $dir $keyspec $set_encpolicy_args \
|
|
2>&1 >>$seqres.full | egrep -q 'Invalid argument'; then
|
|
_notrun "kernel does not support encryption policy: '$set_encpolicy_args'"
|
|
fi
|
|
# fscrypt allows setting policies with modes it knows about, even
|
|
# without kernel crypto API support. E.g. a policy using Adiantum
|
|
# encryption can be set on a kernel without CONFIG_CRYPTO_ADIANTUM.
|
|
# But actually trying to use such an encrypted directory will fail.
|
|
# To reliably check for availability of both the contents and filenames
|
|
# encryption modes, try creating a nonempty file.
|
|
if ! echo foo > $dir/file; then
|
|
_notrun "encryption policy '$set_encpolicy_args' is unusable; probably missing kernel crypto API support"
|
|
fi
|
|
if (( policy_version <= 1 )); then
|
|
$KEYCTL_PROG clear @s
|
|
fi
|
|
rm -r $dir
|
|
}
|
|
|
|
_scratch_mkfs_encrypted()
|
|
{
|
|
case $FSTYP in
|
|
ext4|f2fs)
|
|
_scratch_mkfs -O encrypt
|
|
;;
|
|
ubifs)
|
|
# erase the UBI volume; reformated automatically on next mount
|
|
$UBIUPDATEVOL_PROG ${SCRATCH_DEV} -t
|
|
;;
|
|
*)
|
|
_notrun "No encryption support for $FSTYP"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_scratch_mkfs_sized_encrypted()
|
|
{
|
|
case $FSTYP in
|
|
ext4|f2fs)
|
|
MKFS_OPTIONS="$MKFS_OPTIONS -O encrypt" _scratch_mkfs_sized $*
|
|
;;
|
|
*)
|
|
_notrun "Filesystem $FSTYP not supported in _scratch_mkfs_sized_encrypted"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Give the invoking shell a new session keyring. This makes any keys we add to
|
|
# the session keyring scoped to the lifetime of the test script.
|
|
_new_session_keyring()
|
|
{
|
|
$KEYCTL_PROG new_session >>$seqres.full
|
|
}
|
|
|
|
# Generate a key descriptor (16 character hex string)
|
|
_generate_key_descriptor()
|
|
{
|
|
local keydesc=""
|
|
local i
|
|
for ((i = 0; i < 8; i++)); do
|
|
keydesc="${keydesc}$(printf "%02x" $(( $RANDOM % 256 )))"
|
|
done
|
|
echo $keydesc
|
|
}
|
|
|
|
# Generate a raw encryption key, but don't add it to any keyring yet.
|
|
_generate_raw_encryption_key()
|
|
{
|
|
local raw=""
|
|
local i
|
|
for ((i = 0; i < 64; i++)); do
|
|
raw="${raw}\\x$(printf "%02x" $(( $RANDOM % 256 )))"
|
|
done
|
|
echo $raw
|
|
}
|
|
|
|
# Add the specified raw encryption key to the session keyring, using the
|
|
# specified key descriptor.
|
|
_add_session_encryption_key()
|
|
{
|
|
local keydesc=$1
|
|
local raw=$2
|
|
|
|
#
|
|
# Add the key to the session keyring. The required structure is:
|
|
#
|
|
# #define FS_MAX_KEY_SIZE 64
|
|
# struct fscrypt_key {
|
|
# u32 mode;
|
|
# u8 raw[FS_MAX_KEY_SIZE];
|
|
# u32 size;
|
|
# } __packed;
|
|
#
|
|
# The kernel ignores 'mode' but requires that 'size' be 64.
|
|
#
|
|
# Keys are named $FSTYP:KEYDESC where KEYDESC is the 16-character key
|
|
# descriptor hex string. Newer kernels (ext4 4.8 and later, f2fs 4.6
|
|
# and later) also allow the common key prefix "fscrypt:" in addition to
|
|
# their filesystem-specific key prefix ("ext4:", "f2fs:"). It would be
|
|
# 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
|
|
echo -n -e "${mode}${raw}${size}" |
|
|
$KEYCTL_PROG padd logon $FSTYP:$keydesc @s >>$seqres.full
|
|
}
|
|
|
|
#
|
|
# Generate a random encryption key, add it to the session keyring, and print out
|
|
# the resulting key descriptor (example: "8bf798e1a494e1ec"). Requires the
|
|
# keyctl program. It's assumed the caller has already set up a test-scoped
|
|
# session keyring using _new_session_keyring.
|
|
#
|
|
_generate_session_encryption_key()
|
|
{
|
|
local keydesc=$(_generate_key_descriptor)
|
|
local raw=$(_generate_raw_encryption_key)
|
|
|
|
_add_session_encryption_key $keydesc $raw
|
|
|
|
echo $keydesc
|
|
}
|
|
|
|
# Unlink an encryption key from the session keyring, given its key descriptor.
|
|
_unlink_session_encryption_key()
|
|
{
|
|
local keydesc=$1
|
|
local keyid=$($KEYCTL_PROG search @s logon $FSTYP:$keydesc)
|
|
$KEYCTL_PROG unlink $keyid >>$seqres.full
|
|
}
|
|
|
|
# Revoke an encryption key from the session keyring, given its key descriptor.
|
|
_revoke_session_encryption_key()
|
|
{
|
|
local keydesc=$1
|
|
local keyid=$($KEYCTL_PROG search @s logon $FSTYP:$keydesc)
|
|
$KEYCTL_PROG revoke $keyid >>$seqres.full
|
|
}
|
|
|
|
# Set an encryption policy on the specified directory.
|
|
_set_encpolicy()
|
|
{
|
|
local dir=$1
|
|
shift
|
|
|
|
$XFS_IO_PROG -c "set_encpolicy $*" "$dir"
|
|
}
|
|
|
|
_user_do_set_encpolicy()
|
|
{
|
|
local dir=$1
|
|
shift
|
|
|
|
_user_do "$XFS_IO_PROG -c \"set_encpolicy $*\" \"$dir\""
|
|
}
|
|
|
|
# Display the specified file or directory's encryption policy.
|
|
_get_encpolicy()
|
|
{
|
|
local file=$1
|
|
shift
|
|
|
|
$XFS_IO_PROG -c "get_encpolicy $*" "$file"
|
|
}
|
|
|
|
_user_do_get_encpolicy()
|
|
{
|
|
local file=$1
|
|
shift
|
|
|
|
_user_do "$XFS_IO_PROG -c \"get_encpolicy $*\" \"$file\""
|
|
}
|
|
|
|
# Add an encryption key to the given filesystem.
|
|
_add_enckey()
|
|
{
|
|
local mnt=$1
|
|
local raw_key=$2
|
|
shift 2
|
|
|
|
echo -ne "$raw_key" | $XFS_IO_PROG -c "add_enckey $*" "$mnt"
|
|
}
|
|
|
|
_user_do_add_enckey()
|
|
{
|
|
local mnt=$1
|
|
local raw_key=$2
|
|
shift 2
|
|
|
|
_user_do "echo -ne \"$raw_key\" | $XFS_IO_PROG -c \"add_enckey $*\" \"$mnt\""
|
|
}
|
|
|
|
# Remove the given encryption key from the given filesystem.
|
|
_rm_enckey()
|
|
{
|
|
local mnt=$1
|
|
local keyspec=$2
|
|
shift 2
|
|
|
|
$XFS_IO_PROG -c "rm_enckey $* $keyspec" "$mnt"
|
|
}
|
|
|
|
_user_do_rm_enckey()
|
|
{
|
|
local mnt=$1
|
|
local keyspec=$2
|
|
shift 2
|
|
|
|
_user_do "$XFS_IO_PROG -c \"rm_enckey $* $keyspec\" \"$mnt\""
|
|
}
|
|
|
|
# Get the status of the given encryption key on the given filesystem.
|
|
_enckey_status()
|
|
{
|
|
local mnt=$1
|
|
local keyspec=$2
|
|
shift 2
|
|
|
|
$XFS_IO_PROG -c "enckey_status $* $keyspec" "$mnt"
|
|
}
|
|
|
|
_user_do_enckey_status()
|
|
{
|
|
local mnt=$1
|
|
local keyspec=$2
|
|
shift 2
|
|
|
|
_user_do "$XFS_IO_PROG -c \"enckey_status $* $keyspec\" \"$mnt\""
|
|
}
|
|
|
|
# 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.
|
|
_get_encryption_nonce()
|
|
{
|
|
local device=$1
|
|
local inode=$2
|
|
|
|
case $FSTYP in
|
|
ext4)
|
|
# Use debugfs to dump the special xattr named "c", which is the
|
|
# file's fscrypt_context. This produces a line like:
|
|
#
|
|
# c (28) = 01 01 04 02 00 00 00 00 00 00 00 00 ef bd 18 76 5d f6 41 4e c0 a2 cd 5f 91 29 7e 12
|
|
#
|
|
# Then filter it to get just the 16-byte 'nonce' field at the end:
|
|
#
|
|
# efbd18765df6414ec0a2cd5f91297e12
|
|
#
|
|
$DEBUGFS_PROG $device -R "ea_get <$inode> c" 2>>$seqres.full \
|
|
| grep '^c ' | sed 's/^.*=//' | tr -d ' \n' | tail -c 32
|
|
;;
|
|
f2fs)
|
|
# dump.f2fs prints the fscrypt_context like:
|
|
#
|
|
# xattr: e_name_index:9 e_name:c e_name_len:1 e_value_size:28 e_value:
|
|
# format: 1
|
|
# contents_encryption_mode: 0x1
|
|
# filenames_encryption_mode: 0x4
|
|
# flags: 0x2
|
|
# master_key_descriptor: 0000000000000000
|
|
# nonce: EFBD18765DF6414EC0A2CD5F91297E12
|
|
#
|
|
# Also support the case where the whole xattr is printed as hex,
|
|
# as is the case for fscrypt_context_v2.
|
|
#
|
|
# xattr: e_name_index:9 e_name:c e_name_len:1 e_value_size:40 e_value:
|
|
# 020104020000000033809BFEBE68A4AD264079B30861DD5E6B9E72D07523C58794ACF52534BAA756
|
|
#
|
|
$DUMP_F2FS_PROG -i $inode $device | awk '
|
|
/\<e_name:c\>/ { found = 1 }
|
|
(/^nonce:/ || /^[[:xdigit:]]+$/) && found {
|
|
print substr($0, length($0) - 31, 32);
|
|
found = 0;
|
|
}'
|
|
;;
|
|
*)
|
|
_fail "_get_encryption_nonce() isn't implemented on $FSTYP"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Require support for _get_encryption_nonce()
|
|
_require_get_encryption_nonce_support()
|
|
{
|
|
echo "Checking for _get_encryption_nonce() support for $FSTYP" >> $seqres.full
|
|
case $FSTYP in
|
|
ext4)
|
|
_require_command "$DEBUGFS_PROG" debugfs
|
|
;;
|
|
f2fs)
|
|
_require_command "$DUMP_F2FS_PROG" dump.f2fs
|
|
# For fscrypt_context_v2, we actually need a f2fs-tools version
|
|
# that has the patch "f2fs-tools: improve xattr value printing"
|
|
# (https://sourceforge.net/p/linux-f2fs/mailman/message/36648640/).
|
|
# Otherwise the xattr is incorrectly parsed as v1. But just let
|
|
# the test fail in that case, as it was an f2fs-tools bug...
|
|
;;
|
|
*)
|
|
_notrun "_get_encryption_nonce() isn't implemented on $FSTYP"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Retrieve the encrypted filename stored on-disk for the given file.
|
|
# The name is printed to stdout in binary.
|
|
_get_ciphertext_filename()
|
|
{
|
|
local device=$1
|
|
local inode=$2
|
|
local dir_inode=$3
|
|
|
|
case $FSTYP in
|
|
ext4)
|
|
# Extract the filename from the debugfs output line like:
|
|
#
|
|
# 131075 100644 (1) 0 0 0 22-Apr-2019 16:54 \xa2\x85\xb0z\x13\xe9\x09\x86R\xed\xdc\xce\xad\x14d\x19
|
|
#
|
|
# Bytes are shown either literally or as \xHH-style escape
|
|
# sequences; we have to decode the escaped bytes here.
|
|
#
|
|
$DEBUGFS_PROG $device -R "ls -l -r <$dir_inode>" \
|
|
2>>$seqres.full | perl -ne '
|
|
next if not /^\s*'$inode'\s+/;
|
|
s/.*?\d\d:\d\d //;
|
|
chomp;
|
|
s/\\x([[:xdigit:]]{2})/chr hex $1/eg;
|
|
print;'
|
|
;;
|
|
f2fs)
|
|
# Extract the filename from the dump.f2fs output line like:
|
|
#
|
|
# i_name [UpkzIPuts9by1oDmE+Ivfw]
|
|
#
|
|
# The name is shown base64-encoded; we have to decode it here.
|
|
#
|
|
$DUMP_F2FS_PROG $device -i $inode | perl -ne '
|
|
next if not /^i_name\s+\[([A-Za-z0-9+,]+)\]/;
|
|
chomp $1;
|
|
my @chars = split //, $1;
|
|
my $ac = 0;
|
|
my $bits = 0;
|
|
my $table = join "", (A..Z, a..z, 0..9, "+", ",");
|
|
foreach (@chars) {
|
|
$ac += index($table, $_) << $bits;
|
|
$bits += 6;
|
|
if ($bits >= 8) {
|
|
print chr($ac & 0xff);
|
|
$ac >>= 8;
|
|
$bits -= 8;
|
|
}
|
|
}
|
|
if ($ac != 0) {
|
|
print STDERR "Invalid base64-encoded string!\n";
|
|
}'
|
|
;;
|
|
*)
|
|
_fail "_get_ciphertext_filename() isn't implemented on $FSTYP"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Require support for _get_ciphertext_filename().
|
|
_require_get_ciphertext_filename_support()
|
|
{
|
|
echo "Checking for _get_ciphertext_filename() support for $FSTYP" >> $seqres.full
|
|
case $FSTYP in
|
|
ext4)
|
|
# Verify that the "ls -l -r" debugfs command is supported and
|
|
# that it hex-encodes non-ASCII characters, rather than using an
|
|
# ambiguous escaping method. This requires e2fsprogs v1.45.1 or
|
|
# later; or more specifically, a version that has the commit
|
|
# "debugfs: avoid ambiguity when printing filenames".
|
|
_require_command "$DEBUGFS_PROG" debugfs
|
|
_scratch_mount
|
|
touch $SCRATCH_MNT/$'\xc1'
|
|
_scratch_unmount
|
|
if ! $DEBUGFS_PROG $SCRATCH_DEV -R "ls -l -r /" 2>&1 \
|
|
| tee -a $seqres.full | grep -E -q '\s+\\xc1\s*$'; then
|
|
_notrun "debugfs (e2fsprogs) is too old; doesn't support showing unambiguous on-disk filenames"
|
|
fi
|
|
;;
|
|
f2fs)
|
|
# Verify that dump.f2fs shows encrypted filenames in full. This
|
|
# requires f2fs-tools v1.13.0 or later; or more specifically, a
|
|
# version that has the commit
|
|
# "f2fs-tools: improve filename printing".
|
|
|
|
_require_command "$DUMP_F2FS_PROG" dump.f2fs
|
|
_require_command "$KEYCTL_PROG" keyctl
|
|
_scratch_mount
|
|
_new_session_keyring
|
|
|
|
local keydesc=$(_generate_session_encryption_key)
|
|
local dir=$SCRATCH_MNT/test.${FUNCNAME[0]}
|
|
local file=$dir/$(perl -e 'print "A" x 255')
|
|
mkdir $dir
|
|
_set_encpolicy $dir $keydesc
|
|
touch $file
|
|
local inode=$(stat -c %i $file)
|
|
|
|
_scratch_unmount
|
|
$KEYCTL_PROG clear @s
|
|
|
|
# 255-character filename should result in 340 base64 characters.
|
|
if ! $DUMP_F2FS_PROG -i $inode $SCRATCH_DEV \
|
|
| grep -E -q '^i_name[[:space:]]+\[[A-Za-z0-9+,]{340}\]'; then
|
|
_notrun "dump.f2fs (f2fs-tools) is too old; doesn't support showing unambiguous on-disk filenames"
|
|
fi
|
|
;;
|
|
*)
|
|
_notrun "_get_ciphertext_filename() isn't implemented on $FSTYP"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Get an encrypted file's list of on-disk blocks as a comma-separated list of
|
|
# block offsets from the start of the device. "Blocks" are 512 bytes each here.
|
|
_get_ciphertext_block_list()
|
|
{
|
|
local file=$1
|
|
|
|
sync
|
|
$XFS_IO_PROG -c fiemap $file | perl -ne '
|
|
next if not /^\s*\d+: \[\d+\.\.\d+\]: (\d+)\.\.(\d+)/;
|
|
print $_ . "," foreach $1..$2;' | sed 's/,$//'
|
|
}
|
|
|
|
# Dump a block list that was previously saved by _get_ciphertext_block_list().
|
|
_dump_ciphertext_blocks()
|
|
{
|
|
local device=$1
|
|
local blocklist=$2
|
|
local block
|
|
|
|
for block in $(tr ',' ' ' <<< $blocklist); do
|
|
dd if=$device bs=512 count=1 skip=$block status=none
|
|
done
|
|
}
|
|
|
|
_do_verify_ciphertext_for_encryption_policy()
|
|
{
|
|
local contents_encryption_mode=$1
|
|
local filenames_encryption_mode=$2
|
|
local policy_flags=$3
|
|
local set_encpolicy_args=$4
|
|
local keyspec=$5
|
|
local raw_key_hex=$6
|
|
local crypt_cmd="$here/src/fscrypt-crypt-util $7"
|
|
|
|
local blocksize=$(_get_block_size $SCRATCH_MNT)
|
|
local test_contents_files=()
|
|
local test_filenames_files=()
|
|
local i src dir dst inode blocklist \
|
|
padding_flag padding dir_inode len name f nonce decrypted_name
|
|
|
|
# Create files whose encrypted contents we'll verify. For each, save
|
|
# the information: (copy of original file, inode number of encrypted
|
|
# file, comma-separated block list) into test_contents_files[].
|
|
echo "Creating files for contents verification" >> $seqres.full
|
|
i=1
|
|
rm -f $tmp.testfile_*
|
|
for src in /dev/zero /dev/urandom; do
|
|
head -c $((4 * blocksize)) $src > $tmp.testfile_$i
|
|
(( i++ ))
|
|
done
|
|
dir=$SCRATCH_MNT/encdir
|
|
mkdir $dir
|
|
_set_encpolicy $dir $keyspec $set_encpolicy_args -f $policy_flags
|
|
for src in $tmp.testfile_*; do
|
|
dst=$dir/${src##*.}
|
|
cp $src $dst
|
|
inode=$(stat -c %i $dst)
|
|
blocklist=$(_get_ciphertext_block_list $dst)
|
|
test_contents_files+=("$src $inode $blocklist")
|
|
done
|
|
|
|
# Create files whose encrypted names we'll verify. For each, save the
|
|
# information: (original filename, inode number of encrypted file, inode
|
|
# of parent directory, padding amount) into test_filenames_files[]. Try
|
|
# each padding amount: 4, 8, 16, or 32 bytes. Also try various filename
|
|
# lengths, including boundary cases. Assume NAME_MAX == 255.
|
|
echo "Creating files for filenames verification" >> $seqres.full
|
|
for padding_flag in 0 1 2 3; do
|
|
padding=$((4 << padding_flag))
|
|
dir=$SCRATCH_MNT/encdir.pad$padding
|
|
mkdir $dir
|
|
dir_inode=$(stat -c %i $dir)
|
|
_set_encpolicy $dir $keyspec $set_encpolicy_args \
|
|
-f $((policy_flags | padding_flag))
|
|
for len in 1 3 15 16 17 32 100 254 255; do
|
|
name=$(tr -d -C a-zA-Z0-9 < /dev/urandom | head -c $len)
|
|
touch $dir/$name
|
|
inode=$(stat -c %i $dir/$name)
|
|
test_filenames_files+=("$name $inode $dir_inode $padding")
|
|
done
|
|
done
|
|
|
|
# Now unmount the filesystem and verify the ciphertext we just wrote.
|
|
_scratch_unmount
|
|
|
|
echo "Verifying encrypted file contents" >> $seqres.full
|
|
for f in "${test_contents_files[@]}"; do
|
|
read -r src inode blocklist <<< "$f"
|
|
nonce=$(_get_encryption_nonce $SCRATCH_DEV $inode)
|
|
_dump_ciphertext_blocks $SCRATCH_DEV $blocklist > $tmp.actual_contents
|
|
$crypt_cmd $contents_encryption_mode $raw_key_hex \
|
|
--file-nonce=$nonce --block-size=$blocksize \
|
|
< $src > $tmp.expected_contents
|
|
if ! cmp $tmp.expected_contents $tmp.actual_contents; then
|
|
_fail "Expected encrypted contents != actual encrypted contents. File: $f"
|
|
fi
|
|
$crypt_cmd $contents_encryption_mode $raw_key_hex --decrypt \
|
|
--file-nonce=$nonce --block-size=$blocksize \
|
|
< $tmp.actual_contents > $tmp.decrypted_contents
|
|
if ! cmp $src $tmp.decrypted_contents; then
|
|
_fail "Contents decryption sanity check failed. File: $f"
|
|
fi
|
|
done
|
|
|
|
echo "Verifying encrypted file names" >> $seqres.full
|
|
for f in "${test_filenames_files[@]}"; do
|
|
read -r name inode dir_inode padding <<< "$f"
|
|
nonce=$(_get_encryption_nonce $SCRATCH_DEV $dir_inode)
|
|
_get_ciphertext_filename $SCRATCH_DEV $inode $dir_inode \
|
|
> $tmp.actual_name
|
|
echo -n "$name" | \
|
|
$crypt_cmd $filenames_encryption_mode $raw_key_hex \
|
|
--file-nonce=$nonce --padding=$padding \
|
|
--block-size=255 > $tmp.expected_name
|
|
if ! cmp $tmp.expected_name $tmp.actual_name; then
|
|
_fail "Expected encrypted filename != actual encrypted filename. File: $f"
|
|
fi
|
|
$crypt_cmd $filenames_encryption_mode $raw_key_hex --decrypt \
|
|
--file-nonce=$nonce --padding=$padding \
|
|
--block-size=255 < $tmp.actual_name \
|
|
> $tmp.decrypted_name
|
|
decrypted_name=$(tr -d '\0' < $tmp.decrypted_name)
|
|
if [ "$name" != "$decrypted_name" ]; then
|
|
_fail "Filename decryption sanity check failed ($name != $decrypted_name). File: $f"
|
|
fi
|
|
done
|
|
}
|
|
|
|
_fscrypt_mode_name_to_num()
|
|
{
|
|
local name=$1
|
|
|
|
case "$name" in
|
|
AES-256-XTS) echo 1 ;; # FS_ENCRYPTION_MODE_AES_256_XTS
|
|
AES-256-CTS-CBC) echo 4 ;; # FS_ENCRYPTION_MODE_AES_256_CTS
|
|
AES-128-CBC-ESSIV) echo 5 ;; # FS_ENCRYPTION_MODE_AES_128_CBC
|
|
AES-128-CTS-CBC) echo 6 ;; # FS_ENCRYPTION_MODE_AES_128_CTS
|
|
Adiantum) echo 9 ;; # FS_ENCRYPTION_MODE_ADIANTUM
|
|
*) _fail "Unknown fscrypt mode: $name" ;;
|
|
esac
|
|
}
|
|
|
|
# Verify that file contents and names are encrypted correctly when an encryption
|
|
# policy of the specified type is used.
|
|
#
|
|
# The first two parameters are the contents and filenames encryption modes to
|
|
# test. Optionally, also specify 'direct' to test the DIRECT_KEY flag, and/or
|
|
# 'v2' to test v2 policies.
|
|
_verify_ciphertext_for_encryption_policy()
|
|
{
|
|
local contents_encryption_mode=$1
|
|
local filenames_encryption_mode=$2
|
|
local opt
|
|
local policy_version=1
|
|
local policy_flags=0
|
|
local set_encpolicy_args=""
|
|
local crypt_util_args=""
|
|
|
|
shift 2
|
|
for opt; do
|
|
case "$opt" in
|
|
v2)
|
|
policy_version=2
|
|
;;
|
|
direct)
|
|
if [ $contents_encryption_mode != \
|
|
$filenames_encryption_mode ]; then
|
|
_fail "For direct key mode, contents and filenames modes must match"
|
|
fi
|
|
(( policy_flags |= 0x04 )) # FS_POLICY_FLAG_DIRECT_KEY
|
|
;;
|
|
*)
|
|
_fail "Unknown option '$opt' passed to ${FUNCNAME[0]}"
|
|
;;
|
|
esac
|
|
done
|
|
local contents_mode_num=$(_fscrypt_mode_name_to_num $contents_encryption_mode)
|
|
local filenames_mode_num=$(_fscrypt_mode_name_to_num $filenames_encryption_mode)
|
|
|
|
set_encpolicy_args+=" -c $contents_mode_num"
|
|
set_encpolicy_args+=" -n $filenames_mode_num"
|
|
|
|
if (( policy_version > 1 )); then
|
|
set_encpolicy_args+=" -v 2"
|
|
crypt_util_args+=" --kdf=HKDF-SHA512"
|
|
if (( policy_flags & 0x04 )); then
|
|
crypt_util_args+=" --mode-num=$contents_mode_num"
|
|
fi
|
|
else
|
|
if (( policy_flags & 0x04 )); then
|
|
crypt_util_args+=" --kdf=none"
|
|
else
|
|
crypt_util_args+=" --kdf=AES-128-ECB"
|
|
fi
|
|
fi
|
|
set_encpolicy_args=${set_encpolicy_args# }
|
|
|
|
_require_scratch_encryption $set_encpolicy_args
|
|
_require_test_program "fscrypt-crypt-util"
|
|
_require_xfs_io_command "fiemap"
|
|
_require_get_encryption_nonce_support
|
|
_require_get_ciphertext_filename_support
|
|
if (( policy_version == 1 )); then
|
|
_require_command "$KEYCTL_PROG" keyctl
|
|
fi
|
|
|
|
echo "Creating encryption-capable filesystem" >> $seqres.full
|
|
_scratch_mkfs_encrypted &>> $seqres.full
|
|
_scratch_mount
|
|
|
|
echo "Generating encryption key" >> $seqres.full
|
|
local raw_key=$(_generate_raw_encryption_key)
|
|
if (( policy_version > 1 )); then
|
|
local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" \
|
|
| awk '{print $NF}')
|
|
else
|
|
local keyspec=$(_generate_key_descriptor)
|
|
_new_session_keyring
|
|
_add_session_encryption_key $keyspec $raw_key
|
|
fi
|
|
local raw_key_hex=$(echo "$raw_key" | tr -d '\\x')
|
|
|
|
echo
|
|
echo -e "Verifying ciphertext with parameters:"
|
|
echo -e "\tcontents_encryption_mode: $contents_encryption_mode"
|
|
echo -e "\tfilenames_encryption_mode: $filenames_encryption_mode"
|
|
[ $# -ne 0 ] && echo -e "\toptions: $*"
|
|
|
|
_do_verify_ciphertext_for_encryption_policy \
|
|
"$contents_encryption_mode" \
|
|
"$filenames_encryption_mode" \
|
|
"$policy_flags" \
|
|
"$set_encpolicy_args" \
|
|
"$keyspec" \
|
|
"$raw_key_hex" \
|
|
"$crypt_util_args"
|
|
}
|