common/encrypt: support verifying ciphertext of v2 encryption policies
[xfstests-dev.git] / common / encrypt
1 ##/bin/bash
2 # SPDX-License-Identifier: GPL-2.0
3 # Copyright (c) 2016 Google, Inc.  All Rights Reserved.
4 #
5 # Functions for setting up and testing file encryption
6
7 #
8 # _require_scratch_encryption [-c CONTENTS_MODE] [-n FILENAMES_MODE]
9 #                             [-v POLICY_VERSION]
10 #
11 # Require encryption support on the scratch device.
12 #
13 # This checks for support for the default type of encryption policy (v1 with
14 # AES-256-XTS and AES-256-CTS).  Options can be specified to also require
15 # support for a different type of encryption policy.
16 #
17 _require_scratch_encryption()
18 {
19         _require_scratch
20
21         _require_xfs_io_command "set_encpolicy"
22
23         # The 'test_dummy_encryption' mount option interferes with trying to use
24         # encryption for real, even if we are just trying to get/set policies
25         # and never put any keys in the keyring.  So skip the real encryption
26         # tests if the 'test_dummy_encryption' mount option was specified.
27         _exclude_scratch_mount_option "test_dummy_encryption"
28
29         # Make a filesystem on the scratch device with the encryption feature
30         # enabled.  If this fails then probably the userspace tools (e.g.
31         # e2fsprogs or f2fs-tools) are too old to understand encryption.
32         if ! _scratch_mkfs_encrypted &>>$seqres.full; then
33                 _notrun "$FSTYP userspace tools do not support encryption"
34         fi
35
36         # Try to mount the filesystem.  If this fails then either the kernel
37         # isn't aware of encryption, or the mkfs options were not compatible
38         # with encryption (e.g. ext4 with block size != PAGE_SIZE).
39         if ! _try_scratch_mount &>>$seqres.full; then
40                 _notrun "kernel is unaware of $FSTYP encryption feature," \
41                         "or mkfs options are not compatible with encryption"
42         fi
43
44         # The kernel may be aware of encryption without supporting it.  For
45         # example, for ext4 this is the case with kernels configured with
46         # CONFIG_EXT4_FS_ENCRYPTION=n.  Detect support for encryption by trying
47         # to set an encryption policy.  (For ext4 we could instead check for the
48         # presence of /sys/fs/ext4/features/encryption, but this is broken on
49         # some older kernels and is ext4-specific anyway.)
50         mkdir $SCRATCH_MNT/tmpdir
51         if _set_encpolicy $SCRATCH_MNT/tmpdir 2>&1 >>$seqres.full | \
52                 egrep -q 'Inappropriate ioctl for device|Operation not supported'
53         then
54                 _notrun "kernel does not support $FSTYP encryption"
55         fi
56         rmdir $SCRATCH_MNT/tmpdir
57
58         # If required, check for support for the specific type of encryption
59         # policy required by the test.
60         if [ $# -ne 0 ]; then
61                 _require_encryption_policy_support $SCRATCH_MNT "$@"
62         fi
63
64         _scratch_unmount
65 }
66
67 _require_encryption_policy_support()
68 {
69         local mnt=$1
70         local dir=$mnt/tmpdir
71         local set_encpolicy_args=""
72         local policy_version=1
73         local c
74
75         OPTIND=2
76         while getopts "c:n:v:" c; do
77                 case $c in
78                 c|n)
79                         set_encpolicy_args+=" -$c $OPTARG"
80                         ;;
81                 v)
82                         set_encpolicy_args+=" -$c $OPTARG"
83                         policy_version=$OPTARG
84                         ;;
85                 *)
86                         _fail "Unrecognized option '$c'"
87                         ;;
88                 esac
89         done
90         set_encpolicy_args=${set_encpolicy_args# }
91
92         echo "Checking whether kernel supports encryption policy: $set_encpolicy_args" \
93                 >> $seqres.full
94
95         mkdir $dir
96         if (( policy_version > 1 )); then
97                 _require_xfs_io_command "get_encpolicy" "-t"
98                 local output=$(_get_encpolicy $dir -t)
99                 if [ "$output" != "supported" ]; then
100                         if [ "$output" = "unsupported" ]; then
101                                 _notrun "kernel does not support $FSTYP encryption v2 API"
102                         fi
103                         _fail "Unexpected output from 'get_encpolicy -t': $output"
104                 fi
105                 # Both the kernel and xfs_io support v2 encryption policies, and
106                 # therefore also filesystem-level keys -- since that's the only
107                 # way to provide keys for v2 policies.
108                 local raw_key=$(_generate_raw_encryption_key)
109                 local keyspec=$(_add_enckey $mnt "$raw_key" | awk '{print $NF}')
110         else
111                 _require_command "$KEYCTL_PROG" keyctl
112                 _new_session_keyring
113                 local keyspec=$(_generate_session_encryption_key)
114         fi
115         if _set_encpolicy $dir $keyspec $set_encpolicy_args \
116                 2>&1 >>$seqres.full | egrep -q 'Invalid argument'; then
117                 _notrun "kernel does not support encryption policy: '$set_encpolicy_args'"
118         fi
119         # fscrypt allows setting policies with modes it knows about, even
120         # without kernel crypto API support.  E.g. a policy using Adiantum
121         # encryption can be set on a kernel without CONFIG_CRYPTO_ADIANTUM.
122         # But actually trying to use such an encrypted directory will fail.
123         # To reliably check for availability of both the contents and filenames
124         # encryption modes, try creating a nonempty file.
125         if ! echo foo > $dir/file; then
126                 _notrun "encryption policy '$set_encpolicy_args' is unusable; probably missing kernel crypto API support"
127         fi
128         if (( policy_version <= 1 )); then
129                 $KEYCTL_PROG clear @s
130         fi
131         rm -r $dir
132 }
133
134 _scratch_mkfs_encrypted()
135 {
136         case $FSTYP in
137         ext4|f2fs)
138                 _scratch_mkfs -O encrypt
139                 ;;
140         ubifs)
141                 # erase the UBI volume; reformated automatically on next mount
142                 $UBIUPDATEVOL_PROG ${SCRATCH_DEV} -t
143                 ;;
144         *)
145                 _notrun "No encryption support for $FSTYP"
146                 ;;
147         esac
148 }
149
150 _scratch_mkfs_sized_encrypted()
151 {
152         case $FSTYP in
153         ext4|f2fs)
154                 MKFS_OPTIONS="$MKFS_OPTIONS -O encrypt" _scratch_mkfs_sized $*
155                 ;;
156         *)
157                 _notrun "Filesystem $FSTYP not supported in _scratch_mkfs_sized_encrypted"
158                 ;;
159         esac
160 }
161
162 # Give the invoking shell a new session keyring.  This makes any keys we add to
163 # the session keyring scoped to the lifetime of the test script.
164 _new_session_keyring()
165 {
166         $KEYCTL_PROG new_session >>$seqres.full
167 }
168
169 # Generate a key descriptor (16 character hex string)
170 _generate_key_descriptor()
171 {
172         local keydesc=""
173         local i
174         for ((i = 0; i < 8; i++)); do
175                 keydesc="${keydesc}$(printf "%02x" $(( $RANDOM % 256 )))"
176         done
177         echo $keydesc
178 }
179
180 # Generate a raw encryption key, but don't add it to any keyring yet.
181 _generate_raw_encryption_key()
182 {
183         local raw=""
184         local i
185         for ((i = 0; i < 64; i++)); do
186                 raw="${raw}\\x$(printf "%02x" $(( $RANDOM % 256 )))"
187         done
188         echo $raw
189 }
190
191 # Add the specified raw encryption key to the session keyring, using the
192 # specified key descriptor.
193 _add_session_encryption_key()
194 {
195         local keydesc=$1
196         local raw=$2
197
198         #
199         # Add the key to the session keyring.  The required structure is:
200         #
201         #       #define FS_MAX_KEY_SIZE 64
202         #       struct fscrypt_key {
203         #               u32 mode;
204         #               u8 raw[FS_MAX_KEY_SIZE];
205         #               u32 size;
206         #       } __packed;
207         #
208         # The kernel ignores 'mode' but requires that 'size' be 64.
209         #
210         # Keys are named $FSTYP:KEYDESC where KEYDESC is the 16-character key
211         # descriptor hex string.  Newer kernels (ext4 4.8 and later, f2fs 4.6
212         # and later) also allow the common key prefix "fscrypt:" in addition to
213         # their filesystem-specific key prefix ("ext4:", "f2fs:").  It would be
214         # nice to use the common key prefix, but for now use the filesystem-
215         # specific prefix to make it possible to test older kernels...
216         #
217         local big_endian=$(echo -ne '\x11' | od -tx2 | head -1 | \
218                            cut -f2 -d' ' | cut -c1 )
219         if (( big_endian )); then
220                 local mode='\x00\x00\x00\x00'
221                 local size='\x00\x00\x00\x40'
222         else
223                 local mode='\x00\x00\x00\x00'
224                 local size='\x40\x00\x00\x00'
225         fi
226         echo -n -e "${mode}${raw}${size}" |
227                 $KEYCTL_PROG padd logon $FSTYP:$keydesc @s >>$seqres.full
228 }
229
230 #
231 # Generate a random encryption key, add it to the session keyring, and print out
232 # the resulting key descriptor (example: "8bf798e1a494e1ec").  Requires the
233 # keyctl program.  It's assumed the caller has already set up a test-scoped
234 # session keyring using _new_session_keyring.
235 #
236 _generate_session_encryption_key()
237 {
238         local keydesc=$(_generate_key_descriptor)
239         local raw=$(_generate_raw_encryption_key)
240
241         _add_session_encryption_key $keydesc $raw
242
243         echo $keydesc
244 }
245
246 # Unlink an encryption key from the session keyring, given its key descriptor.
247 _unlink_session_encryption_key()
248 {
249         local keydesc=$1
250         local keyid=$($KEYCTL_PROG search @s logon $FSTYP:$keydesc)
251         $KEYCTL_PROG unlink $keyid >>$seqres.full
252 }
253
254 # Revoke an encryption key from the session keyring, given its key descriptor.
255 _revoke_session_encryption_key()
256 {
257         local keydesc=$1
258         local keyid=$($KEYCTL_PROG search @s logon $FSTYP:$keydesc)
259         $KEYCTL_PROG revoke $keyid >>$seqres.full
260 }
261
262 # Set an encryption policy on the specified directory.
263 _set_encpolicy()
264 {
265         local dir=$1
266         shift
267
268         $XFS_IO_PROG -c "set_encpolicy $*" "$dir"
269 }
270
271 _user_do_set_encpolicy()
272 {
273         local dir=$1
274         shift
275
276         _user_do "$XFS_IO_PROG -c \"set_encpolicy $*\" \"$dir\""
277 }
278
279 # Display the specified file or directory's encryption policy.
280 _get_encpolicy()
281 {
282         local file=$1
283         shift
284
285         $XFS_IO_PROG -c "get_encpolicy $*" "$file"
286 }
287
288 _user_do_get_encpolicy()
289 {
290         local file=$1
291         shift
292
293         _user_do "$XFS_IO_PROG -c \"get_encpolicy $*\" \"$file\""
294 }
295
296 # Add an encryption key to the given filesystem.
297 _add_enckey()
298 {
299         local mnt=$1
300         local raw_key=$2
301         shift 2
302
303         echo -ne "$raw_key" | $XFS_IO_PROG -c "add_enckey $*" "$mnt"
304 }
305
306 _user_do_add_enckey()
307 {
308         local mnt=$1
309         local raw_key=$2
310         shift 2
311
312         _user_do "echo -ne \"$raw_key\" | $XFS_IO_PROG -c \"add_enckey $*\" \"$mnt\""
313 }
314
315 # Remove the given encryption key from the given filesystem.
316 _rm_enckey()
317 {
318         local mnt=$1
319         local keyspec=$2
320         shift 2
321
322         $XFS_IO_PROG -c "rm_enckey $* $keyspec" "$mnt"
323 }
324
325 _user_do_rm_enckey()
326 {
327         local mnt=$1
328         local keyspec=$2
329         shift 2
330
331         _user_do "$XFS_IO_PROG -c \"rm_enckey $* $keyspec\" \"$mnt\""
332 }
333
334 # Get the status of the given encryption key on the given filesystem.
335 _enckey_status()
336 {
337         local mnt=$1
338         local keyspec=$2
339         shift 2
340
341         $XFS_IO_PROG -c "enckey_status $* $keyspec" "$mnt"
342 }
343
344 _user_do_enckey_status()
345 {
346         local mnt=$1
347         local keyspec=$2
348         shift 2
349
350         _user_do "$XFS_IO_PROG -c \"enckey_status $* $keyspec\" \"$mnt\""
351 }
352
353 # Retrieve the encryption nonce of the given inode as a hex string.  The nonce
354 # was randomly generated by the filesystem and isn't exposed directly to
355 # userspace.  But it can be read using the filesystem's debugging tools.
356 _get_encryption_nonce()
357 {
358         local device=$1
359         local inode=$2
360
361         case $FSTYP in
362         ext4)
363                 # Use debugfs to dump the special xattr named "c", which is the
364                 # file's fscrypt_context.  This produces a line like:
365                 #
366                 #       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
367                 #
368                 # Then filter it to get just the 16-byte 'nonce' field at the end:
369                 #
370                 #       efbd18765df6414ec0a2cd5f91297e12
371                 #
372                 $DEBUGFS_PROG $device -R "ea_get <$inode> c" 2>>$seqres.full \
373                         | grep '^c ' | sed 's/^.*=//' | tr -d ' \n' | tail -c 32
374                 ;;
375         f2fs)
376                 # dump.f2fs prints the fscrypt_context like:
377                 #
378                 #       xattr: e_name_index:9 e_name:c e_name_len:1 e_value_size:28 e_value:
379                 #       format: 1
380                 #       contents_encryption_mode: 0x1
381                 #       filenames_encryption_mode: 0x4
382                 #       flags: 0x2
383                 #       master_key_descriptor: 0000000000000000
384                 #       nonce: EFBD18765DF6414EC0A2CD5F91297E12
385                 #
386                 # Also support the case where the whole xattr is printed as hex,
387                 # as is the case for fscrypt_context_v2.
388                 #
389                 #       xattr: e_name_index:9 e_name:c e_name_len:1 e_value_size:40 e_value:
390                 #       020104020000000033809BFEBE68A4AD264079B30861DD5E6B9E72D07523C58794ACF52534BAA756
391                 #
392                 $DUMP_F2FS_PROG -i $inode $device | awk '
393                         /\<e_name:c\>/ { found = 1 }
394                         (/^nonce:/ || /^[[:xdigit:]]+$/) && found {
395                                 print substr($0, length($0) - 31, 32);
396                                 found = 0;
397                         }'
398                 ;;
399         *)
400                 _fail "_get_encryption_nonce() isn't implemented on $FSTYP"
401                 ;;
402         esac
403 }
404
405 # Require support for _get_encryption_nonce()
406 _require_get_encryption_nonce_support()
407 {
408         echo "Checking for _get_encryption_nonce() support for $FSTYP" >> $seqres.full
409         case $FSTYP in
410         ext4)
411                 _require_command "$DEBUGFS_PROG" debugfs
412                 ;;
413         f2fs)
414                 _require_command "$DUMP_F2FS_PROG" dump.f2fs
415                 # For fscrypt_context_v2, we actually need a f2fs-tools version
416                 # that has the patch "f2fs-tools: improve xattr value printing"
417                 # (https://sourceforge.net/p/linux-f2fs/mailman/message/36648640/).
418                 # Otherwise the xattr is incorrectly parsed as v1.  But just let
419                 # the test fail in that case, as it was an f2fs-tools bug...
420                 ;;
421         *)
422                 _notrun "_get_encryption_nonce() isn't implemented on $FSTYP"
423                 ;;
424         esac
425 }
426
427 # Retrieve the encrypted filename stored on-disk for the given file.
428 # The name is printed to stdout in binary.
429 _get_ciphertext_filename()
430 {
431         local device=$1
432         local inode=$2
433         local dir_inode=$3
434
435         case $FSTYP in
436         ext4)
437                 # Extract the filename from the debugfs output line like:
438                 #
439                 #  131075  100644 (1)      0      0       0 22-Apr-2019 16:54 \xa2\x85\xb0z\x13\xe9\x09\x86R\xed\xdc\xce\xad\x14d\x19
440                 #
441                 # Bytes are shown either literally or as \xHH-style escape
442                 # sequences; we have to decode the escaped bytes here.
443                 #
444                 $DEBUGFS_PROG $device -R "ls -l -r <$dir_inode>" \
445                                         2>>$seqres.full | perl -ne '
446                         next if not /^\s*'$inode'\s+/;
447                         s/.*?\d\d:\d\d //;
448                         chomp;
449                         s/\\x([[:xdigit:]]{2})/chr hex $1/eg;
450                         print;'
451                 ;;
452         f2fs)
453                 # Extract the filename from the dump.f2fs output line like:
454                 #
455                 #  i_name                                       [UpkzIPuts9by1oDmE+Ivfw]
456                 #
457                 # The name is shown base64-encoded; we have to decode it here.
458                 #
459                 $DUMP_F2FS_PROG $device -i $inode | perl -ne '
460                         next if not /^i_name\s+\[([A-Za-z0-9+,]+)\]/;
461                         chomp $1;
462                         my @chars = split //, $1;
463                         my $ac = 0;
464                         my $bits = 0;
465                         my $table = join "", (A..Z, a..z, 0..9, "+", ",");
466                         foreach (@chars) {
467                                 $ac += index($table, $_) << $bits;
468                                 $bits += 6;
469                                 if ($bits >= 8) {
470                                         print chr($ac & 0xff);
471                                         $ac >>= 8;
472                                         $bits -= 8;
473                                 }
474                         }
475                         if ($ac != 0) {
476                                 print STDERR "Invalid base64-encoded string!\n";
477                         }'
478                 ;;
479         *)
480                 _fail "_get_ciphertext_filename() isn't implemented on $FSTYP"
481                 ;;
482         esac
483 }
484
485 # Require support for _get_ciphertext_filename().
486 _require_get_ciphertext_filename_support()
487 {
488         echo "Checking for _get_ciphertext_filename() support for $FSTYP" >> $seqres.full
489         case $FSTYP in
490         ext4)
491                 # Verify that the "ls -l -r" debugfs command is supported and
492                 # that it hex-encodes non-ASCII characters, rather than using an
493                 # ambiguous escaping method.  This requires e2fsprogs v1.45.1 or
494                 # later; or more specifically, a version that has the commit
495                 # "debugfs: avoid ambiguity when printing filenames".
496                 _require_command "$DEBUGFS_PROG" debugfs
497                 _scratch_mount
498                 touch $SCRATCH_MNT/$'\xc1'
499                 _scratch_unmount
500                 if ! $DEBUGFS_PROG $SCRATCH_DEV -R "ls -l -r /" 2>&1 \
501                         | tee -a $seqres.full | grep -E -q '\s+\\xc1\s*$'; then
502                         _notrun "debugfs (e2fsprogs) is too old; doesn't support showing unambiguous on-disk filenames"
503                 fi
504                 ;;
505         f2fs)
506                 # Verify that dump.f2fs shows encrypted filenames in full.  This
507                 # requires f2fs-tools v1.13.0 or later; or more specifically, a
508                 # version that has the commit
509                 # "f2fs-tools: improve filename printing".
510
511                 _require_command "$DUMP_F2FS_PROG" dump.f2fs
512                 _require_command "$KEYCTL_PROG" keyctl
513                 _scratch_mount
514                 _new_session_keyring
515
516                 local keydesc=$(_generate_session_encryption_key)
517                 local dir=$SCRATCH_MNT/test.${FUNCNAME[0]}
518                 local file=$dir/$(perl -e 'print "A" x 255')
519                 mkdir $dir
520                 _set_encpolicy $dir $keydesc
521                 touch $file
522                 local inode=$(stat -c %i $file)
523
524                 _scratch_unmount
525                 $KEYCTL_PROG clear @s
526
527                 # 255-character filename should result in 340 base64 characters.
528                 if ! $DUMP_F2FS_PROG -i $inode $SCRATCH_DEV \
529                         | grep -E -q '^i_name[[:space:]]+\[[A-Za-z0-9+,]{340}\]'; then
530                         _notrun "dump.f2fs (f2fs-tools) is too old; doesn't support showing unambiguous on-disk filenames"
531                 fi
532                 ;;
533         *)
534                 _notrun "_get_ciphertext_filename() isn't implemented on $FSTYP"
535                 ;;
536         esac
537 }
538
539 # Get an encrypted file's list of on-disk blocks as a comma-separated list of
540 # block offsets from the start of the device.  "Blocks" are 512 bytes each here.
541 _get_ciphertext_block_list()
542 {
543         local file=$1
544
545         sync
546         $XFS_IO_PROG -c fiemap $file | perl -ne '
547                 next if not /^\s*\d+: \[\d+\.\.\d+\]: (\d+)\.\.(\d+)/;
548                 print $_ . "," foreach $1..$2;' | sed 's/,$//'
549 }
550
551 # Dump a block list that was previously saved by _get_ciphertext_block_list().
552 _dump_ciphertext_blocks()
553 {
554         local device=$1
555         local blocklist=$2
556         local block
557
558         for block in $(tr ',' ' ' <<< $blocklist); do
559                 dd if=$device bs=512 count=1 skip=$block status=none
560         done
561 }
562
563 _do_verify_ciphertext_for_encryption_policy()
564 {
565         local contents_encryption_mode=$1
566         local filenames_encryption_mode=$2
567         local policy_flags=$3
568         local set_encpolicy_args=$4
569         local keyspec=$5
570         local raw_key_hex=$6
571         local crypt_cmd="$here/src/fscrypt-crypt-util $7"
572
573         local blocksize=$(_get_block_size $SCRATCH_MNT)
574         local test_contents_files=()
575         local test_filenames_files=()
576         local i src dir dst inode blocklist \
577               padding_flag padding dir_inode len name f nonce decrypted_name
578
579         # Create files whose encrypted contents we'll verify.  For each, save
580         # the information: (copy of original file, inode number of encrypted
581         # file, comma-separated block list) into test_contents_files[].
582         echo "Creating files for contents verification" >> $seqres.full
583         i=1
584         rm -f $tmp.testfile_*
585         for src in /dev/zero /dev/urandom; do
586                 head -c $((4 * blocksize)) $src > $tmp.testfile_$i
587                 (( i++ ))
588         done
589         dir=$SCRATCH_MNT/encdir
590         mkdir $dir
591         _set_encpolicy $dir $keyspec $set_encpolicy_args -f $policy_flags
592         for src in $tmp.testfile_*; do
593                 dst=$dir/${src##*.}
594                 cp $src $dst
595                 inode=$(stat -c %i $dst)
596                 blocklist=$(_get_ciphertext_block_list $dst)
597                 test_contents_files+=("$src $inode $blocklist")
598         done
599
600         # Create files whose encrypted names we'll verify.  For each, save the
601         # information: (original filename, inode number of encrypted file, inode
602         # of parent directory, padding amount) into test_filenames_files[].  Try
603         # each padding amount: 4, 8, 16, or 32 bytes.  Also try various filename
604         # lengths, including boundary cases.  Assume NAME_MAX == 255.
605         echo "Creating files for filenames verification" >> $seqres.full
606         for padding_flag in 0 1 2 3; do
607                 padding=$((4 << padding_flag))
608                 dir=$SCRATCH_MNT/encdir.pad$padding
609                 mkdir $dir
610                 dir_inode=$(stat -c %i $dir)
611                 _set_encpolicy $dir $keyspec $set_encpolicy_args \
612                         -f $((policy_flags | padding_flag))
613                 for len in 1 3 15 16 17 32 100 254 255; do
614                         name=$(tr -d -C a-zA-Z0-9 < /dev/urandom | head -c $len)
615                         touch $dir/$name
616                         inode=$(stat -c %i $dir/$name)
617                         test_filenames_files+=("$name $inode $dir_inode $padding")
618                 done
619         done
620
621         # Now unmount the filesystem and verify the ciphertext we just wrote.
622         _scratch_unmount
623
624         echo "Verifying encrypted file contents" >> $seqres.full
625         for f in "${test_contents_files[@]}"; do
626                 read -r src inode blocklist <<< "$f"
627                 nonce=$(_get_encryption_nonce $SCRATCH_DEV $inode)
628                 _dump_ciphertext_blocks $SCRATCH_DEV $blocklist > $tmp.actual_contents
629                 $crypt_cmd $contents_encryption_mode $raw_key_hex \
630                         --file-nonce=$nonce --block-size=$blocksize \
631                         < $src > $tmp.expected_contents
632                 if ! cmp $tmp.expected_contents $tmp.actual_contents; then
633                         _fail "Expected encrypted contents != actual encrypted contents.  File: $f"
634                 fi
635                 $crypt_cmd $contents_encryption_mode $raw_key_hex --decrypt \
636                         --file-nonce=$nonce --block-size=$blocksize \
637                         < $tmp.actual_contents > $tmp.decrypted_contents
638                 if ! cmp $src $tmp.decrypted_contents; then
639                         _fail "Contents decryption sanity check failed.  File: $f"
640                 fi
641         done
642
643         echo "Verifying encrypted file names" >> $seqres.full
644         for f in "${test_filenames_files[@]}"; do
645                 read -r name inode dir_inode padding <<< "$f"
646                 nonce=$(_get_encryption_nonce $SCRATCH_DEV $dir_inode)
647                 _get_ciphertext_filename $SCRATCH_DEV $inode $dir_inode \
648                         > $tmp.actual_name
649                 echo -n "$name" | \
650                         $crypt_cmd $filenames_encryption_mode $raw_key_hex \
651                         --file-nonce=$nonce --padding=$padding \
652                         --block-size=255 > $tmp.expected_name
653                 if ! cmp $tmp.expected_name $tmp.actual_name; then
654                         _fail "Expected encrypted filename != actual encrypted filename.  File: $f"
655                 fi
656                 $crypt_cmd $filenames_encryption_mode $raw_key_hex --decrypt \
657                         --file-nonce=$nonce --padding=$padding \
658                         --block-size=255 < $tmp.actual_name \
659                         > $tmp.decrypted_name
660                 decrypted_name=$(tr -d '\0' < $tmp.decrypted_name)
661                 if [ "$name" != "$decrypted_name" ]; then
662                         _fail "Filename decryption sanity check failed ($name != $decrypted_name).  File: $f"
663                 fi
664         done
665 }
666
667 _fscrypt_mode_name_to_num()
668 {
669         local name=$1
670
671         case "$name" in
672         AES-256-XTS)            echo 1 ;; # FS_ENCRYPTION_MODE_AES_256_XTS
673         AES-256-CTS-CBC)        echo 4 ;; # FS_ENCRYPTION_MODE_AES_256_CTS
674         AES-128-CBC-ESSIV)      echo 5 ;; # FS_ENCRYPTION_MODE_AES_128_CBC
675         AES-128-CTS-CBC)        echo 6 ;; # FS_ENCRYPTION_MODE_AES_128_CTS
676         Adiantum)               echo 9 ;; # FS_ENCRYPTION_MODE_ADIANTUM
677         *)                      _fail "Unknown fscrypt mode: $name" ;;
678         esac
679 }
680
681 # Verify that file contents and names are encrypted correctly when an encryption
682 # policy of the specified type is used.
683 #
684 # The first two parameters are the contents and filenames encryption modes to
685 # test.  Optionally, also specify 'direct' to test the DIRECT_KEY flag, and/or
686 # 'v2' to test v2 policies.
687 _verify_ciphertext_for_encryption_policy()
688 {
689         local contents_encryption_mode=$1
690         local filenames_encryption_mode=$2
691         local opt
692         local policy_version=1
693         local policy_flags=0
694         local set_encpolicy_args=""
695         local crypt_util_args=""
696
697         shift 2
698         for opt; do
699                 case "$opt" in
700                 v2)
701                         policy_version=2
702                         ;;
703                 direct)
704                         if [ $contents_encryption_mode != \
705                              $filenames_encryption_mode ]; then
706                                 _fail "For direct key mode, contents and filenames modes must match"
707                         fi
708                         (( policy_flags |= 0x04 )) # FS_POLICY_FLAG_DIRECT_KEY
709                         ;;
710                 *)
711                         _fail "Unknown option '$opt' passed to ${FUNCNAME[0]}"
712                         ;;
713                 esac
714         done
715         local contents_mode_num=$(_fscrypt_mode_name_to_num $contents_encryption_mode)
716         local filenames_mode_num=$(_fscrypt_mode_name_to_num $filenames_encryption_mode)
717
718         set_encpolicy_args+=" -c $contents_mode_num"
719         set_encpolicy_args+=" -n $filenames_mode_num"
720
721         if (( policy_version > 1 )); then
722                 set_encpolicy_args+=" -v 2"
723                 crypt_util_args+=" --kdf=HKDF-SHA512"
724                 if (( policy_flags & 0x04 )); then
725                         crypt_util_args+=" --mode-num=$contents_mode_num"
726                 fi
727         else
728                 if (( policy_flags & 0x04 )); then
729                         crypt_util_args+=" --kdf=none"
730                 else
731                         crypt_util_args+=" --kdf=AES-128-ECB"
732                 fi
733         fi
734         set_encpolicy_args=${set_encpolicy_args# }
735
736         _require_scratch_encryption $set_encpolicy_args
737         _require_test_program "fscrypt-crypt-util"
738         _require_xfs_io_command "fiemap"
739         _require_get_encryption_nonce_support
740         _require_get_ciphertext_filename_support
741         if (( policy_version == 1 )); then
742                 _require_command "$KEYCTL_PROG" keyctl
743         fi
744
745         echo "Creating encryption-capable filesystem" >> $seqres.full
746         _scratch_mkfs_encrypted &>> $seqres.full
747         _scratch_mount
748
749         echo "Generating encryption key" >> $seqres.full
750         local raw_key=$(_generate_raw_encryption_key)
751         if (( policy_version > 1 )); then
752                 local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" \
753                                 | awk '{print $NF}')
754         else
755                 local keyspec=$(_generate_key_descriptor)
756                 _new_session_keyring
757                 _add_session_encryption_key $keyspec $raw_key
758         fi
759         local raw_key_hex=$(echo "$raw_key" | tr -d '\\x')
760
761         echo
762         echo -e "Verifying ciphertext with parameters:"
763         echo -e "\tcontents_encryption_mode: $contents_encryption_mode"
764         echo -e "\tfilenames_encryption_mode: $filenames_encryption_mode"
765         [ $# -ne 0 ] && echo -e "\toptions: $*"
766
767         _do_verify_ciphertext_for_encryption_policy \
768                 "$contents_encryption_mode" \
769                 "$filenames_encryption_mode" \
770                 "$policy_flags" \
771                 "$set_encpolicy_args" \
772                 "$keyspec" \
773                 "$raw_key_hex" \
774                 "$crypt_util_args"
775 }