xfs: replace open-coded calls to xfs_logprint with helpers
[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 #                             [-f POLICY_FLAGS] [-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_flags=0
73         local policy_version=1
74         local c
75
76         OPTIND=2
77         while getopts "c:n:f:v:" c; do
78                 case $c in
79                 c|n)
80                         set_encpolicy_args+=" -$c $OPTARG"
81                         ;;
82                 f)
83                         set_encpolicy_args+=" -$c $OPTARG"
84                         policy_flags=$OPTARG
85                         ;;
86                 v)
87                         set_encpolicy_args+=" -$c $OPTARG"
88                         policy_version=$OPTARG
89                         ;;
90                 *)
91                         _fail "Unrecognized option '$c'"
92                         ;;
93                 esac
94         done
95         set_encpolicy_args=${set_encpolicy_args# }
96
97         echo "Checking whether kernel supports encryption policy: $set_encpolicy_args" \
98                 >> $seqres.full
99
100         if (( policy_flags & (FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 |
101                               FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) )); then
102                 _scratch_unmount
103                 _scratch_mkfs_stable_inodes_encrypted &>> $seqres.full
104                 _scratch_mount
105         fi
106
107         mkdir $dir
108         if (( policy_version > 1 )); then
109                 _require_xfs_io_command "get_encpolicy" "-t"
110                 local output=$(_get_encpolicy $dir -t)
111                 if [ "$output" != "supported" ]; then
112                         if [ "$output" = "unsupported" ]; then
113                                 _notrun "kernel does not support $FSTYP encryption v2 API"
114                         fi
115                         _fail "Unexpected output from 'get_encpolicy -t': $output"
116                 fi
117                 # Both the kernel and xfs_io support v2 encryption policies, and
118                 # therefore also filesystem-level keys -- since that's the only
119                 # way to provide keys for v2 policies.
120                 local raw_key=$(_generate_raw_encryption_key)
121                 local keyspec=$(_add_enckey $mnt "$raw_key" | awk '{print $NF}')
122         else
123                 _require_command "$KEYCTL_PROG" keyctl
124                 _new_session_keyring
125                 local keyspec=$(_generate_session_encryption_key)
126         fi
127         if _set_encpolicy $dir $keyspec $set_encpolicy_args \
128                 2>&1 >>$seqres.full | egrep -q 'Invalid argument'; then
129                 _notrun "kernel does not support encryption policy: '$set_encpolicy_args'"
130         fi
131         # fscrypt allows setting policies with modes it knows about, even
132         # without kernel crypto API support.  E.g. a policy using Adiantum
133         # encryption can be set on a kernel without CONFIG_CRYPTO_ADIANTUM.
134         # But actually trying to use such an encrypted directory will fail.
135         # To reliably check for availability of both the contents and filenames
136         # encryption modes, try creating a nonempty file.
137         if ! echo foo > $dir/file; then
138                 _notrun "encryption policy '$set_encpolicy_args' is unusable; probably missing kernel crypto API support"
139         fi
140         if (( policy_version <= 1 )); then
141                 $KEYCTL_PROG clear @s
142         fi
143         rm -r $dir
144 }
145
146 _scratch_mkfs_encrypted()
147 {
148         case $FSTYP in
149         ext4|f2fs)
150                 _scratch_mkfs -O encrypt
151                 ;;
152         ubifs)
153                 # erase the UBI volume; reformated automatically on next mount
154                 $UBIUPDATEVOL_PROG ${SCRATCH_DEV} -t
155                 ;;
156         *)
157                 _notrun "No encryption support for $FSTYP"
158                 ;;
159         esac
160 }
161
162 _scratch_mkfs_sized_encrypted()
163 {
164         case $FSTYP in
165         ext4|f2fs)
166                 MKFS_OPTIONS="$MKFS_OPTIONS -O encrypt" _scratch_mkfs_sized $*
167                 ;;
168         *)
169                 _notrun "Filesystem $FSTYP not supported in _scratch_mkfs_sized_encrypted"
170                 ;;
171         esac
172 }
173
174 # Like _scratch_mkfs_encrypted(), but add -O stable_inodes if applicable for the
175 # filesystem type.  This is necessary for using encryption policies that include
176 # the inode number in the IVs, e.g. policies with the IV_INO_LBLK_64 flag set.
177 _scratch_mkfs_stable_inodes_encrypted()
178 {
179         case $FSTYP in
180         ext4)
181                 if ! _scratch_mkfs -O encrypt -O stable_inodes; then
182                         _notrun "-O stable_inodes is not supported"
183                 fi
184                 ;;
185         *)
186                 _scratch_mkfs_encrypted
187                 ;;
188         esac
189 }
190
191 # For some tests it's helpful to always use the same key so that the test's
192 # output is always the same.  For this purpose the following key can be used:
193 TEST_RAW_KEY=
194 for i in {1..64}; do
195         TEST_RAW_KEY+="\\x$(printf "%02x" $i)"
196 done
197 # Key descriptor: arbitrary value
198 TEST_KEY_DESCRIPTOR="0000111122223333"
199 # Key identifier: HKDF-SHA512(key=$TEST_RAW_KEY, salt="", info="fscrypt\0\x01")
200 TEST_KEY_IDENTIFIER="69b2f6edeee720cce0577937eb8a6751"
201
202 # Give the invoking shell a new session keyring.  This makes any keys we add to
203 # the session keyring scoped to the lifetime of the test script.
204 _new_session_keyring()
205 {
206         $KEYCTL_PROG new_session >>$seqres.full
207 }
208
209 # Generate a key descriptor (16 character hex string)
210 _generate_key_descriptor()
211 {
212         local keydesc=""
213         local i
214         for ((i = 0; i < 8; i++)); do
215                 keydesc="${keydesc}$(printf "%02x" $(( $RANDOM % 256 )))"
216         done
217         echo $keydesc
218 }
219
220 # Generate a raw encryption key, but don't add it to any keyring yet.
221 _generate_raw_encryption_key()
222 {
223         local raw=""
224         local i
225         for ((i = 0; i < 64; i++)); do
226                 raw="${raw}\\x$(printf "%02x" $(( $RANDOM % 256 )))"
227         done
228         echo $raw
229 }
230
231 # Serialize an integer into a CPU-endian bytestring of the given length, and
232 # print it as a string where each byte is hex-escaped.  For example,
233 # `_num_to_hex 1000 4` == "\xe8\x03\x00\x00" if the CPU is little endian.
234 _num_to_hex()
235 {
236         local value=$1
237         local nbytes=$2
238         local i
239         local big_endian=$(echo -ne '\x11' | od -tx2 | head -1 | \
240                            cut -f2 -d' ' | cut -c1)
241
242         if (( big_endian )); then
243                 for (( i = 0; i < nbytes; i++ )); do
244                         printf '\\x%02x' $(((value >> (8*(nbytes-1-i))) & 0xff))
245                 done
246         else
247                 for (( i = 0; i < nbytes; i++ )); do
248                         printf '\\x%02x' $(((value >> (8*i)) & 0xff))
249                 done
250         fi
251 }
252
253 # Add the specified raw encryption key to the session keyring, using the
254 # specified key descriptor.
255 _add_session_encryption_key()
256 {
257         local keydesc=$1
258         local raw=$2
259
260         #
261         # Add the key to the session keyring.  The required structure is:
262         #
263         #       #define FSCRYPT_MAX_KEY_SIZE 64
264         #       struct fscrypt_key {
265         #               __u32 mode;
266         #               __u8 raw[FSCRYPT_MAX_KEY_SIZE];
267         #               __u32 size;
268         #       };
269         #
270         # The kernel ignores 'mode' but requires that 'size' be 64.
271         #
272         # Keys are named $FSTYP:KEYDESC where KEYDESC is the 16-character key
273         # descriptor hex string.  Newer kernels (ext4 4.8 and later, f2fs 4.6
274         # and later) also allow the common key prefix "fscrypt:" in addition to
275         # their filesystem-specific key prefix ("ext4:", "f2fs:").  It would be
276         # nice to use the common key prefix, but for now use the filesystem-
277         # specific prefix to make it possible to test older kernels...
278         #
279         local mode=$(_num_to_hex 0 4)
280         local size=$(_num_to_hex 64 4)
281         echo -n -e "${mode}${raw}${size}" |
282                 $KEYCTL_PROG padd logon $FSTYP:$keydesc @s >>$seqres.full
283 }
284
285 #
286 # Generate a random encryption key, add it to the session keyring, and print out
287 # the resulting key descriptor (example: "8bf798e1a494e1ec").  Requires the
288 # keyctl program.  It's assumed the caller has already set up a test-scoped
289 # session keyring using _new_session_keyring.
290 #
291 _generate_session_encryption_key()
292 {
293         local keydesc=$(_generate_key_descriptor)
294         local raw=$(_generate_raw_encryption_key)
295
296         _add_session_encryption_key $keydesc $raw
297
298         echo $keydesc
299 }
300
301 # Unlink an encryption key from the session keyring, given its key descriptor.
302 _unlink_session_encryption_key()
303 {
304         local keydesc=$1
305         local keyid=$($KEYCTL_PROG search @s logon $FSTYP:$keydesc)
306         $KEYCTL_PROG unlink $keyid >>$seqres.full
307 }
308
309 # Revoke an encryption key from the session keyring, given its key descriptor.
310 _revoke_session_encryption_key()
311 {
312         local keydesc=$1
313         local keyid=$($KEYCTL_PROG search @s logon $FSTYP:$keydesc)
314         $KEYCTL_PROG revoke $keyid >>$seqres.full
315 }
316
317 # Set an encryption policy on the specified directory.
318 _set_encpolicy()
319 {
320         local dir=$1
321         shift
322
323         $XFS_IO_PROG -c "set_encpolicy $*" "$dir"
324 }
325
326 _user_do_set_encpolicy()
327 {
328         local dir=$1
329         shift
330
331         _user_do "$XFS_IO_PROG -c \"set_encpolicy $*\" \"$dir\""
332 }
333
334 # Display the specified file or directory's encryption policy.
335 _get_encpolicy()
336 {
337         local file=$1
338         shift
339
340         $XFS_IO_PROG -c "get_encpolicy $*" "$file"
341 }
342
343 _user_do_get_encpolicy()
344 {
345         local file=$1
346         shift
347
348         _user_do "$XFS_IO_PROG -c \"get_encpolicy $*\" \"$file\""
349 }
350
351 # Add an encryption key to the given filesystem.
352 _add_enckey()
353 {
354         local mnt=$1
355         local raw_key=$2
356         shift 2
357
358         echo -ne "$raw_key" | $XFS_IO_PROG -c "add_enckey $*" "$mnt"
359 }
360
361 _user_do_add_enckey()
362 {
363         local mnt=$1
364         local raw_key=$2
365         shift 2
366
367         _user_do "echo -ne \"$raw_key\" | $XFS_IO_PROG -c \"add_enckey $*\" \"$mnt\""
368 }
369
370 # Remove the given encryption key from the given filesystem.
371 _rm_enckey()
372 {
373         local mnt=$1
374         local keyspec=$2
375         shift 2
376
377         $XFS_IO_PROG -c "rm_enckey $* $keyspec" "$mnt"
378 }
379
380 _user_do_rm_enckey()
381 {
382         local mnt=$1
383         local keyspec=$2
384         shift 2
385
386         _user_do "$XFS_IO_PROG -c \"rm_enckey $* $keyspec\" \"$mnt\""
387 }
388
389 # Get the status of the given encryption key on the given filesystem.
390 _enckey_status()
391 {
392         local mnt=$1
393         local keyspec=$2
394         shift 2
395
396         $XFS_IO_PROG -c "enckey_status $* $keyspec" "$mnt"
397 }
398
399 _user_do_enckey_status()
400 {
401         local mnt=$1
402         local keyspec=$2
403         shift 2
404
405         _user_do "$XFS_IO_PROG -c \"enckey_status $* $keyspec\" \"$mnt\""
406 }
407
408 # Require support for adding a key to a filesystem's fscrypt keyring via an
409 # "fscrypt-provisioning" keyring key.
410 _require_add_enckey_by_key_id()
411 {
412         local mnt=$1
413
414         # Userspace support
415         _require_xfs_io_command "add_enckey" "-k"
416
417         # Kernel support
418         if $XFS_IO_PROG -c "add_enckey -k 12345" "$mnt" \
419                 |& grep -q 'Invalid argument'; then
420                 _notrun "Kernel doesn't support key_id parameter to FS_IOC_ADD_ENCRYPTION_KEY"
421         fi
422 }
423
424 # Add a key of type "fscrypt-provisioning" to the session keyring and print the
425 # resulting key ID.
426 _add_fscrypt_provisioning_key()
427 {
428         local desc=$1
429         local type=$2
430         local raw=$3
431
432         # The format of the key payload must be:
433         #
434         #       struct fscrypt_provisioning_key_payload {
435         #               __u32 type;
436         #               __u32 __reserved;
437         #               __u8 raw[];
438         #       };
439         #
440         local type_hex=$(_num_to_hex $type 4)
441         local reserved=$(_num_to_hex 0 4)
442         echo -n -e "${type_hex}${reserved}${raw}" |
443                 $KEYCTL_PROG padd fscrypt-provisioning "$desc" @s
444 }
445
446 # Retrieve the encryption nonce of the given inode as a hex string.  The nonce
447 # was randomly generated by the filesystem and isn't exposed directly to
448 # userspace.  But it can be read using the filesystem's debugging tools.
449 _get_encryption_nonce()
450 {
451         local device=$1
452         local inode=$2
453
454         case $FSTYP in
455         ext4)
456                 # Use debugfs to dump the special xattr named "c", which is the
457                 # file's fscrypt_context.  This produces a line like:
458                 #
459                 #       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
460                 #
461                 # Then filter it to get just the 16-byte 'nonce' field at the end:
462                 #
463                 #       efbd18765df6414ec0a2cd5f91297e12
464                 #
465                 $DEBUGFS_PROG $device -R "ea_get <$inode> c" 2>>$seqres.full \
466                         | grep '^c ' | sed 's/^.*=//' | tr -d ' \n' | tail -c 32
467                 ;;
468         f2fs)
469                 # dump.f2fs prints the fscrypt_context like:
470                 #
471                 #       xattr: e_name_index:9 e_name:c e_name_len:1 e_value_size:28 e_value:
472                 #       format: 1
473                 #       contents_encryption_mode: 0x1
474                 #       filenames_encryption_mode: 0x4
475                 #       flags: 0x2
476                 #       master_key_descriptor: 0000000000000000
477                 #       nonce: EFBD18765DF6414EC0A2CD5F91297E12
478                 #
479                 # Also support the case where the whole xattr is printed as hex,
480                 # as is the case for fscrypt_context_v2.
481                 #
482                 #       xattr: e_name_index:9 e_name:c e_name_len:1 e_value_size:40 e_value:
483                 #       020104020000000033809BFEBE68A4AD264079B30861DD5E6B9E72D07523C58794ACF52534BAA756
484                 #
485                 $DUMP_F2FS_PROG -i $inode $device | awk '
486                         /\<e_name:c\>/ { found = 1 }
487                         (/^nonce:/ || /^[[:xdigit:]]+$/) && found {
488                                 print substr($0, length($0) - 31, 32);
489                                 found = 0;
490                         }'
491                 ;;
492         *)
493                 _fail "_get_encryption_nonce() isn't implemented on $FSTYP"
494                 ;;
495         esac
496 }
497
498 # Require support for _get_encryption_nonce()
499 _require_get_encryption_nonce_support()
500 {
501         echo "Checking for _get_encryption_nonce() support for $FSTYP" >> $seqres.full
502         case $FSTYP in
503         ext4)
504                 _require_command "$DEBUGFS_PROG" debugfs
505                 ;;
506         f2fs)
507                 _require_command "$DUMP_F2FS_PROG" dump.f2fs
508                 # For fscrypt_context_v2, we actually need a f2fs-tools version
509                 # that has the patch "f2fs-tools: improve xattr value printing"
510                 # (https://sourceforge.net/p/linux-f2fs/mailman/message/36648640/).
511                 # Otherwise the xattr is incorrectly parsed as v1.  But just let
512                 # the test fail in that case, as it was an f2fs-tools bug...
513                 ;;
514         *)
515                 _notrun "_get_encryption_nonce() isn't implemented on $FSTYP"
516                 ;;
517         esac
518 }
519
520 # Retrieve the encrypted filename stored on-disk for the given file.
521 # The name is printed to stdout in binary.
522 _get_ciphertext_filename()
523 {
524         local device=$1
525         local inode=$2
526         local dir_inode=$3
527
528         case $FSTYP in
529         ext4)
530                 # Extract the filename from the debugfs output line like:
531                 #
532                 #  131075  100644 (1)      0      0       0 22-Apr-2019 16:54 \xa2\x85\xb0z\x13\xe9\x09\x86R\xed\xdc\xce\xad\x14d\x19
533                 #
534                 # Bytes are shown either literally or as \xHH-style escape
535                 # sequences; we have to decode the escaped bytes here.
536                 #
537                 $DEBUGFS_PROG $device -R "ls -l -r <$dir_inode>" \
538                                         2>>$seqres.full | perl -ne '
539                         next if not /^\s*'$inode'\s+/;
540                         s/.*?\d\d:\d\d //;
541                         chomp;
542                         s/\\x([[:xdigit:]]{2})/chr hex $1/eg;
543                         print;'
544                 ;;
545         f2fs)
546                 # Extract the filename from the dump.f2fs output line like:
547                 #
548                 #  i_name                                       [UpkzIPuts9by1oDmE+Ivfw]
549                 #
550                 # The name is shown base64-encoded; we have to decode it here.
551                 #
552                 $DUMP_F2FS_PROG $device -i $inode | perl -ne '
553                         next if not /^i_name\s+\[([A-Za-z0-9+,]+)\]/;
554                         chomp $1;
555                         my @chars = split //, $1;
556                         my $ac = 0;
557                         my $bits = 0;
558                         my $table = join "", (A..Z, a..z, 0..9, "+", ",");
559                         foreach (@chars) {
560                                 $ac += index($table, $_) << $bits;
561                                 $bits += 6;
562                                 if ($bits >= 8) {
563                                         print chr($ac & 0xff);
564                                         $ac >>= 8;
565                                         $bits -= 8;
566                                 }
567                         }
568                         if ($ac != 0) {
569                                 print STDERR "Invalid base64-encoded string!\n";
570                         }'
571                 ;;
572         *)
573                 _fail "_get_ciphertext_filename() isn't implemented on $FSTYP"
574                 ;;
575         esac
576 }
577
578 # Require support for _get_ciphertext_filename().
579 _require_get_ciphertext_filename_support()
580 {
581         echo "Checking for _get_ciphertext_filename() support for $FSTYP" >> $seqres.full
582         case $FSTYP in
583         ext4)
584                 # Verify that the "ls -l -r" debugfs command is supported and
585                 # that it hex-encodes non-ASCII characters, rather than using an
586                 # ambiguous escaping method.  This requires e2fsprogs v1.45.1 or
587                 # later; or more specifically, a version that has the commit
588                 # "debugfs: avoid ambiguity when printing filenames".
589                 _require_command "$DEBUGFS_PROG" debugfs
590                 _scratch_mount
591                 touch $SCRATCH_MNT/$'\xc1'
592                 _scratch_unmount
593                 if ! $DEBUGFS_PROG $SCRATCH_DEV -R "ls -l -r /" 2>&1 \
594                         | tee -a $seqres.full | grep -E -q '\s+\\xc1\s*$'; then
595                         _notrun "debugfs (e2fsprogs) is too old; doesn't support showing unambiguous on-disk filenames"
596                 fi
597                 ;;
598         f2fs)
599                 # Verify that dump.f2fs shows encrypted filenames in full.  This
600                 # requires f2fs-tools v1.13.0 or later; or more specifically, a
601                 # version that has the commit
602                 # "f2fs-tools: improve filename printing".
603
604                 _require_command "$DUMP_F2FS_PROG" dump.f2fs
605                 _require_command "$KEYCTL_PROG" keyctl
606                 _scratch_mount
607                 _new_session_keyring
608
609                 local keydesc=$(_generate_session_encryption_key)
610                 local dir=$SCRATCH_MNT/test.${FUNCNAME[0]}
611                 local file=$dir/$(perl -e 'print "A" x 255')
612                 mkdir $dir
613                 _set_encpolicy $dir $keydesc
614                 touch $file
615                 local inode=$(stat -c %i $file)
616
617                 _scratch_unmount
618                 $KEYCTL_PROG clear @s
619
620                 # 255-character filename should result in 340 base64 characters.
621                 if ! $DUMP_F2FS_PROG -i $inode $SCRATCH_DEV \
622                         | grep -E -q '^i_name[[:space:]]+\[[A-Za-z0-9+,]{340}\]'; then
623                         _notrun "dump.f2fs (f2fs-tools) is too old; doesn't support showing unambiguous on-disk filenames"
624                 fi
625                 ;;
626         *)
627                 _notrun "_get_ciphertext_filename() isn't implemented on $FSTYP"
628                 ;;
629         esac
630 }
631
632 # Get an encrypted file's list of on-disk blocks as a comma-separated list of
633 # block offsets from the start of the device.  "Blocks" are 512 bytes each here.
634 _get_ciphertext_block_list()
635 {
636         local file=$1
637
638         sync
639         $XFS_IO_PROG -c fiemap $file | perl -ne '
640                 next if not /^\s*\d+: \[\d+\.\.\d+\]: (\d+)\.\.(\d+)/;
641                 print $_ . "," foreach $1..$2;' | sed 's/,$//'
642 }
643
644 # Dump a block list that was previously saved by _get_ciphertext_block_list().
645 _dump_ciphertext_blocks()
646 {
647         local device=$1
648         local blocklist=$2
649         local block
650
651         for block in $(tr ',' ' ' <<< $blocklist); do
652                 dd if=$device bs=512 count=1 skip=$block status=none
653         done
654 }
655
656 _do_verify_ciphertext_for_encryption_policy()
657 {
658         local contents_encryption_mode=$1
659         local filenames_encryption_mode=$2
660         local policy_flags=$3
661         local set_encpolicy_args=$4
662         local keyspec=$5
663         local raw_key_hex=$6
664         local crypt_contents_cmd="$here/src/fscrypt-crypt-util $7"
665         local crypt_filename_cmd="$here/src/fscrypt-crypt-util $8"
666
667         local blocksize=$(_get_block_size $SCRATCH_MNT)
668         local test_contents_files=()
669         local test_filenames_files=()
670         local i src dir dst inode blocklist \
671               padding_flag padding dir_inode len name f nonce decrypted_name
672
673         # Create files whose encrypted contents we'll verify.  For each, save
674         # the information: (copy of original file, inode number of encrypted
675         # file, comma-separated block list) into test_contents_files[].
676         echo "Creating files for contents verification" >> $seqres.full
677         i=1
678         rm -f $tmp.testfile_*
679         for src in /dev/zero /dev/urandom; do
680                 head -c $((4 * blocksize)) $src > $tmp.testfile_$i
681                 (( i++ ))
682         done
683         dir=$SCRATCH_MNT/encdir
684         mkdir $dir
685         _set_encpolicy $dir $keyspec $set_encpolicy_args -f $policy_flags
686         for src in $tmp.testfile_*; do
687                 dst=$dir/${src##*.}
688                 cp $src $dst
689                 inode=$(stat -c %i $dst)
690                 blocklist=$(_get_ciphertext_block_list $dst)
691                 test_contents_files+=("$src $inode $blocklist")
692         done
693
694         # Create files whose encrypted names we'll verify.  For each, save the
695         # information: (original filename, inode number of encrypted file, inode
696         # of parent directory, padding amount) into test_filenames_files[].  Try
697         # each padding amount: 4, 8, 16, or 32 bytes.  Also try various filename
698         # lengths, including boundary cases.  Assume NAME_MAX == 255.
699         echo "Creating files for filenames verification" >> $seqres.full
700         for padding_flag in 0 1 2 3; do
701                 padding=$((4 << padding_flag))
702                 dir=$SCRATCH_MNT/encdir.pad$padding
703                 mkdir $dir
704                 dir_inode=$(stat -c %i $dir)
705                 _set_encpolicy $dir $keyspec $set_encpolicy_args \
706                         -f $((policy_flags | padding_flag))
707                 for len in 1 3 15 16 17 32 100 254 255; do
708                         name=$(tr -d -C a-zA-Z0-9 < /dev/urandom | head -c $len)
709                         touch $dir/$name
710                         inode=$(stat -c %i $dir/$name)
711                         test_filenames_files+=("$name $inode $dir_inode $padding")
712                 done
713         done
714
715         # Now unmount the filesystem and verify the ciphertext we just wrote.
716         _scratch_unmount
717
718         echo "Verifying encrypted file contents" >> $seqres.full
719         for f in "${test_contents_files[@]}"; do
720                 read -r src inode blocklist <<< "$f"
721                 nonce=$(_get_encryption_nonce $SCRATCH_DEV $inode)
722                 _dump_ciphertext_blocks $SCRATCH_DEV $blocklist > $tmp.actual_contents
723                 $crypt_contents_cmd $contents_encryption_mode $raw_key_hex \
724                         --file-nonce=$nonce --block-size=$blocksize \
725                         --inode-number=$inode < $src > $tmp.expected_contents
726                 if ! cmp $tmp.expected_contents $tmp.actual_contents; then
727                         _fail "Expected encrypted contents != actual encrypted contents.  File: $f"
728                 fi
729                 $crypt_contents_cmd $contents_encryption_mode $raw_key_hex \
730                         --decrypt --file-nonce=$nonce --block-size=$blocksize \
731                         --inode-number=$inode \
732                         < $tmp.actual_contents > $tmp.decrypted_contents
733                 if ! cmp $src $tmp.decrypted_contents; then
734                         _fail "Contents decryption sanity check failed.  File: $f"
735                 fi
736         done
737
738         echo "Verifying encrypted file names" >> $seqres.full
739         for f in "${test_filenames_files[@]}"; do
740                 read -r name inode dir_inode padding <<< "$f"
741                 nonce=$(_get_encryption_nonce $SCRATCH_DEV $dir_inode)
742                 _get_ciphertext_filename $SCRATCH_DEV $inode $dir_inode \
743                         > $tmp.actual_name
744                 echo -n "$name" | \
745                         $crypt_filename_cmd $filenames_encryption_mode \
746                         $raw_key_hex --file-nonce=$nonce --padding=$padding \
747                         --block-size=255 --inode-number=$dir_inode \
748                         > $tmp.expected_name
749                 if ! cmp $tmp.expected_name $tmp.actual_name; then
750                         _fail "Expected encrypted filename != actual encrypted filename.  File: $f"
751                 fi
752                 $crypt_filename_cmd $filenames_encryption_mode $raw_key_hex \
753                         --decrypt --file-nonce=$nonce --padding=$padding \
754                         --block-size=255 --inode-number=$dir_inode \
755                         < $tmp.actual_name > $tmp.decrypted_name
756                 decrypted_name=$(tr -d '\0' < $tmp.decrypted_name)
757                 if [ "$name" != "$decrypted_name" ]; then
758                         _fail "Filename decryption sanity check failed ($name != $decrypted_name).  File: $f"
759                 fi
760         done
761 }
762
763 # fscrypt UAPI constants (see <linux/fscrypt.h>)
764
765 FSCRYPT_MODE_AES_256_XTS=1
766 FSCRYPT_MODE_AES_256_CTS=4
767 FSCRYPT_MODE_AES_128_CBC=5
768 FSCRYPT_MODE_AES_128_CTS=6
769 FSCRYPT_MODE_ADIANTUM=9
770
771 FSCRYPT_POLICY_FLAG_DIRECT_KEY=0x04
772 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64=0x08
773 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32=0x10
774
775 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR=1
776 FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER=2
777
778 _fscrypt_mode_name_to_num()
779 {
780         local name=$1
781
782         case "$name" in
783         AES-256-XTS)            echo $FSCRYPT_MODE_AES_256_XTS ;;
784         AES-256-CTS-CBC)        echo $FSCRYPT_MODE_AES_256_CTS ;;
785         AES-128-CBC-ESSIV)      echo $FSCRYPT_MODE_AES_128_CBC ;;
786         AES-128-CTS-CBC)        echo $FSCRYPT_MODE_AES_128_CTS ;;
787         Adiantum)               echo $FSCRYPT_MODE_ADIANTUM ;;
788         *)                      _fail "Unknown fscrypt mode: $name" ;;
789         esac
790 }
791
792 # Verify that file contents and names are encrypted correctly when an encryption
793 # policy of the specified type is used.
794 #
795 # The first two parameters are the contents and filenames encryption modes to
796 # test.  The following optional parameters are also accepted to further modify
797 # the type of encryption policy that is tested:
798 #
799 #       'v2':                   test a v2 encryption policy
800 #       'direct':               test the DIRECT_KEY policy flag
801 #       'iv_ino_lblk_64':       test the IV_INO_LBLK_64 policy flag
802 #       'iv_ino_lblk_32':       test the IV_INO_LBLK_32 policy flag
803 #
804 _verify_ciphertext_for_encryption_policy()
805 {
806         local contents_encryption_mode=$1
807         local filenames_encryption_mode=$2
808         local opt
809         local policy_version=1
810         local policy_flags=0
811         local set_encpolicy_args=""
812         local crypt_util_args=""
813         local crypt_util_contents_args=""
814         local crypt_util_filename_args=""
815
816         shift 2
817         for opt; do
818                 case "$opt" in
819                 v2)
820                         policy_version=2
821                         ;;
822                 direct)
823                         if [ $contents_encryption_mode != \
824                              $filenames_encryption_mode ]; then
825                                 _fail "For direct key mode, contents and filenames modes must match"
826                         fi
827                         (( policy_flags |= FSCRYPT_POLICY_FLAG_DIRECT_KEY ))
828                         ;;
829                 iv_ino_lblk_64)
830                         (( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 ))
831                         ;;
832                 iv_ino_lblk_32)
833                         (( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 ))
834                         ;;
835                 *)
836                         _fail "Unknown option '$opt' passed to ${FUNCNAME[0]}"
837                         ;;
838                 esac
839         done
840         local contents_mode_num=$(_fscrypt_mode_name_to_num $contents_encryption_mode)
841         local filenames_mode_num=$(_fscrypt_mode_name_to_num $filenames_encryption_mode)
842
843         set_encpolicy_args+=" -c $contents_mode_num"
844         set_encpolicy_args+=" -n $filenames_mode_num"
845
846         if (( policy_version > 1 )); then
847                 set_encpolicy_args+=" -v 2"
848                 crypt_util_args+=" --kdf=HKDF-SHA512"
849                 if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
850                         crypt_util_args+=" --mode-num=$contents_mode_num"
851                 elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 )); then
852                         crypt_util_args+=" --iv-ino-lblk-64"
853                         crypt_util_contents_args+=" --mode-num=$contents_mode_num"
854                         crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
855                 elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 )); then
856                         crypt_util_args+=" --iv-ino-lblk-32"
857                         crypt_util_contents_args+=" --mode-num=$contents_mode_num"
858                         crypt_util_filename_args+=" --mode-num=$filenames_mode_num"
859                 fi
860         else
861                 if (( policy_flags & ~FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
862                         _fail "unsupported flags for v1 policy: $policy_flags"
863                 fi
864                 if (( policy_flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
865                         crypt_util_args+=" --kdf=none"
866                 else
867                         crypt_util_args+=" --kdf=AES-128-ECB"
868                 fi
869         fi
870         set_encpolicy_args=${set_encpolicy_args# }
871
872         _require_scratch_encryption $set_encpolicy_args -f $policy_flags
873         _require_test_program "fscrypt-crypt-util"
874         _require_xfs_io_command "fiemap"
875         _require_get_encryption_nonce_support
876         _require_get_ciphertext_filename_support
877         if (( policy_version == 1 )); then
878                 _require_command "$KEYCTL_PROG" keyctl
879         fi
880
881         echo "Creating encryption-capable filesystem" >> $seqres.full
882         if (( policy_flags & (FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 |
883                               FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) )); then
884                 _scratch_mkfs_stable_inodes_encrypted &>> $seqres.full
885         else
886                 _scratch_mkfs_encrypted &>> $seqres.full
887         fi
888         _scratch_mount
889
890         crypt_util_args+=" --fs-uuid=$(blkid -s UUID -o value $SCRATCH_DEV | tr -d -)"
891
892         crypt_util_contents_args+="$crypt_util_args"
893         crypt_util_filename_args+="$crypt_util_args"
894
895         echo "Generating encryption key" >> $seqres.full
896         local raw_key=$(_generate_raw_encryption_key)
897         if (( policy_version > 1 )); then
898                 local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" \
899                                 | awk '{print $NF}')
900         else
901                 local keyspec=$(_generate_key_descriptor)
902                 _new_session_keyring
903                 _add_session_encryption_key $keyspec $raw_key
904         fi
905         local raw_key_hex=$(echo "$raw_key" | tr -d '\\x')
906
907         echo
908         echo -e "Verifying ciphertext with parameters:"
909         echo -e "\tcontents_encryption_mode: $contents_encryption_mode"
910         echo -e "\tfilenames_encryption_mode: $filenames_encryption_mode"
911         [ $# -ne 0 ] && echo -e "\toptions: $*"
912
913         _do_verify_ciphertext_for_encryption_policy \
914                 "$contents_encryption_mode" \
915                 "$filenames_encryption_mode" \
916                 "$policy_flags" \
917                 "$set_encpolicy_args" \
918                 "$keyspec" \
919                 "$raw_key_hex" \
920                 "$crypt_util_contents_args" \
921                 "$crypt_util_filename_args"
922 }