##/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] # [-f POLICY_FLAGS] [-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_flags=0 local policy_version=1 local c OPTIND=2 while getopts "c:n:f:v:" c; do case $c in c|n) set_encpolicy_args+=" -$c $OPTARG" ;; f) set_encpolicy_args+=" -$c $OPTARG" policy_flags=$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 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 fi 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 } # Like _scratch_mkfs_encrypted(), but add -O stable_inodes if applicable for the # filesystem type. This is necessary for using encryption policies that include # the inode number in the IVs, e.g. policies with the IV_INO_LBLK_64 flag set. _scratch_mkfs_stable_inodes_encrypted() { case $FSTYP in ext4) if ! _scratch_mkfs -O encrypt -O stable_inodes; then _notrun "-O stable_inodes is not supported" fi ;; *) _scratch_mkfs_encrypted ;; esac } # For some tests it's helpful to always use the same key so that the test's # output is always the same. For this purpose the following key can be used: TEST_RAW_KEY= for i in {1..64}; do TEST_RAW_KEY+="\\x$(printf "%02x" $i)" done # Key descriptor: arbitrary value TEST_KEY_DESCRIPTOR="0000111122223333" # Key identifier: HKDF-SHA512(key=$TEST_RAW_KEY, salt="", info="fscrypt\0\x01") TEST_KEY_IDENTIFIER="69b2f6edeee720cce0577937eb8a6751" # 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 } # 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() { local keydesc=$1 local raw=$2 # # Add the key to the session keyring. The required structure is: # # #define FSCRYPT_MAX_KEY_SIZE 64 # struct fscrypt_key { # __u32 mode; # __u8 raw[FSCRYPT_MAX_KEY_SIZE]; # __u32 size; # }; # # 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 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 } # # 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\"" } # 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. _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 ' /\/ { 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_contents_cmd="$here/src/fscrypt-crypt-util $7" local crypt_filename_cmd="$here/src/fscrypt-crypt-util $8" 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_contents_cmd $contents_encryption_mode $raw_key_hex \ --file-nonce=$nonce --block-size=$blocksize \ --inode-number=$inode < $src > $tmp.expected_contents if ! cmp $tmp.expected_contents $tmp.actual_contents; then _fail "Expected encrypted contents != actual encrypted contents. File: $f" fi $crypt_contents_cmd $contents_encryption_mode $raw_key_hex \ --decrypt --file-nonce=$nonce --block-size=$blocksize \ --inode-number=$inode \ < $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_filename_cmd $filenames_encryption_mode \ $raw_key_hex --file-nonce=$nonce --padding=$padding \ --block-size=255 --inode-number=$dir_inode \ > $tmp.expected_name if ! cmp $tmp.expected_name $tmp.actual_name; then _fail "Expected encrypted filename != actual encrypted filename. File: $f" fi $crypt_filename_cmd $filenames_encryption_mode $raw_key_hex \ --decrypt --file-nonce=$nonce --padding=$padding \ --block-size=255 --inode-number=$dir_inode \ < $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 UAPI constants (see ) FSCRYPT_MODE_AES_256_XTS=1 FSCRYPT_MODE_AES_256_CTS=4 FSCRYPT_MODE_AES_128_CBC=5 FSCRYPT_MODE_AES_128_CTS=6 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 _fscrypt_mode_name_to_num() { local name=$1 case "$name" in AES-256-XTS) echo $FSCRYPT_MODE_AES_256_XTS ;; AES-256-CTS-CBC) echo $FSCRYPT_MODE_AES_256_CTS ;; AES-128-CBC-ESSIV) echo $FSCRYPT_MODE_AES_128_CBC ;; AES-128-CTS-CBC) echo $FSCRYPT_MODE_AES_128_CTS ;; Adiantum) echo $FSCRYPT_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. The following optional parameters are also accepted to further modify # the type of encryption policy that is tested: # # '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() { 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="" local crypt_util_contents_args="" local crypt_util_filename_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 |= FSCRYPT_POLICY_FLAG_DIRECT_KEY )) ;; 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]}" ;; 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 & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then 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 _fail "unsupported flags for v1 policy: $policy_flags" fi if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); 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 -f $policy_flags _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 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 fi _scratch_mount crypt_util_args+=" --fs-uuid=$(blkid -s UUID -o value $SCRATCH_DEV | tr -d -)" crypt_util_contents_args+="$crypt_util_args" crypt_util_filename_args+="$crypt_util_args" 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_contents_args" \ "$crypt_util_filename_args" }