generic: test that encryption nonces are unique and random
authorEric Biggers <ebiggers@google.com>
Sat, 31 Oct 2020 07:23:44 +0000 (00:23 -0700)
committerEryu Guan <guaneryu@gmail.com>
Sun, 1 Nov 2020 16:25:08 +0000 (00:25 +0800)
Test that encryption nonces are unique and random, where randomness is
approximated as "incompressible by the xz program".

This gets indirectly tested by generic/399, but there are some gaps.
It's good to test for this directly too.

This test runs and passes on ext4 and f2fs.  It doesn't currently run on
ubifs because _get_encryption_nonce() isn't implemented for ubifs yet.
(At some point I'll probably switch _get_encryption_nonce() to use
FS_IOC_GET_ENCRYPTION_NONCE, which was added in Linux 5.7.  But for now
I'd like to keep the tests using it runnable on older kernels too.)

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
tests/generic/613 [new file with mode: 0755]
tests/generic/613.out [new file with mode: 0644]
tests/generic/group

diff --git a/tests/generic/613 b/tests/generic/613
new file mode 100755 (executable)
index 0000000..888ecf0
--- /dev/null
@@ -0,0 +1,118 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2020 Google LLC
+#
+# FS QA Test No. 613
+#
+# Test that encryption nonces are unique and random, where randomness is
+# approximated as "incompressible by the xz program".
+#
+# An encryption nonce is the 16-byte value that the filesystem generates for
+# each encrypted file.  These nonces must be unique in order to cause different
+# files to be encrypted differently, which is an important security property.
+# In practice, they need to be random to achieve that; and it's easy enough to
+# test for both uniqueness and randomness, so we test for both.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1       # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+       cd /
+       rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/encrypt
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+_supported_fs generic
+_require_scratch_encryption -v 2
+_require_get_encryption_nonce_support
+_require_command "$XZ_PROG" xz
+
+_scratch_mkfs_encrypted &>> $seqres.full
+_scratch_mount
+
+echo -e "\n# Adding encryption keys"
+_add_enckey $SCRATCH_MNT "$TEST_RAW_KEY"
+_add_enckey $SCRATCH_MNT "$TEST_RAW_KEY" -d $TEST_KEY_DESCRIPTOR
+
+# Create a bunch of encrypted files and directories -- enough for the uniqueness
+# and randomness tests to be meaningful, but not so many that this test takes a
+# long time.  Test using both v1 and v2 encryption policies, and for each of
+# those test the case of an encryption policy that is assigned to an empty
+# directory as well as the case of a file created in an encrypted directory.
+echo -e "\n# Creating encrypted files and directories"
+inodes=()
+for i in {1..50}; do
+       dir=$SCRATCH_MNT/v1_policy_dir_$i
+       mkdir $dir
+       inodes+=("$(stat -c %i $dir)")
+       _set_encpolicy $dir $TEST_KEY_DESCRIPTOR
+
+       dir=$SCRATCH_MNT/v2_policy_dir_$i
+       mkdir $dir
+       inodes+=("$(stat -c %i $dir)")
+       _set_encpolicy $dir $TEST_KEY_IDENTIFIER
+done
+for i in {1..50}; do
+       file=$SCRATCH_MNT/v1_policy_dir_1/$i
+       touch $file
+       inodes+=("$(stat -c %i $file)")
+
+       file=$SCRATCH_MNT/v2_policy_dir_1/$i
+       touch $file
+       inodes+=("$(stat -c %i $file)")
+done
+_scratch_unmount
+
+# Build files that contain all the nonces.  nonces_hex contains them in hex, one
+# per line.  nonces_bin contains them in binary, all concatenated.
+echo -e "\n# Getting encryption nonces from inodes"
+echo -n > $tmp.nonces_hex
+echo -n > $tmp.nonces_bin
+for inode in "${inodes[@]}"; do
+       nonce=$(_get_encryption_nonce $SCRATCH_DEV $inode)
+       if (( ${#nonce} != 32 )) || [ -n "$(echo "$nonce" | tr -d 0-9a-fA-F)" ]
+       then
+               _fail "Expected nonce to be 16 bytes (32 hex characters), but got \"$nonce\""
+       fi
+       echo $nonce >> $tmp.nonces_hex
+       echo -ne "$(echo $nonce | sed 's/[0-9a-fA-F]\{2\}/\\x\0/g')" \
+               >> $tmp.nonces_bin
+done
+
+# Verify the uniqueness and randomness of the nonces.  In theory randomness
+# implies uniqueness here, but it's easy enough to explicitly test for both.
+
+echo -e "\n# Verifying uniqueness of nonces"
+echo "Listing non-unique nonces:"
+sort < $tmp.nonces_hex | uniq -d
+
+echo -e "\n# Verifying randomness of nonces"
+uncompressed_size=$(stat -c %s $tmp.nonces_bin)
+echo "Uncompressed size is $uncompressed_size bytes"
+compressed_size=$($XZ_PROG -c < $tmp.nonces_bin | wc -c)
+echo "Compressed size is $compressed_size bytes" >> $seqres.full
+# The xz format has 60 bytes of overhead.  Go a bit lower to avoid flakiness.
+if (( compressed_size >= uncompressed_size + 55 )); then
+       echo "Nonces are incompressible, as expected"
+else
+       _fail "Nonces are compressible (non-random); compressed $uncompressed_size => $compressed_size bytes!"
+fi
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/613.out b/tests/generic/613.out
new file mode 100644 (file)
index 0000000..203a64f
--- /dev/null
@@ -0,0 +1,16 @@
+QA output created by 613
+
+# Adding encryption keys
+Added encryption key with identifier 69b2f6edeee720cce0577937eb8a6751
+Added encryption key with descriptor 0000111122223333
+
+# Creating encrypted files and directories
+
+# Getting encryption nonces from inodes
+
+# Verifying uniqueness of nonces
+Listing non-unique nonces:
+
+# Verifying randomness of nonces
+Uncompressed size is 3200 bytes
+Nonces are incompressible, as expected
index 8054d874f00504e465a6eba2b442a3638e95a1c9..31057ac8a67daa6507618d4e35c051e0a867cfb2 100644 (file)
 610 auto quick prealloc zero
 611 auto quick attr
 612 auto quick clone
+613 auto quick encrypt