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