|  | ##/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 ' | 
|  | /\<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_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 <linux/fscrypt.h>) | 
|  |  | 
|  | 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" | 
|  | } |