From: Eryu Guan <guaneryu@gmail.com>
To: Eric Biggers <ebiggers@kernel.org>
Cc: fstests@vger.kernel.org, linux-fscrypt@vger.kernel.org,
linux-ext4@vger.kernel.org,
linux-f2fs-devel@lists.sourceforge.net
Subject: Re: [RFC PATCH 4/7] common/encrypt: add helper for ciphertext verification tests
Date: Sun, 12 May 2019 20:27:03 +0800 [thread overview]
Message-ID: <20190512122703.GJ15846@desktop> (raw)
In-Reply-To: <20190426204153.101861-5-ebiggers@kernel.org>
On Fri, Apr 26, 2019 at 01:41:50PM -0700, Eric Biggers wrote:
> From: Eric Biggers <ebiggers@google.com>
>
> Introduce a function _verify_ciphertext_for_encryption_policy() which
> verifies the correctness of encryption with the specified settings.
>
> Basically, it does the following:
>
> 1. If missing any prerequisites, skip the test.
>
> 2. Create files in encrypted directories on the scratch device.
>
> 3. Unmount the scratch device and compare the actual ciphertext stored
> on-disk to the ciphertext computed by the fscrypt-crypt-util program.
>
> Both file contents and names are verified, and non-default encryption
> modes are supported. Previously, non-default encryption modes were
> untested by xfstests. Also, while there's an existing test generic/399
> that checks that encrypted contents seem random, it doesn't actually
> test for correctness, nor does it test filenames encryption.
>
> Signed-off-by: Eric Biggers <ebiggers@google.com>
> ---
> common/encrypt | 390 +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 390 insertions(+)
>
> diff --git a/common/encrypt b/common/encrypt
> index 37f16b94..3e48abc0 100644
> --- a/common/encrypt
> +++ b/common/encrypt
> @@ -263,3 +263,393 @@ _get_encpolicy()
>
> $XFS_IO_PROG -c "get_encpolicy $*" "$file"
> }
> +
> +# 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
> + $DUMP_F2FS_PROG -i $inode $device | awk '
> + /\<e_name:c\>/ { found = 1 }
> + /^nonce:/ && found {
> + print substr($0, length($0) - 31, 32);
> + found = 0;
> + }'
> + ;;
> + *)
> + _notrun "_get_encryption_nonce() isn't implemented on $FSTYP"
> + ;;
No need to _notrun here, _require_get_encryption_nonce_support() should
be called first and already does the check in test. Perhaps just echoing
an error message to indicate the failure here is fine.
> + 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
> + ;;
> + *)
> + _notrun "_get_encryption_nonce() isn't implemented on $FSTYP"
> + ;;
> + esac
> +}
> +
> +# Retrieve the filename stored on-disk for the given file.
> +# The name is printed to stdout in binary.
> +_get_on_disk_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
> + #
> + $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 base64-encoded, so 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";
> + }'
> + ;;
> + *)
> + _notrun "_get_on_disk_filename() isn't implemented on $FSTYP"
Same here, the get_on_disk_filename support is checked by
_require_get_on_disk_filename_support(). And looks like this function
has nothing to do with fs encryption, move it to common/rc?
> + ;;
> + esac
> +}
> +
> +# Require support for _get_on_disk_filename()
> +_require_get_on_disk_filename_support()
> +{
> + echo "Checking for _get_on_disk_filename() support for $FSTYP" >> $seqres.full
> + case $FSTYP in
> + ext4)
> + # Verify that the "ls -l -r" debugfs command is supported and
> + # hex-encodes non-ASCII characters, rather than using an
> + # ambiguous escaping method. This requires the e2fsprogs patch
> + # "debugfs: avoid ambiguity when printing filenames"
> + # (https://marc.info/?l=linux-ext4&m=155596495624232&w=2).
> + # TODO: once merged, list the minimum e2fsprogs version here.
> + _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 the patch "f2fs-tools: improve filename printing"
> + # (https://sourceforge.net/p/linux-f2fs/mailman/message/36648641/).
> + # TODO: once merged, list the minimum f2fs-tools version here.
> +
> + _require_command "$DUMP_F2FS_PROG" dump.f2fs
> + _require_command "$KEYCTL_PROG" keyctl
> + _scratch_mount
> + _new_session_keyring
> +
> + local keydesc=$(_generate_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_on_disk_filename() isn't implemented on $FSTYP"
> + ;;
> + esac
> +}
> +
> +# Get the 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_file_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_file_block_list().
> +_dump_file_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
> +}
Above two functions seem generic enough to be moved to common/rc
Thanks,
Eryu
> +
> +_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 keydesc=$5
> + local raw_key_hex=$6
> + local crypt_cmd="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 $keydesc $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_file_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 $keydesc $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_file_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_on_disk_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.
> +_verify_ciphertext_for_encryption_policy()
> +{
> + local contents_encryption_mode=$1
> + local filenames_encryption_mode=$2
> + local opt
> + local policy_flags=0
> + local set_encpolicy_args=""
> + local crypt_util_args=""
> +
> + shift 2
> + for opt; do
> + case "$opt" in
> + 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_flags & 0x04 )); then
> + crypt_util_args+=" --kdf=none"
> + else
> + crypt_util_args+=" --kdf=AES-128-ECB"
> + 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_on_disk_filename_support
> + _require_command "$KEYCTL_PROG" keyctl
> +
> + 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)
> + local keydesc=$(_generate_key_descriptor)
> + _new_session_keyring
> + _add_encryption_key $keydesc $raw_key
> + 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" \
> + "$keydesc" \
> + "$raw_key_hex" \
> + "$crypt_util_args"
> +}
> --
> 2.21.0.593.g511ec345e18-goog
>
WARNING: multiple messages have this Message-ID (diff)
From: Eryu Guan <guaneryu@gmail.com>
To: Eric Biggers <ebiggers@kernel.org>
Cc: linux-fscrypt@vger.kernel.org, linux-ext4@vger.kernel.org,
fstests@vger.kernel.org, linux-f2fs-devel@lists.sourceforge.net
Subject: Re: [RFC PATCH 4/7] common/encrypt: add helper for ciphertext verification tests
Date: Sun, 12 May 2019 20:27:03 +0800 [thread overview]
Message-ID: <20190512122703.GJ15846@desktop> (raw)
In-Reply-To: <20190426204153.101861-5-ebiggers@kernel.org>
On Fri, Apr 26, 2019 at 01:41:50PM -0700, Eric Biggers wrote:
> From: Eric Biggers <ebiggers@google.com>
>
> Introduce a function _verify_ciphertext_for_encryption_policy() which
> verifies the correctness of encryption with the specified settings.
>
> Basically, it does the following:
>
> 1. If missing any prerequisites, skip the test.
>
> 2. Create files in encrypted directories on the scratch device.
>
> 3. Unmount the scratch device and compare the actual ciphertext stored
> on-disk to the ciphertext computed by the fscrypt-crypt-util program.
>
> Both file contents and names are verified, and non-default encryption
> modes are supported. Previously, non-default encryption modes were
> untested by xfstests. Also, while there's an existing test generic/399
> that checks that encrypted contents seem random, it doesn't actually
> test for correctness, nor does it test filenames encryption.
>
> Signed-off-by: Eric Biggers <ebiggers@google.com>
> ---
> common/encrypt | 390 +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 390 insertions(+)
>
> diff --git a/common/encrypt b/common/encrypt
> index 37f16b94..3e48abc0 100644
> --- a/common/encrypt
> +++ b/common/encrypt
> @@ -263,3 +263,393 @@ _get_encpolicy()
>
> $XFS_IO_PROG -c "get_encpolicy $*" "$file"
> }
> +
> +# 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
> + $DUMP_F2FS_PROG -i $inode $device | awk '
> + /\<e_name:c\>/ { found = 1 }
> + /^nonce:/ && found {
> + print substr($0, length($0) - 31, 32);
> + found = 0;
> + }'
> + ;;
> + *)
> + _notrun "_get_encryption_nonce() isn't implemented on $FSTYP"
> + ;;
No need to _notrun here, _require_get_encryption_nonce_support() should
be called first and already does the check in test. Perhaps just echoing
an error message to indicate the failure here is fine.
> + 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
> + ;;
> + *)
> + _notrun "_get_encryption_nonce() isn't implemented on $FSTYP"
> + ;;
> + esac
> +}
> +
> +# Retrieve the filename stored on-disk for the given file.
> +# The name is printed to stdout in binary.
> +_get_on_disk_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
> + #
> + $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 base64-encoded, so 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";
> + }'
> + ;;
> + *)
> + _notrun "_get_on_disk_filename() isn't implemented on $FSTYP"
Same here, the get_on_disk_filename support is checked by
_require_get_on_disk_filename_support(). And looks like this function
has nothing to do with fs encryption, move it to common/rc?
> + ;;
> + esac
> +}
> +
> +# Require support for _get_on_disk_filename()
> +_require_get_on_disk_filename_support()
> +{
> + echo "Checking for _get_on_disk_filename() support for $FSTYP" >> $seqres.full
> + case $FSTYP in
> + ext4)
> + # Verify that the "ls -l -r" debugfs command is supported and
> + # hex-encodes non-ASCII characters, rather than using an
> + # ambiguous escaping method. This requires the e2fsprogs patch
> + # "debugfs: avoid ambiguity when printing filenames"
> + # (https://marc.info/?l=linux-ext4&m=155596495624232&w=2).
> + # TODO: once merged, list the minimum e2fsprogs version here.
> + _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 the patch "f2fs-tools: improve filename printing"
> + # (https://sourceforge.net/p/linux-f2fs/mailman/message/36648641/).
> + # TODO: once merged, list the minimum f2fs-tools version here.
> +
> + _require_command "$DUMP_F2FS_PROG" dump.f2fs
> + _require_command "$KEYCTL_PROG" keyctl
> + _scratch_mount
> + _new_session_keyring
> +
> + local keydesc=$(_generate_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_on_disk_filename() isn't implemented on $FSTYP"
> + ;;
> + esac
> +}
> +
> +# Get the 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_file_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_file_block_list().
> +_dump_file_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
> +}
Above two functions seem generic enough to be moved to common/rc
Thanks,
Eryu
> +
> +_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 keydesc=$5
> + local raw_key_hex=$6
> + local crypt_cmd="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 $keydesc $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_file_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 $keydesc $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_file_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_on_disk_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.
> +_verify_ciphertext_for_encryption_policy()
> +{
> + local contents_encryption_mode=$1
> + local filenames_encryption_mode=$2
> + local opt
> + local policy_flags=0
> + local set_encpolicy_args=""
> + local crypt_util_args=""
> +
> + shift 2
> + for opt; do
> + case "$opt" in
> + 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_flags & 0x04 )); then
> + crypt_util_args+=" --kdf=none"
> + else
> + crypt_util_args+=" --kdf=AES-128-ECB"
> + 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_on_disk_filename_support
> + _require_command "$KEYCTL_PROG" keyctl
> +
> + 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)
> + local keydesc=$(_generate_key_descriptor)
> + _new_session_keyring
> + _add_encryption_key $keydesc $raw_key
> + 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" \
> + "$keydesc" \
> + "$raw_key_hex" \
> + "$crypt_util_args"
> +}
> --
> 2.21.0.593.g511ec345e18-goog
>
next prev parent reply other threads:[~2019-05-12 12:27 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-04-26 20:41 [RFC PATCH 0/7] xfstests: verify fscrypt-encrypted contents and filenames Eric Biggers
2019-04-26 20:41 ` [RFC PATCH 1/7] common/encrypt: introduce helpers for set_encpolicy and get_encpolicy Eric Biggers
2019-05-12 12:21 ` Eryu Guan
2019-05-12 12:21 ` Eryu Guan
2019-04-26 20:41 ` [RFC PATCH 2/7] fscrypt-crypt-util: add utility for reproducing fscrypt encrypted data Eric Biggers
2019-04-26 20:41 ` Eric Biggers
2019-04-26 20:41 ` [RFC PATCH 3/7] common/encrypt: support requiring other encryption settings Eric Biggers
2019-04-26 20:41 ` [f2fs-dev] " Eric Biggers
2019-04-26 20:41 ` [RFC PATCH 4/7] common/encrypt: add helper for ciphertext verification tests Eric Biggers
2019-05-12 12:27 ` Eryu Guan [this message]
2019-05-12 12:27 ` Eryu Guan
2019-05-13 19:12 ` Eric Biggers
2019-05-13 19:12 ` [f2fs-dev] " Eric Biggers
2019-05-13 19:12 ` Eric Biggers
2019-05-14 2:20 ` Eryu Guan
2019-05-14 2:20 ` Eryu Guan
2019-04-26 20:41 ` [RFC PATCH 5/7] generic: verify ciphertext of v1 encryption policies with AES-256 Eric Biggers
2019-04-26 20:41 ` [RFC PATCH 6/7] generic: verify ciphertext of v1 encryption policies with AES-128 Eric Biggers
2019-04-26 20:41 ` [RFC PATCH 7/7] generic: verify ciphertext of v1 encryption policies with Adiantum Eric Biggers
2019-05-06 15:57 ` [RFC PATCH 0/7] xfstests: verify fscrypt-encrypted contents and filenames Eric Biggers
2019-05-12 12:58 ` Eryu Guan
2019-05-12 12:58 ` Eryu Guan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190512122703.GJ15846@desktop \
--to=guaneryu@gmail.com \
--cc=ebiggers@kernel.org \
--cc=fstests@vger.kernel.org \
--cc=linux-ext4@vger.kernel.org \
--cc=linux-f2fs-devel@lists.sourceforge.net \
--cc=linux-fscrypt@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.