diff options
Diffstat (limited to 'cli-tests')
35 files changed, 2577 insertions, 0 deletions
diff --git a/cli-tests/README.md b/cli-tests/README.md new file mode 100644 index 0000000..dfcc1d0 --- /dev/null +++ b/cli-tests/README.md @@ -0,0 +1,67 @@ +# fscrypt command-line interface tests + +## Usage + +To run the command-line interface (CLI) tests for `fscrypt`, ensure +that your kernel is v5.4 or later and has `CONFIG_FS_ENCRYPTION=y`. +Also ensure that you have the following packages installed: + +* e2fsprogs +* expect +* keyutils + +Then, run: + +```shell +make cli-test +``` + +You'll need to enter your `sudo` password, as the tests require root. + +If you only want to run specific tests, run a command like: + +```shell +make && sudo cli-tests/run.sh t_encrypt t_unlock +``` + +## Updating the expected output + +When the output of `fscrypt` has intentionally changed, the test +`.out` files need to be updated. This can be done automatically by +the following command, but be sure to review the changes: + +```shell +make cli-test-update +``` + +## Writing CLI tests + +The fscrypt CLI tests are `bash` scripts named like `t_*.sh`. + +The test scripts must be executable and begin by sourcing `common.sh`. +They all run in bash "extra-strict mode" (`-e -u -o pipefail`). They +run as root and have access to the following environment: + +* `$DEV`, `$DEV_ROOT`: ext4 filesystem images with encryption enabled + +* `$MNT`, `$MNT_ROOT`: the mountpoints of the above filesystems. + Initially all filesystems are mounted and are setup for fscrypt. + Login protectors will be stored on `$MNT_ROOT`. + +* `$TMPDIR`: a temporary directory that the test may use + +* `$FSCRYPT_CONF`: location of the fscrypt.conf file. Initially this + file exists and specifies to use v2 policies with the default + settings, except password hashing is configured to be extra fast. + +* `$TEST_USER`: a non-root user that the test may use. Their password + is `TEST_USER_PASS`. + +Any output (stdout and stderr) the test prints is compared to the +corresponding `.out` file. If a difference is detected then the test +is considered to have failed. The output is first sent through some +standard filters; see `run.sh`. + +The test is also failed if it exits with nonzero status. + +See `common.sh` for utility functions the tests may use. diff --git a/cli-tests/common.sh b/cli-tests/common.sh new file mode 100644 index 0000000..1d7b17b --- /dev/null +++ b/cli-tests/common.sh @@ -0,0 +1,187 @@ +#!/bin/bash +# +# common.sh - helper functions for fscrypt command-line interface tests +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +# Use extra-strict mode. +set -e -u -o pipefail + +# Don't allow running the test scripts directly. They need to be run via +# run.sh, to set up everything correctly. +if [ -z "${MNT:-}" ] || [ -z "${MNT_ROOT:-}" ]; then + echo 1>&2 "ERROR: This script can only be run via run.sh, not on its own." + exit 1 +fi + +# Prints an error message, then fails the test by exiting with failure status. +_fail() +{ + echo 1>&2 "ERROR: $1" + exit 1 +} + +# Runs a shell command and expects that it fails. +_expect_failure() +{ + if eval "$1"; then + _fail "command unexpectedly succeeded: \"$1\"" + fi +} + +# Prints a message to mark the beginning of the next part of the test. +_print_header() +{ + echo + echo "# $1" +} + +# Deletes all files on the test filesystems, including all policies and +# protectors. Leaves the fscrypt metadata directories themselves. +_reset_filesystems() +{ + local mnt + + for mnt in "$MNT" "$MNT_ROOT"; do + rm -rf "${mnt:?}"/* "${mnt:?}"/.fscrypt/{policies,protectors}/* + done +} + +# Prints the number of filesystems that have encryption support enabled. +_get_enabled_fs_count() +{ + local count + + count=$(fscrypt status | awk '/filesystems supporting encryption/ { print $4 }') + if [ -z "$count" ]; then + _fail "encryption support status line not found" + fi + echo "$count" +} + +# Gets the descriptor of the given protector. +_get_protector_descriptor() +{ + local mnt=$1 + local source=$2 + + case $source in + custom) + local name=$3 + local description="custom protector \\\"$name\\\"" + ;; + login) + local user=$3 + local description="login protector for $user" + ;; + *) + _fail "Unknown protector source $source" + esac + + local descriptor + descriptor=$(fscrypt status "$mnt" | + awk -F ' *' '{ if ($3 == "'"$description"'") print $1 }') + if [ -z "$descriptor" ]; then + _fail "Can't find $description on $mnt" + fi + echo "$descriptor" +} + +# Gets the descriptor of the login protector for $TEST_USER. +_get_login_descriptor() +{ + _get_protector_descriptor "$MNT_ROOT" login "$TEST_USER" +} + +# Prints the number of filesystems that have fscrypt metadata. +_get_setup_fs_count() +{ + local count + + count=$(fscrypt status | awk '/filesystems with fscrypt metadata/ { print $5 }') + if [ -z "$count" ]; then + _fail "fscrypt metadata status line not found" + fi + echo "$count" +} + +# Removes all fscrypt metadata from the given filesystem. +_rm_metadata() +{ + rm -r "${1:?}/.fscrypt" +} + +# Runs a shell command, ignoring its output (stdout and stderr) if it succeeds. +# If the command fails, prints its output and fails the test. +_run_noisy_command() +{ + if ! eval "$1" &> "$TMPDIR/out"; then + _fail "Command failed: '$1'. Output was: $(cat "$TMPDIR/out")" + fi +} + +# Runs the given shell command as the test user. +_user_do() +{ + su "$TEST_USER" --shell=/bin/bash --command="export PATH='$PATH'; $1" +} + +# Runs the given shell command as the test user and expects it to fail. +_user_do_and_expect_failure() +{ + _expect_failure "_user_do '$1'" +} + +# Clear the test user's user keyring and unlink it from root's user keyring, if +# it is linked into it. +_cleanup_user_keyrings() +{ + local ringid + + ringid=$(_user_do "keyctl show @u" | awk '/keyring: _uid/{print $1}') + + _user_do "keyctl clear $ringid" + keyctl unlink "$ringid" @u &> /dev/null || true +} + +# Gives the test a new session keyring which contains the test user's keyring +# but not root's keyring. Also clears the test user's keyring. This must be +# called at the beginning of the test script as it may re-execute the script. +_setup_session_keyring() +{ + # This *should* just use 'keyctl new_session', but that doesn't work if + # the session keyring is owned by a user other than root. So instead we + # have to use 'keyctl session' and re-execute the script. + if [ -z "${FSCRYPT_SESSION_KEYRING_SET:-}" ]; then + export FSCRYPT_SESSION_KEYRING_SET=1 + set +e + keyctl session - "$0" |& grep -v '^Joined session keyring' + exit "${PIPESTATUS[0]}" + fi + + # Link the test user's keyring into the new session keyring. + keyctl setperm @s 0x3f000000 # all possessor permissions + _user_do "keyctl link @u @s" + + # Clear the test user's keyring. + _user_do "keyctl clear @u" +} + +# Wraps the 'expect' command to force subprocesses to have 80-column output. +expect() +{ + command expect -c 'set stty_init "cols 80"' -f - +} diff --git a/cli-tests/run.sh b/cli-tests/run.sh new file mode 100755 index 0000000..9ab5b78 --- /dev/null +++ b/cli-tests/run.sh @@ -0,0 +1,307 @@ +#!/bin/bash +# +# run.sh - run the fscrypt command-line interface tests +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +# Use extra-strict mode. +set -e -u -o pipefail + +# Ensure we're in the cli-tests/ directory. +cd "$(dirname "$0")" + +# Names of the test devices. +# Variables with these names are exported to the tests. +DEVICES=(DEV DEV_ROOT) + +# Names of the mountpoint of each test device. +# Variables with these names are exported to the tests. +MOUNTS=(MNT MNT_ROOT) + +# Name of the test user. This user will be created and deleted by this script. +# This variable is exported to the tests. +TEST_USER=fscrypt-test-user + +# The temporary directory to use. +# This variable is exported to the tests. +TMPDIR=$(mktemp -d /tmp/fscrypt.XXXXXX) + +# The loopback devices that correspond to each test device. +LOOPS=() + +# Update the expected output files to match the actual output? +UPDATE_OUTPUT=false + +LONGOPTS_ARRAY=( +'help' +'update-output' +) +LONGOPTS=$(echo "${LONGOPTS_ARRAY[*]}" | tr ' ' ,) + +cleanup() +{ + local mnt loop + + # Unmount all the test filesystems. + for mnt in "${MOUNTS[@]}"; do + mnt="$TMPDIR/$mnt" + if mountpoint "$mnt" &> /dev/null; then + umount "$mnt" + fi + done + + # Delete the loopback device of each test device. + for loop in "${LOOPS[@]}"; do + losetup -d "$loop" + done + + # Delete all temporary files. + rm -rf "${TMPDIR:?}"/* +} + +cleanup_full() +{ + cleanup + rm -rf "$TMPDIR" + userdel "$TEST_USER" || true +} + +# Filters the output of the test script to make the output consistent on every +# run of the test. For example, references to the mountpoint like +# /tmp/fscrypt.4OTb6y/MNT will be replaced with simply MNT, since the name of +# the temporary directory is different every time. +filter_test_output() +{ + local sedscript="" + local raw_output=$TMPDIR/raw-test-output + local i + + cat > "$raw_output" + + # Filter mountpoint and device names. + for i in "${!DEVICES[@]}"; do + sedscript+="s@$TMPDIR/${MOUNTS[$i]}@${MOUNTS[$i]}@g;" + sedscript+="s@${LOOPS[$i]}@${DEVICES[$i]}@g;" + done + + # Filter the path to fscrypt.conf. + sedscript+="s@$FSCRYPT_CONF@FSCRYPT_CONF@g;" + + # Filter policy and protector descriptors. + sedscript+=$(grep -E -o '\<([a-f0-9]{16})|([a-f0-9]{32})\>' \ + "$raw_output" \ + | awk '{ printf "s@\\<" $1 "\\>@desc" NR "@g;" }') + + # Filter any other paths in TMPDIR. + sedscript+="s@$TMPDIR@TMPDIR@g;" + + # At some point, 'bash -c COMMAND' started showing error messages as + # "bash: line 1: " instead of just "bash: ". Filter out the "line 1: ". + sedscript+="s@^bash: line 1: @bash: @;" + + # Work around protojson whitespace randomization. + sedscript+="/^Options: /s@ @ @g;" + sedscript+="s@^Options: @Options: @;" + + sed -e "$sedscript" "$raw_output" +} + +# Prepares to run a test script. +setup_for_test() +{ + local i dev_var mnt_var img mnt loop + + # Start with a clean state. + cleanup + + # ../bin/fscrypt might not be accessible to $TEST_USER. Copy it into + # $TMPDIR so that $TEST_USER is guaranteed to have access to it. + mkdir "$TMPDIR/bin" + cp ../bin/fscrypt "$TMPDIR/bin/" + chmod 755 "$TMPDIR" "$TMPDIR/bin" "$TMPDIR/bin/fscrypt" + + # Create the test filesystems and mountpoints. + LOOPS=() + for i in "${!DEVICES[@]}"; do + dev_var=${DEVICES[$i]} + mnt_var=${MOUNTS[$i]} + img="$TMPDIR/$dev_var" + if ! mkfs.ext4 -O encrypt -F -b 4096 -I 256 "$img" $((1<<15)) \ + &> "$TMPDIR/mkfs.out" + then + cat 1>&2 "$TMPDIR/mkfs.out" + exit 1 + fi + loop=$(losetup --find --show "$img") + LOOPS+=("$loop") + export "$dev_var=$loop" + mnt="$TMPDIR/$mnt_var" + export "$mnt_var=$mnt" + mkdir -p "$mnt" + mount "$loop" "$mnt" + done + + # Give the tests their own "root" mount for storing login protectors, so + # they don't use the real "/". + export FSCRYPT_ROOT_MNT="$MNT_ROOT" + + # Enable consistent output mode. + export FSCRYPT_CONSISTENT_OUTPUT="1" + + # Give the tests their own fscrypt.conf. + export FSCRYPT_CONF="$TMPDIR/fscrypt.conf" + fscrypt setup --time=1ms --quiet --all-users > /dev/null + + # The tests assume kernel support for v2 policies. + if ! grep -E -q '"policy_version": +"2"' "$FSCRYPT_CONF"; then + cat 1>&2 << EOF +ERROR: Can't run these tests because your kernel doesn't support v2 policies. +You need kernel v5.4 or later. +EOF + exit 1 + fi + + # Set up the test filesystems that aren't already set up. + fscrypt setup --quiet --all-users "$MNT" > /dev/null +} + +run_test() +{ + local t=$1 + + # Run the test script. + set +e + "./$1.sh" |& filter_test_output > "$t.out.actual" + status=${PIPESTATUS[0]} + set -e + + # Check for failure status. + if [ "$status" != 0 ]; then + echo 1>&2 "FAILED: $t [exited with failure status $status]" + if [ -s "$t.out.actual" ]; then + if (( $(wc -l "$t.out.actual" | cut -f1 -d' ') > 10 )); then + echo 1>&2 "Last 10 lines of test output:" + tail -n10 "$t.out.actual" | sed 1>&2 's/^/ /' + echo 1>&2 + echo 1>&2 "See $t.out.actual for the full output." + else + echo 1>&2 "Test output:" + sed 1>&2 's/^/ /' < "$t.out.actual" + fi + fi + exit 1 + fi + + # Check for output mismatch. + if ! cmp "$t.out" "$t.out.actual" &> /dev/null; then + if $UPDATE_OUTPUT; then + cp "$t.out.actual" "$t.out" + echo "Updated $t.out" + else + echo 1>&2 "FAILED: $t [output mismatch]" + echo 1>&2 "Differences between $t.out and $t.out.actual:" + echo 1>&2 + diff 1>&2 "$t.out" "$t.out.actual" + exit 1 + fi + fi + rm -f "$t.out.actual" +} + +usage() +{ + cat << EOF +Usage: run.sh [--update-output] [TEST_SCRIPT_NAME]..." +EOF + exit 1 +} + +if ! options=$(getopt -o "" -l "$LONGOPTS" -- "$@"); then + usage +fi +eval set -- "$options" +while (( $# >= 1 )); do + case "$1" in + --update-output) + UPDATE_OUTPUT=true + ;; + --) + shift + break + ;; + --help|*) + usage + ;; + esac + shift +done + +if [ "$(id -u)" != 0 ]; then + echo 1>&2 "ERROR: You must be root to run these tests." + exit 1 +fi + +# Check for prerequisites. +PREREQ_CMDS=(mkfs.ext4 expect keyctl) +PREREQ_PKGS=(e2fsprogs expect keyutils) +for i in ${!PREREQ_CMDS[*]}; do + if ! type -P "${PREREQ_CMDS[$i]}" > /dev/null; then + cat 1>&2 << EOF +ERROR: You must install the '${PREREQ_PKGS[$i]}' package to run these tests. + Try a command like 'sudo apt-get install ${PREREQ_PKGS[$i]}'. +EOF + exit 1 + fi +done + +# Use a consistent umask. +umask 022 + +# Use a consistent locale, to prevent output mismatches. +export LANG=C +export LC_ALL=C + +# Always cleanup fully on exit. +trap cleanup_full EXIT + +# Create a test user, so that we can test non-root use of fscrypt. Give them a +# password, so that we can test creating login passphrase protected directories. +userdel "$TEST_USER" &> /dev/null || true +useradd "$TEST_USER" +echo "$TEST_USER:TEST_USER_PASS" | chpasswd +export TEST_USER + +# Let the tests use $TMPDIR if they need it. +export TMPDIR + +# Make it so that running 'fscrypt' in the tests runs the correct binary. +export PATH="$TMPDIR/bin:$PATH" + +if (( $# >= 1 )); then + # Tests specified on command line. + tests=("$@") +else + # No tests specified on command line. Just run everything. + tests=(t_*.sh) +fi +for t in "${tests[@]}"; do + t=${t%.sh} + echo "Running $t" + setup_for_test + run_test "$t" +done + +echo "All tests passed!" diff --git a/cli-tests/t_change_passphrase.out b/cli-tests/t_change_passphrase.out new file mode 100644 index 0000000..747ed89 --- /dev/null +++ b/cli-tests/t_change_passphrase.out @@ -0,0 +1,32 @@ + +# Create encrypted directory + +# Try to unlock with wrong passphrase +[ERROR] fscrypt unlock: incorrect key provided +mkdir: cannot create directory 'MNT/dir/subdir': Required key not available + +# Change passphrase + +# Try to unlock with old passphrase +[ERROR] fscrypt unlock: incorrect key provided +mkdir: cannot create directory 'MNT/dir/subdir': Required key not available + +# Unlock with new passphrase + +# Try to change passphrase (interactively, mismatch) +spawn fscrypt metadata change-passphrase --protector=MNT:desc1
+Enter old custom passphrase for protector "prot":
+Enter new custom passphrase for protector "prot":
+Confirm passphrase:
+[ERROR] fscrypt metadata change-passphrase: entered passphrases do not match
+ +# Change passphrase (interactively) +spawn fscrypt metadata change-passphrase --protector=MNT:desc1
+Enter old custom passphrase for protector "prot":
+Enter new custom passphrase for protector "prot":
+Confirm passphrase:
+Passphrase for protector desc1 successfully changed.
+ +# Lock, then unlock with new passphrase +"MNT/dir" is now locked. +mkdir: cannot create directory 'MNT/dir/subdir': Required key not available diff --git a/cli-tests/t_change_passphrase.sh b/cli-tests/t_change_passphrase.sh new file mode 100755 index 0000000..1360bc2 --- /dev/null +++ b/cli-tests/t_change_passphrase.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Test changing the passphrase of a custom_passphrase protector. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" + +_print_header "Create encrypted directory" +mkdir "$dir" +echo pass1 | fscrypt encrypt --quiet --name=prot --skip-unlock "$dir" + +_print_header "Try to unlock with wrong passphrase" +_expect_failure "echo pass2 | fscrypt unlock --quiet '$dir'" +_expect_failure "mkdir '$dir/subdir'" +protector=$(_get_protector_descriptor "$dir" custom prot) + +_print_header "Change passphrase" +echo $'pass1\npass2' | \ + fscrypt metadata change-passphrase --protector="$MNT:$protector" --quiet + +_print_header "Try to unlock with old passphrase" +_expect_failure "echo pass1 | fscrypt unlock --quiet '$dir'" +_expect_failure "mkdir '$dir/subdir'" + +_print_header "Unlock with new passphrase" +echo pass2 | fscrypt unlock --quiet "$dir" +mkdir "$dir/subdir" +rmdir "$dir/subdir" + +_print_header "Try to change passphrase (interactively, mismatch)" +expect << EOF +spawn fscrypt metadata change-passphrase --protector=$MNT:$protector +expect "Enter old custom passphrase" +send "pass2\r" +expect "Enter new custom passphrase" +send "pass3\r" +expect "Confirm passphrase" +send "bad\r" +expect eof +EOF + +_print_header "Change passphrase (interactively)" +expect << EOF +spawn fscrypt metadata change-passphrase --protector=$MNT:$protector +expect "Enter old custom passphrase" +send "pass2\r" +expect "Enter new custom passphrase" +send "pass3\r" +expect "Confirm passphrase" +send "pass3\r" +expect eof +EOF + +_print_header "Lock, then unlock with new passphrase" +fscrypt lock "$dir" +_expect_failure "mkdir '$dir/subdir'" +echo pass3 | fscrypt unlock --quiet "$dir" +mkdir "$dir/subdir" diff --git a/cli-tests/t_encrypt.out b/cli-tests/t_encrypt.out new file mode 100644 index 0000000..4de05e4 --- /dev/null +++ b/cli-tests/t_encrypt.out @@ -0,0 +1,106 @@ + +# Try to encrypt a nonexistent directory +[ERROR] fscrypt encrypt: no such file or directory +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Try to encrypt a nonempty directory +[ERROR] fscrypt encrypt: Directory "MNT/dir" cannot be + encrypted because it is non-empty. + +Files cannot be encrypted in-place. Instead, encrypt a new directory, copy the +files into it, and securely delete the original directory. For example: + + mkdir "MNT/dir.new" + fscrypt encrypt "MNT/dir.new" + cp -a -T "MNT/dir" "MNT/dir.new" + find "MNT/dir" -type f -print0 | xargs -0 shred -n1 --remove=unlink + rm -rf "MNT/dir" + mv "MNT/dir.new" "MNT/dir" + +Caution: due to the nature of modern storage devices and filesystems, the +original data may still be recoverable from disk. It's much better to encrypt +your files from the start. +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# => with trailing slash +[ERROR] fscrypt encrypt: Directory "MNT/dir/" cannot be + encrypted because it is non-empty. + +Files cannot be encrypted in-place. Instead, encrypt a new directory, copy the +files into it, and securely delete the original directory. For example: + + mkdir "MNT/dir.new" + fscrypt encrypt "MNT/dir.new" + cp -a -T "MNT/dir" "MNT/dir.new" + find "MNT/dir" -type f -print0 | xargs -0 shred -n1 --remove=unlink + rm -rf "MNT/dir" + mv "MNT/dir.new" "MNT/dir" + +Caution: due to the nature of modern storage devices and filesystems, the +original data may still be recoverable from disk. It's much better to encrypt +your files from the start. +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Encrypt a directory as non-root user +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "prot" + +POLICY UNLOCKED PROTECTORS +desc2 Yes desc1 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc2 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "prot" +ext4 filesystem "MNT" has 1 protector and 1 policy (only including ones owned by fscrypt-test-user or root). +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "prot" + +POLICY UNLOCKED PROTECTORS +desc2 Yes desc1 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc2 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "prot" + +# Try to encrypt an already-encrypted directory +[ERROR] fscrypt encrypt: file or directory "MNT/dir" is + already encrypted + +# Try to encrypt another user's directory as a non-root user +[ERROR] fscrypt encrypt: cannot encrypt "MNT/dir" because + it's owned by another user (root). + + Encryption can only be enabled on a directory you own, + even if you have write access to the directory. +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted diff --git a/cli-tests/t_encrypt.sh b/cli-tests/t_encrypt.sh new file mode 100755 index 0000000..ffd6165 --- /dev/null +++ b/cli-tests/t_encrypt.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# General tests for 'fscrypt encrypt'. For protector-specific tests, see +# t_encrypt_custom, t_encrypt_login, and t_encrypt_raw_key. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" + +begin() +{ + _reset_filesystems + mkdir "$dir" + _print_header "$@" +} + +show_status() +{ + local encrypted=$1 + + fscrypt status "$MNT" + if $encrypted; then + fscrypt status "$dir" + else + _expect_failure "fscrypt status '$dir'" + fi +} + +begin "Try to encrypt a nonexistent directory" +_expect_failure "echo hunter2 | fscrypt encrypt --quiet '$MNT/nonexistent'" +show_status false + +begin "Try to encrypt a nonempty directory" +touch "$dir/file" +_expect_failure "echo hunter2 | fscrypt encrypt --quiet '$dir'" +show_status false +_print_header "=> with trailing slash" +_expect_failure "echo hunter2 | fscrypt encrypt --quiet '$dir/'" +show_status false + +begin "Encrypt a directory as non-root user" +chown "$TEST_USER" "$dir" +_user_do "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" +show_status true +_user_do "fscrypt status '$MNT'" +_user_do "fscrypt status '$dir'" + +_print_header "Try to encrypt an already-encrypted directory" +_user_do_and_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" + +begin "Try to encrypt another user's directory as a non-root user" +_user_do_and_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" +show_status false diff --git a/cli-tests/t_encrypt_custom.out b/cli-tests/t_encrypt_custom.out new file mode 100644 index 0000000..2f1c03c --- /dev/null +++ b/cli-tests/t_encrypt_custom.out @@ -0,0 +1,58 @@ + +# Encrypt with custom passphrase protector +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "prot" + +POLICY UNLOCKED PROTECTORS +desc2 Yes desc1 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc2 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "prot" + +# Encrypt with custom passphrase protector, interactively +spawn fscrypt encrypt MNT/dir
+The following protector sources are available:
+1 - Your login passphrase (pam_passphrase)
+2 - A custom passphrase (custom_passphrase)
+3 - A raw 256-bit key (raw_key)
+Enter the source number for the new protector [2 - custom_passphrase]: 2
+Enter a name for the new protector: prot
+Enter custom passphrase for protector "prot":
+Confirm passphrase:
+"MNT/dir" is now encrypted, unlocked, and ready for use.
+ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc6 No custom protector "prot" + +POLICY UNLOCKED PROTECTORS +desc7 Yes desc6 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc7 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc6 No custom protector "prot" + +# Try to use a custom protector without a name +[ERROR] fscrypt encrypt: custom_passphrase protectors must be named + +Use --name=PROTECTOR_NAME to specify a protector name. +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted diff --git a/cli-tests/t_encrypt_custom.sh b/cli-tests/t_encrypt_custom.sh new file mode 100755 index 0000000..48cbe25 --- /dev/null +++ b/cli-tests/t_encrypt_custom.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Test encrypting a directory using a custom_passphrase protector. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" + +begin() +{ + _reset_filesystems + mkdir "$dir" + _print_header "$1" +} + +show_status() +{ + local encrypted=$1 + + fscrypt status "$MNT" + if $encrypted; then + fscrypt status "$dir" + else + _expect_failure "fscrypt status '$dir'" + fi +} + +begin "Encrypt with custom passphrase protector" +echo hunter2 | fscrypt encrypt --quiet --name=prot "$dir" +show_status true + +begin "Encrypt with custom passphrase protector, interactively" +expect << EOF +spawn fscrypt encrypt "$dir" +expect "Enter the source number for the new protector" +send "2\r" +expect "Enter a name for the new protector:" +send "prot\r" +expect "Enter custom passphrase" +send "hunter2\r" +expect "Confirm passphrase" +send "hunter2\r" +expect eof +EOF +show_status true + +begin "Try to use a custom protector without a name" +_expect_failure "echo hunter2 | fscrypt encrypt --quiet '$dir'" +show_status false diff --git a/cli-tests/t_encrypt_login.out b/cli-tests/t_encrypt_login.out new file mode 100644 index 0000000..b1f6c82 --- /dev/null +++ b/cli-tests/t_encrypt_login.out @@ -0,0 +1,209 @@ + +# Encrypt with login protector + +IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for + important recovery instructions. It is *strongly recommended* to + record the recovery passphrase in a secure location; otherwise you + will lose access to this directory if you reinstall the operating + system or move this filesystem to another system. + +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 Yes (MNT_ROOT) login protector for fscrypt-test-user +desc2 No custom protector "Recovery passphrase for dir" + +POLICY UNLOCKED PROTECTORS +desc3 Yes desc1, desc2 +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 No login protector for fscrypt-test-user +"MNT/dir" is encrypted with fscrypt. + +Policy: desc3 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 2 protectors: +PROTECTOR LINKED DESCRIPTION +desc1 Yes (MNT_ROOT) login protector for fscrypt-test-user +desc2 No custom protector "Recovery passphrase for dir" + +# => Lock, then unlock with login passphrase +"MNT/dir" is now locked. + +# => Lock, then unlock with recovery passphrase +"MNT/dir" is now locked. + +# Encrypt with login protector, interactively +spawn fscrypt encrypt MNT/dir
+The following protector sources are available:
+1 - Your login passphrase (pam_passphrase)
+2 - A custom passphrase (custom_passphrase)
+3 - A raw 256-bit key (raw_key)
+Enter the source number for the new protector [2 - custom_passphrase]: 1
+
+IMPORTANT: Before continuing, ensure you have properly set up your system for
+ login protectors. See
+ https://github.com/google/fscrypt#setting-up-for-login-protectors
+
+Enter login passphrase for fscrypt-test-user:
+
+IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for
+ important recovery instructions. It is *strongly recommended* to
+ record the recovery passphrase in a secure location; otherwise you
+ will lose access to this directory if you reinstall the operating
+ system or move this filesystem to another system.
+
+"MNT/dir" is now encrypted, unlocked, and ready for use.
+ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc10 Yes (MNT_ROOT) login protector for fscrypt-test-user +desc11 No custom protector "Recovery passphrase for dir" + +POLICY UNLOCKED PROTECTORS +desc12 Yes desc10, desc11 +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc10 No login protector for fscrypt-test-user +"MNT/dir" is encrypted with fscrypt. + +Policy: desc12 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 2 protectors: +PROTECTOR LINKED DESCRIPTION +desc10 Yes (MNT_ROOT) login protector for fscrypt-test-user +desc11 No custom protector "Recovery passphrase for dir" + +# Encrypt with login protector as root + +IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for + important recovery instructions. It is *strongly recommended* to + record the recovery passphrase in a secure location; otherwise you + will lose access to this directory if you reinstall the operating + system or move this filesystem to another system. + +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc19 Yes (MNT_ROOT) login protector for fscrypt-test-user +desc20 No custom protector "Recovery passphrase for dir" + +POLICY UNLOCKED PROTECTORS +desc21 Yes desc19, desc20 +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc19 No login protector for fscrypt-test-user +"MNT/dir" is encrypted with fscrypt. + +Policy: desc21 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 2 protectors: +PROTECTOR LINKED DESCRIPTION +desc19 Yes (MNT_ROOT) login protector for fscrypt-test-user +desc20 No custom protector "Recovery passphrase for dir" + +Protector is owned by fscrypt-test-user:fscrypt-test-user +"MNT/dir" is now locked. +"MNT/dir" is now locked. + +# Encrypt with login protector with --no-recovery +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc28 Yes (MNT_ROOT) login protector for fscrypt-test-user + +POLICY UNLOCKED PROTECTORS +desc29 Yes desc28 +ext4 filesystem "MNT_ROOT" has 1 protector and 0 policies. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc28 No login protector for fscrypt-test-user +"MNT/dir" is encrypted with fscrypt. + +Policy: desc29 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc28 Yes (MNT_ROOT) login protector for fscrypt-test-user + +# Encrypt with login protector on root fs (shouldn't generate a recovery passphrase) +"MNT_ROOT/dir" is encrypted with fscrypt. + +Policy: desc34 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc35 No login protector for fscrypt-test-user +ext4 filesystem "MNT_ROOT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc35 No login protector for fscrypt-test-user + +POLICY UNLOCKED PROTECTORS +desc34 Yes desc35 + +# Try to give a login protector a name +[ERROR] fscrypt encrypt: cannot assign name "prot" to new login protector for + user "fscrypt-test-user" because login protectors are + identified by user, not by name. + +To fix this, don't specify the --name=PROTECTOR_NAME option. +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +ext4 filesystem "MNT_ROOT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Try to use the wrong login passphrase +[ERROR] fscrypt encrypt: incorrect login passphrase +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +ext4 filesystem "MNT_ROOT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Test that linked protector works even if UUID link is broken + +IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for + important recovery instructions. It is *strongly recommended* to + record the recovery passphrase in a secure location; otherwise you + will lose access to this directory if you reinstall the operating + system or move this filesystem to another system. + +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc39 No custom protector "Recovery passphrase for dir" +desc40 Yes (MNT_ROOT) login protector for fscrypt-test-user + +POLICY UNLOCKED PROTECTORS +desc41 Yes desc40, desc39 diff --git a/cli-tests/t_encrypt_login.sh b/cli-tests/t_encrypt_login.sh new file mode 100755 index 0000000..b6ae2d8 --- /dev/null +++ b/cli-tests/t_encrypt_login.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +# Test encrypting a directory using a login (pam_passphrase) protector. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" + +begin() +{ + _reset_filesystems + mkdir "$dir" + _print_header "$1" +} + +show_status() +{ + local encrypted=$1 + + fscrypt status "$MNT" + fscrypt status "$MNT_ROOT" + if $encrypted; then + fscrypt status "$dir" + else + _expect_failure "fscrypt status '$dir'" + fi +} + +begin "Encrypt with login protector" +chown "$TEST_USER" "$dir" +_user_do "echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase '$dir'" +show_status true +recovery_passphrase=$(grep -E '^ +[a-z]{20}$' "$dir/fscrypt_recovery_readme.txt" | sed 's/^ +//') +recovery_protector=$(_get_protector_descriptor "$MNT" custom 'Recovery passphrase for dir') +login_protector=$(_get_login_descriptor) +_print_header "=> Lock, then unlock with login passphrase" +_user_do "fscrypt lock '$dir'" +# FIXME: should we be able to use $MNT:$login_protector here? +_user_do "echo TEST_USER_PASS | fscrypt unlock --quiet --unlock-with=$MNT_ROOT:$login_protector '$dir'" +_print_header "=> Lock, then unlock with recovery passphrase" +_user_do "fscrypt lock '$dir'" +_user_do "echo $recovery_passphrase | fscrypt unlock --quiet --unlock-with=$MNT:$recovery_protector '$dir'" + +begin "Encrypt with login protector, interactively" +chown "$TEST_USER" "$dir" +_user_do expect << EOF +spawn fscrypt encrypt "$dir" +expect "Enter the source number for the new protector" +send "1\r" +expect "Enter login passphrase" +send "TEST_USER_PASS\r" +expect eof +EOF +show_status true + +begin "Encrypt with login protector as root" +echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase --user="$TEST_USER" "$dir" +show_status true +# The newly-created login protector should be owned by the user, not root. +# This is partly redundant with the below check, but we might as well test both. +login_protector=$(_get_login_descriptor) +owner=$(stat -c "%U:%G" "$MNT_ROOT/.fscrypt/protectors/$login_protector") +echo -e "\nProtector is owned by $owner" +# The user should be able to lock and unlock the directory themselves. This +# tests that the fscrypt metadata file permissions got set appropriately when +# root set up the encryption on the user's behalf. +chown "$TEST_USER" "$dir" +_user_do "fscrypt lock $dir" +_user_do "echo TEST_USER_PASS | fscrypt unlock $dir --quiet --unlock-with=$MNT_ROOT:$login_protector" +_user_do "fscrypt lock $dir" + +begin "Encrypt with login protector with --no-recovery" +chown "$TEST_USER" "$dir" +_user_do "echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase --no-recovery '$dir'" +show_status true + +begin "Encrypt with login protector on root fs (shouldn't generate a recovery passphrase)" +mkdir "$MNT_ROOT/dir" +chown "$TEST_USER" "$MNT_ROOT/dir" +_user_do "echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase --no-recovery '$MNT_ROOT/dir'" +fscrypt status "$MNT_ROOT/dir" +fscrypt status "$MNT_ROOT" +rmdir "$MNT_ROOT/dir" + +begin "Try to give a login protector a name" +chown "$TEST_USER" "$dir" +_user_do_and_expect_failure \ + "echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase --name=prot '$dir'" +show_status false + +begin "Try to use the wrong login passphrase" +chown "$TEST_USER" "$dir" +_user_do_and_expect_failure \ + "echo wrong_passphrase | fscrypt encrypt --quiet --source=pam_passphrase '$dir'" +show_status false + +begin "Test that linked protector works even if UUID link is broken" +echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase --user="$TEST_USER" "$dir" +protector=$(_get_login_descriptor) +link_file=$MNT/.fscrypt/protectors/$protector.link +[ -e "$link_file" ] || _fail "$link_file does not exist" +sed -i 's/UUID=.*/UUID=00000000-0000-0000-0000-000000000000/' "$link_file" +fscrypt status "$MNT" diff --git a/cli-tests/t_encrypt_raw_key.out b/cli-tests/t_encrypt_raw_key.out new file mode 100644 index 0000000..78aa0b7 --- /dev/null +++ b/cli-tests/t_encrypt_raw_key.out @@ -0,0 +1,74 @@ + +# Encrypt with raw_key protector from file +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 No raw key protector "prot" + +POLICY UNLOCKED PROTECTORS +desc2 Yes desc1 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc2 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc1 No raw key protector "prot" + +# Encrypt with raw_key protector from stdin +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc6 No raw key protector "prot" + +POLICY UNLOCKED PROTECTORS +desc7 Yes desc6 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc7 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc6 No raw key protector "prot" + +# Try to encrypt with raw_key protector from file, using wrong key length +[ERROR] fscrypt encrypt: TMPDIR/raw_key: key file must be 32 bytes +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Try to encrypt with raw_key protector from stdin, using wrong key length +[ERROR] fscrypt encrypt: unexpected EOF +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Encrypt with raw_key protector from file, unlock from stdin +"MNT/dir" is now locked. +ext4 filesystem "MNT" has 1 protector and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc11 No raw key protector "prot" + +POLICY UNLOCKED PROTECTORS +desc12 Yes desc11 +"MNT/dir" is encrypted with fscrypt. + +Policy: desc12 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc11 No raw key protector "prot" diff --git a/cli-tests/t_encrypt_raw_key.sh b/cli-tests/t_encrypt_raw_key.sh new file mode 100755 index 0000000..e5c6d20 --- /dev/null +++ b/cli-tests/t_encrypt_raw_key.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Test encrypting a directory using a raw_key protector. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" +raw_key_file="$TMPDIR/raw_key" + +begin() +{ + _reset_filesystems + mkdir "$dir" + _print_header "$1" +} + +show_status() +{ + local encrypted=$1 + + fscrypt status "$MNT" + if $encrypted; then + fscrypt status "$dir" + else + _expect_failure "fscrypt status '$dir'" + fi +} + +begin "Encrypt with raw_key protector from file" +head -c 32 /dev/urandom > "$raw_key_file" +fscrypt encrypt --quiet --name=prot --source=raw_key --key="$raw_key_file" "$dir" +show_status true + +begin "Encrypt with raw_key protector from stdin" +head -c 32 /dev/urandom | fscrypt encrypt --quiet --name=prot --source=raw_key "$dir" +show_status true + +begin "Try to encrypt with raw_key protector from file, using wrong key length" +head -c 16 /dev/urandom > "$raw_key_file" +_expect_failure "fscrypt encrypt --quiet --name=prot --source=raw_key --key='$raw_key_file' '$dir'" +show_status false + +begin "Try to encrypt with raw_key protector from stdin, using wrong key length" +_expect_failure "head -c 16 /dev/urandom | fscrypt encrypt --quiet --name=prot --source=raw_key '$dir'" +show_status false + +begin "Encrypt with raw_key protector from file, unlock from stdin" +head -c 32 /dev/urandom > "$raw_key_file" +fscrypt encrypt --quiet --name=prot --source=raw_key --key="$raw_key_file" "$dir" +fscrypt lock "$dir" +fscrypt unlock --quiet "$dir" < "$raw_key_file" +show_status true diff --git a/cli-tests/t_lock.out b/cli-tests/t_lock.out new file mode 100644 index 0000000..ce27713 --- /dev/null +++ b/cli-tests/t_lock.out @@ -0,0 +1,102 @@ + +# Encrypt directory +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Lock directory +"MNT/dir" is now locked. + +# => filenames should be in encrypted form +cat: MNT/dir/file: No such file or directory + +# => shouldn't be able to create a subdirectory +mkdir: cannot create directory 'MNT/dir/subdir': Required key not available + +# Unlock directory +Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use. +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +contents + +# Try to lock directory while files busy +[ERROR] fscrypt lock: Directory was incompletely locked because some files are + still open. These files remain accessible. + +Try killing any processes using files in the directory, for example using: + + find "MNT/dir" -print0 | xargs -0 fuser -k + +Then re-run: + + fscrypt lock "MNT/dir" + +# => status should be incompletely locked +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Partially (incompletely locked) + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# => open file should still be readable +contents + +# => shouldn't be able to create a new file +bash: MNT/dir/file2: Required key not available + +# Finish locking directory +"MNT/dir" is now locked. +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +cat: MNT/dir/file: No such file or directory +mkdir: cannot create directory 'MNT/dir/subdir': Required key not available + +# Try to lock directory while other user has unlocked +[ERROR] fscrypt lock: Directory "MNT/dir" couldn't be fully + locked because other user(s) have unlocked it. + +If you want to force the directory to be locked, use: + + sudo fscrypt lock --all-users "MNT/dir" +contents +"MNT/dir" is now locked. +cat: MNT/dir/file: No such file or directory + +# Try to operate on locked regular file +"MNT/dir" is now locked. +[ERROR] fscrypt status: cannot operate on locked regular file + "MNT/file" + +It is not possible to operate directly on a locked regular file, since the +kernel does not support this. Specify the parent directory instead. (For loose +files, any directory with the file's policy works.) +[ERROR] fscrypt unlock: cannot operate on locked regular file + "MNT/file" + +It is not possible to operate directly on a locked regular file, since the +kernel does not support this. Specify the parent directory instead. (For loose +files, any directory with the file's policy works.) diff --git a/cli-tests/t_lock.sh b/cli-tests/t_lock.sh new file mode 100755 index 0000000..e5df4df --- /dev/null +++ b/cli-tests/t_lock.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Test locking a directory. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" +mkdir "$dir" + +_print_header "Encrypt directory" +echo hunter2 | fscrypt encrypt --quiet --name=prot "$dir" +fscrypt status "$dir" +echo contents > "$dir/file" + +_print_header "Lock directory" +fscrypt lock "$dir" +_print_header "=> filenames should be in encrypted form" +_expect_failure "cat '$dir/file'" +_print_header "=> shouldn't be able to create a subdirectory" +_expect_failure "mkdir '$dir/subdir'" + +_print_header "Unlock directory" +echo hunter2 | fscrypt unlock "$dir" +fscrypt status "$dir" +cat "$dir/file" + +_print_header "Try to lock directory while files busy" +exec 3<"$dir/file" +_expect_failure "fscrypt lock '$dir'" +_print_header "=> status should be incompletely locked" +fscrypt status "$dir" +_print_header "=> open file should still be readable" +cat "$dir/file" +_print_header "=> shouldn't be able to create a new file" +_expect_failure "bash -c \"echo contents > '$dir/file2'\"" + +_print_header "Finish locking directory" +exec 3<&- +fscrypt lock "$dir" +fscrypt status "$dir" +_expect_failure "cat '$dir/file'" +_expect_failure "mkdir '$dir/subdir'" + +_print_header "Try to lock directory while other user has unlocked" +rm -rf "$dir" +mkdir "$dir" +chown "$TEST_USER" "$dir" +_user_do "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" +_user_do "echo contents > $dir/file" +_expect_failure "fscrypt lock '$dir'" +cat "$dir/file" +fscrypt lock --all-users "$dir" +_expect_failure "cat '$dir/file'" + +_print_header "Try to operate on locked regular file" +_reset_filesystems +rm -rf "$dir" +mkdir "$dir" +echo hunter2 | fscrypt encrypt --quiet --name=prot "$dir" +echo contents > "$dir/file" +mv "$dir/file" "$MNT/file" # Make it a loose encrypted file. +fscrypt lock "$dir" +_expect_failure "fscrypt status '$MNT/file'" +_expect_failure "fscrypt unlock '$MNT/file'" diff --git a/cli-tests/t_metadata.out b/cli-tests/t_metadata.out new file mode 100644 index 0000000..bbcc0f2 --- /dev/null +++ b/cli-tests/t_metadata.out @@ -0,0 +1,19 @@ +ext4 filesystem "MNT" has 3 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "foo" +desc2 No custom protector "bar" +desc3 No custom protector "baz" + +POLICY UNLOCKED PROTECTORS +desc4 No desc1, desc2, desc3 +ext4 filesystem "MNT" has 2 protectors and 1 policy. +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc1 No custom protector "foo" +desc2 No custom protector "bar" + +POLICY UNLOCKED PROTECTORS +desc4 No desc1 diff --git a/cli-tests/t_metadata.sh b/cli-tests/t_metadata.sh new file mode 100755 index 0000000..e688eda --- /dev/null +++ b/cli-tests/t_metadata.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Test 'fscrypt metadata'. + +cd "$(dirname "$0")" +. common.sh + +# Create three protectors, and a policy protected by them. +echo foo | fscrypt metadata create protector "$MNT" \ + --quiet --name=foo --source=custom_passphrase +echo bar | fscrypt metadata create protector "$MNT" \ + --quiet --name=bar --source=custom_passphrase +echo baz | fscrypt metadata create protector "$MNT" \ + --quiet --name=baz --source=custom_passphrase +prot_foo=$MNT:$(_get_protector_descriptor "$MNT" custom foo) +prot_bar=$MNT:$(_get_protector_descriptor "$MNT" custom bar) +desc_baz=$(_get_protector_descriptor "$MNT" custom baz) +prot_baz=$MNT:$desc_baz +echo foo | fscrypt metadata create policy "$MNT" --quiet \ + --protector="$prot_foo" +policy=$MNT:$(fscrypt status "$MNT" | grep -A10 "^POLICY" | \ + tail -1 | awk '{print $1}') +echo -e "bar\nfoo" | fscrypt metadata add-protector-to-policy --quiet \ + --policy="$policy" --protector="$prot_bar" +echo -e "baz\nfoo" | fscrypt metadata add-protector-to-policy --quiet \ + --policy="$policy" --protector="$prot_baz" --unlock-with="$prot_foo" +fscrypt status "$MNT" + +# Remove two of the protectors from the policy. +# Make sure that this works even if the protector was already deleted. +fscrypt metadata remove-protector-from-policy --quiet --force \ + --policy="$policy" --protector="$prot_bar" +rm "$MNT/.fscrypt/protectors/$desc_baz" +fscrypt metadata remove-protector-from-policy --quiet --force \ + --policy="$policy" --protector="$prot_baz" +fscrypt status "$MNT" diff --git a/cli-tests/t_not_enabled.out b/cli-tests/t_not_enabled.out new file mode 100644 index 0000000..07c9aa3 --- /dev/null +++ b/cli-tests/t_not_enabled.out @@ -0,0 +1,63 @@ + +# Disable encryption on DEV + +# Try to encrypt a directory when encryption is disabled +[ERROR] fscrypt encrypt: encryption not enabled on filesystem + MNT (DEV). + +To enable encryption support on this filesystem, run: + + sudo tune2fs -O encrypt "DEV" + +Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation +for more details. + +# Try to unlock a directory when encryption is disabled +[ERROR] fscrypt unlock: encryption not enabled on filesystem + MNT (DEV). + +To enable encryption support on this filesystem, run: + + sudo tune2fs -O encrypt "DEV" + +Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation +for more details. + +# Try to lock a directory when encryption is disabled +[ERROR] fscrypt lock: encryption not enabled on filesystem + MNT (DEV). + +To enable encryption support on this filesystem, run: + + sudo tune2fs -O encrypt "DEV" + +Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation +for more details. + +# Check for additional message when GRUB appears to be installed +[ERROR] fscrypt encrypt: encryption not enabled on filesystem + MNT (DEV). + +To enable encryption support on this filesystem, run: + + sudo tune2fs -O encrypt "DEV" + +WARNING: you seem to have GRUB installed on this filesystem. Before doing the +above, make sure you are using GRUB v2.04 or later; otherwise your system will +become unbootable. + +Also ensure that your kernel has CONFIG_FS_ENCRYPTION=y. See the documentation +for more details. + +# Enable encryption on DEV + +# Encrypt a directory when encryption was just enabled +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" diff --git a/cli-tests/t_not_enabled.sh b/cli-tests/t_not_enabled.sh new file mode 100755 index 0000000..fae1094 --- /dev/null +++ b/cli-tests/t_not_enabled.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Test that fscrypt fails when the filesystem doesn't have the encrypt feature +# enabled. Then test enabling it. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" +mkdir "$dir" + +_print_header "Disable encryption on $DEV" +count_before=$(_get_enabled_fs_count) +umount "$MNT" +_run_noisy_command "debugfs -w -R 'feature -encrypt' '$DEV'" +mount "$DEV" "$MNT" +count_after=$(_get_enabled_fs_count) +(( count_after == count_before - 1 )) || _fail "wrong enabled count" + +_print_header "Try to encrypt a directory when encryption is disabled" +_expect_failure "fscrypt encrypt '$dir'" + +_print_header "Try to unlock a directory when encryption is disabled" +_expect_failure "fscrypt unlock '$dir'" + +_print_header "Try to lock a directory when encryption is disabled" +_expect_failure "fscrypt lock '$dir'" + +_print_header "Check for additional message when GRUB appears to be installed" +mkdir -p "$MNT/boot/grub" +_expect_failure "fscrypt encrypt '$dir'" +rm -r "${MNT:?}/boot" + +_print_header "Enable encryption on $DEV" +_run_noisy_command "tune2fs -O encrypt '$DEV'" + +_print_header "Encrypt a directory when encryption was just enabled" +echo hunter2 | fscrypt encrypt --quiet --source=custom_passphrase --name=prot "$dir" +fscrypt status "$dir" diff --git a/cli-tests/t_not_supported.out b/cli-tests/t_not_supported.out new file mode 100644 index 0000000..68e0897 --- /dev/null +++ b/cli-tests/t_not_supported.out @@ -0,0 +1,9 @@ + +# Mount tmpfs + +# Try to create fscrypt metadata on tmpfs +[ERROR] fscrypt setup: filesystem type tmpfs is not supported for fscrypt setup + +# Try to encrypt a directory on tmpfs +[ERROR] fscrypt encrypt: This kernel doesn't support encryption on tmpfs + filesystems. diff --git a/cli-tests/t_not_supported.sh b/cli-tests/t_not_supported.sh new file mode 100755 index 0000000..8b52392 --- /dev/null +++ b/cli-tests/t_not_supported.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Test that fscrypt fails when the filesystem doesn't support encryption. + +cd "$(dirname "$0")" +. common.sh + +_print_header "Mount tmpfs" +umount "$MNT" +mount tmpfs -t tmpfs -o size=128m "$MNT" + +_print_header "Try to create fscrypt metadata on tmpfs" +_expect_failure "fscrypt setup --quiet '$MNT'" + +_print_header "Try to encrypt a directory on tmpfs" +mkdir "$MNT/dir" +_expect_failure "fscrypt encrypt '$MNT/dir'" diff --git a/cli-tests/t_passphrase_hashing.out b/cli-tests/t_passphrase_hashing.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cli-tests/t_passphrase_hashing.out diff --git a/cli-tests/t_passphrase_hashing.sh b/cli-tests/t_passphrase_hashing.sh new file mode 100755 index 0000000..a67dd7c --- /dev/null +++ b/cli-tests/t_passphrase_hashing.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Test that the passphrase hashing seems to take long enough. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" + +# Test encrypting 5 dirs with default of 1s. +fscrypt setup --force --quiet +start_time=$(date +%s) +for i in $(seq 5); do + rm -rf "$dir" + mkdir "$dir" + echo hunter2 | fscrypt encrypt --quiet --name="prot$i" "$dir" +done +end_time=$(date +%s) +elapsed=$((end_time - start_time)) +if (( elapsed <= 3 )); then + _fail "Passphrase hashing was much faster than expected! (expected about 5 x 1 == 5s, got ${elapsed}s)" +fi + +# Test encrypting 1 dir with difficulty overridden to 5s. +fscrypt setup --force --quiet --time=5s +start_time=$(date +%s) +rm -rf "$dir" +mkdir "$dir" +echo hunter2 | fscrypt encrypt --quiet --name=prot6 "$dir" +end_time=$(date +%s) +elapsed=$((end_time - start_time)) +if (( elapsed <= 3 )); then + _fail "Passphrase hashing was much faster than expected! (expected about 5s, got ${elapsed}s)" +fi diff --git a/cli-tests/t_setup.out b/cli-tests/t_setup.out new file mode 100644 index 0000000..ab0052c --- /dev/null +++ b/cli-tests/t_setup.out @@ -0,0 +1,51 @@ + +# fscrypt setup creates fscrypt.conf +Defaulting to policy_version 2 because kernel supports it. +Customizing passphrase hashing difficulty for this system... +Created global config file at "FSCRYPT_CONF". +Skipping creating MNT_ROOT/.fscrypt because it already exists. + +# fscrypt setup creates fscrypt.conf and /.fscrypt +Defaulting to policy_version 2 because kernel supports it. +Customizing passphrase hashing difficulty for this system... +Created global config file at "FSCRYPT_CONF". +Allow users other than root to create fscrypt metadata on this filesystem? (See +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] Metadata directories created at "MNT_ROOT/.fscrypt", writable by everyone. + +# fscrypt setup when fscrypt.conf already exists (cancel) +Replace "FSCRYPT_CONF"? [y/N] [ERROR] fscrypt setup: operation canceled + +# fscrypt setup when fscrypt.conf already exists (cancel 2) +Replace "FSCRYPT_CONF"? [y/N] [ERROR] fscrypt setup: operation canceled + +# fscrypt setup when fscrypt.conf already exists (accept) +Replace "FSCRYPT_CONF"? [y/N] Defaulting to policy_version 2 because kernel supports it. +Customizing passphrase hashing difficulty for this system... +Created global config file at "FSCRYPT_CONF". +Skipping creating MNT_ROOT/.fscrypt because it already exists. + +# fscrypt setup --quiet when fscrypt.conf already exists +[ERROR] fscrypt setup: operation would be destructive + +If desired, use --force to automatically run destructive operations. + +# fscrypt setup --quiet --force when fscrypt.conf already exists + +# fscrypt setup filesystem +Allow users other than root to create fscrypt metadata on this filesystem? (See +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] Metadata directories created at "MNT/.fscrypt", writable by everyone. + +# fscrypt setup filesystem (already set up) +[ERROR] fscrypt setup: filesystem MNT is already setup for + use with fscrypt + +# no config file +[ERROR] fscrypt setup: "FSCRYPT_CONF" doesn't exist + +Run "sudo fscrypt setup" to create this file. + +# bad config file +[ERROR] fscrypt setup: "FSCRYPT_CONF" is invalid: proto: + syntax error (line 1:1): invalid value bad + +Either fix this file manually, or run "sudo fscrypt setup" to recreate it. diff --git a/cli-tests/t_setup.sh b/cli-tests/t_setup.sh new file mode 100755 index 0000000..f7e302d --- /dev/null +++ b/cli-tests/t_setup.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Test 'fscrypt setup'. + +cd "$(dirname "$0")" +. common.sh + +# global setup + +_print_header "fscrypt setup creates fscrypt.conf" +rm -f "$FSCRYPT_CONF" +fscrypt setup --time=1ms + +_print_header "fscrypt setup creates fscrypt.conf and /.fscrypt" +_rm_metadata "$MNT_ROOT" +rm -f "$FSCRYPT_CONF" +echo y | fscrypt setup --time=1ms +[ -e "$MNT_ROOT/.fscrypt" ] + +_print_header "fscrypt setup when fscrypt.conf already exists (cancel)" +_expect_failure "echo | fscrypt setup --time=1ms" + +_print_header "fscrypt setup when fscrypt.conf already exists (cancel 2)" +_expect_failure "echo N | fscrypt setup --time=1ms" + +_print_header "fscrypt setup when fscrypt.conf already exists (accept)" +echo y | fscrypt setup --time=1ms + +_print_header "fscrypt setup --quiet when fscrypt.conf already exists" +_expect_failure "fscrypt setup --quiet --time=1ms" + +_print_header "fscrypt setup --quiet --force when fscrypt.conf already exists" +fscrypt setup --quiet --force --time=1ms + + +# filesystem setup + +_print_header "fscrypt setup filesystem" +_rm_metadata "$MNT" +echo y | fscrypt setup "$MNT" +[ -e "$MNT/.fscrypt" ] + +_print_header "fscrypt setup filesystem (already set up)" +_expect_failure "fscrypt setup '$MNT'" + +_print_header "no config file" +rm -f "$FSCRYPT_CONF" +_expect_failure "fscrypt setup '$MNT'" + +_print_header "bad config file" +echo bad > "$FSCRYPT_CONF" +_expect_failure "fscrypt setup '$MNT'" diff --git a/cli-tests/t_single_user.out b/cli-tests/t_single_user.out new file mode 100644 index 0000000..d038d52 --- /dev/null +++ b/cli-tests/t_single_user.out @@ -0,0 +1,30 @@ +ext4 filesystem "MNT" has 0 protectors and 0 policies. +Only root can create fscrypt metadata on this filesystem. + +ext4 filesystem "MNT" has 0 protectors and 0 policies (only including ones owned by fscrypt-test-user or root). +Only root can create fscrypt metadata on this filesystem. + + +# Encrypt, lock, and unlock as root +"MNT/dir" is now locked. + +# Encrypt as root with user's login protector + +IMPORTANT: See "MNT/dir/fscrypt_recovery_readme.txt" for + important recovery instructions. It is *strongly recommended* to + record the recovery passphrase in a secure location; otherwise you + will lose access to this directory if you reinstall the operating + system or move this filesystem to another system. + +Protector desc1 no longer protecting policy desc2. +"MNT/dir" is now locked. +Enter login passphrase for fscrypt-test-user: "MNT/dir" is now unlocked and ready for use. + +# Encrypt as user (should fail) +[ERROR] fscrypt encrypt: user lacks permission to create fscrypt metadata on + MNT + +For how to allow users to create fscrypt metadata on a filesystem, refer to +https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem + +# Encrypt as user if they set up filesystem (should succeed) diff --git a/cli-tests/t_single_user.sh b/cli-tests/t_single_user.sh new file mode 100755 index 0000000..c569f20 --- /dev/null +++ b/cli-tests/t_single_user.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Test 'fscrypt setup' without --all-users. + +cd "$(dirname "$0")" +. common.sh + +_rm_metadata "$MNT_ROOT" +_rm_metadata "$MNT" +rm "$FSCRYPT_CONF" +fscrypt setup --time=1ms --quiet +fscrypt setup --time=1ms --quiet "$MNT" +fscrypt status "$MNT" +_user_do "fscrypt status \"$MNT\"" + +dir=$MNT/dir + +begin() +{ + _reset_filesystems + mkdir "$dir" + _print_header "$1" +} + +begin "Encrypt, lock, and unlock as root" +echo hunter2 | fscrypt encrypt --quiet --name=dir --skip-unlock "$dir" +echo hunter2 | fscrypt unlock --quiet "$dir" +fscrypt lock "$dir" + +begin "Encrypt as root with user's login protector" +echo TEST_USER_PASS | fscrypt encrypt --quiet --source=pam_passphrase --user="$TEST_USER" "$dir" +# The user should be able to update the policy and protectors created by the +# above command themselves. The easiest way to test this is by updating the +# policy to remove the auto-generated recovery protector. This verifies that +# (a) the policy was made owned by the user, and that (b) policy updates fall +# back to overwrites when the process cannot write to the containing directory. +# (It would be better to test updating the protectors too, but this is the +# easiest test to do here.) +policy=$(fscrypt status "$dir" | awk '/Policy/{print $2}') +recovery_protector=$(_get_protector_descriptor "$MNT" custom 'Recovery passphrase for dir') +_user_do "fscrypt metadata remove-protector-from-policy --force --protector=$MNT:$recovery_protector --policy=$MNT:$policy" +chown "$TEST_USER" "$dir" +_user_do "fscrypt lock $dir" +_user_do "echo TEST_USER_PASS | fscrypt unlock $dir" + +begin "Encrypt as user (should fail)" +chown "$TEST_USER" "$dir" +_user_do_and_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=dir --skip-unlock \"$dir\"" + +begin "Encrypt as user if they set up filesystem (should succeed)" +_rm_metadata "$MNT" +chown "$TEST_USER" "$MNT" +chown "$TEST_USER" "$dir" +_user_do "fscrypt setup --time=1ms --quiet $MNT" +_user_do "echo hunter2 | fscrypt encrypt --quiet --name=dir3 --skip-unlock \"$dir\"" diff --git a/cli-tests/t_status.out b/cli-tests/t_status.out new file mode 100644 index 0000000..058c62c --- /dev/null +++ b/cli-tests/t_status.out @@ -0,0 +1,50 @@ + +# Get status of setup mountpoint via global status +ext4 supported Yes +ext4 supported Yes + +# Get status of setup mountpoint +ext4 filesystem "MNT" has 0 protectors and 0 policies. +All users can create fscrypt metadata on this filesystem. + +ext4 filesystem "MNT" has 0 protectors and 0 policies (only including ones owned by fscrypt-test-user or root). +All users can create fscrypt metadata on this filesystem. + + +# Get status of unencrypted directory on setup mountpoint +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Remove fscrypt metadata from MNT + +# Check enabled / setup count again + +# Get status of not-setup mounntpoint via global status +ext4 supported No +ext4 supported No + +# Get status of not-setup mountpoint +[ERROR] fscrypt status: filesystem MNT is not setup for use + with fscrypt + +Run "sudo fscrypt setup MNT" to use fscrypt on this +filesystem. +[ERROR] fscrypt status: filesystem MNT is not setup for use + with fscrypt + +Run "sudo fscrypt setup MNT" to use fscrypt on this +filesystem. + +# Get status of unencrypted directory on not-setup mountpoint +[ERROR] fscrypt status: filesystem MNT is not setup for use + with fscrypt + +Run "sudo fscrypt setup MNT" to use fscrypt on this +filesystem. +[ERROR] fscrypt status: filesystem MNT is not setup for use + with fscrypt + +Run "sudo fscrypt setup MNT" to use fscrypt on this +filesystem. diff --git a/cli-tests/t_status.sh b/cli-tests/t_status.sh new file mode 100755 index 0000000..cfc3616 --- /dev/null +++ b/cli-tests/t_status.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Test getting global, filesystem, and unencrypted directory status +# when the filesystem is or isn't set up for fscrypt. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" +mkdir "$dir" + +filter_mnt_status() +{ + awk '$1 == "'"$MNT"'" { print $3, $4, $5 }' +} + +# Initially, $MNT has encryption enabled and fscrypt setup. + +enabled_count1=$(_get_enabled_fs_count) +setup_count1=$(_get_setup_fs_count) + + +_print_header "Get status of setup mountpoint via global status" +fscrypt status | filter_mnt_status +_user_do "fscrypt status" | filter_mnt_status + +_print_header "Get status of setup mountpoint" +fscrypt status "$MNT" +_user_do "fscrypt status '$MNT'" + +_print_header "Get status of unencrypted directory on setup mountpoint" +_expect_failure "fscrypt status '$dir'" +_user_do_and_expect_failure "fscrypt status '$dir'" + +_print_header "Remove fscrypt metadata from $MNT" +_rm_metadata "$MNT" + +# Now, $MNT has encryption enabled but fscrypt *not* setup. + +_print_header "Check enabled / setup count again" +enabled_count2=$(_get_enabled_fs_count) +setup_count2=$(_get_setup_fs_count) +(( enabled_count2 == enabled_count1 )) || _fail "wrong enabled count" +(( setup_count2 == setup_count1 - 1 )) || _fail "wrong setup count" + +_print_header "Get status of not-setup mounntpoint via global status" +fscrypt status | filter_mnt_status +_user_do "fscrypt status" | filter_mnt_status + +_print_header "Get status of not-setup mountpoint" +_expect_failure "fscrypt status '$MNT'" +_user_do_and_expect_failure "fscrypt status '$MNT'" + +_print_header "Get status of unencrypted directory on not-setup mountpoint" +_expect_failure "fscrypt status '$dir'" +_user_do_and_expect_failure "fscrypt status '$dir'" diff --git a/cli-tests/t_unlock.out b/cli-tests/t_unlock.out new file mode 100644 index 0000000..b3c9b2a --- /dev/null +++ b/cli-tests/t_unlock.out @@ -0,0 +1,116 @@ + +# Encrypt directory with --skip-unlock + +# => Check dir status +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +touch: cannot touch 'MNT/dir/file': Required key not available + +# => Get policy status via mount: +desc1 No desc2 + +# Unlock directory +Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use. + +# => Check dir status +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# => Get policy status via mount: +desc1 Yes desc2 + +# Lock by cycling mount + +# => Check dir status +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +mkdir: cannot create directory 'MNT/dir/subdir': Required key not available + +# => Get policy status via mount: +desc1 No desc2 + +# Try to unlock with wrong passphrase +[ERROR] fscrypt unlock: incorrect key provided +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Unlock directory +Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use. + +# => Check dir status +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:2 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +contents + +# => Get policy status via mount: +desc1 Yes desc2 + +# Try to unlock with corrupt policy metadata +[ERROR] fscrypt unlock: fscrypt metadata file at + "MNT/.fscrypt/policies/desc1" + is corrupt: proto: cannot parse invalid wire-format data + +# Try to unlock with missing policy metadata +[ERROR] fscrypt unlock: filesystem "MNT" does not contain + the policy metadata for "MNT/dir". + This directory has either been encrypted with another + tool (such as e4crypt), or the file + "MNT/.fscrypt/policies/desc20" + has been deleted. + +# Try to unlock with missing protector metadata +[ERROR] fscrypt unlock: could not load any protectors + +You may need to mount a linked filesystem. Run with --verbose for more +information. + +# Try to unlock with wrong policy metadata +[ERROR] fscrypt unlock: inconsistent metadata between encrypted directory + "MNT/dir1" and its corresponding + metadata file + "MNT/.fscrypt/policies/desc21". + + Directory has + descriptor:desc21 padding:32 + contents:AES_256_XTS filenames:AES_256_CTS + policy_version:2 + + Metadata file has + descriptor:desc23 padding:32 + contents:AES_256_XTS filenames:AES_256_CTS + policy_version:2 diff --git a/cli-tests/t_unlock.sh b/cli-tests/t_unlock.sh new file mode 100755 index 0000000..e32b0f7 --- /dev/null +++ b/cli-tests/t_unlock.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Test unlocking a directory. + +cd "$(dirname "$0")" +. common.sh + +dir="$MNT/dir" +mkdir "$dir" + +_print_header "Encrypt directory with --skip-unlock" +echo hunter2 | fscrypt encrypt --quiet --name=prot --skip-unlock "$dir" +_print_header "=> Check dir status" +fscrypt status "$dir" +_expect_failure "touch '$dir/file'" +policy=$(fscrypt status "$dir" | awk '/Policy:/{print $2}') +_print_header "=> Get policy status via mount:" +fscrypt status "$MNT" | grep "^$policy" + +_print_header "Unlock directory" +echo hunter2 | fscrypt unlock "$dir" +_print_header "=> Check dir status" +fscrypt status "$dir" +echo contents > "$dir/file" +_print_header "=> Get policy status via mount:" +fscrypt status "$MNT" | grep "^$policy" + +_print_header "Lock by cycling mount" +umount "$MNT" +mount "$DEV" "$MNT" +_print_header "=> Check dir status" +fscrypt status "$dir" +_expect_failure "mkdir '$dir/subdir'" +_print_header "=> Get policy status via mount:" +fscrypt status "$MNT" | grep "^$policy" + +_print_header "Try to unlock with wrong passphrase" +_expect_failure "echo bad | fscrypt unlock --quiet '$dir'" +fscrypt status "$dir" + +_print_header "Unlock directory" +echo hunter2 | fscrypt unlock "$dir" +_print_header "=> Check dir status" +fscrypt status "$dir" +cat "$dir/file" +_print_header "=> Get policy status via mount:" +fscrypt status "$MNT" | grep "^$policy" + +_print_header "Try to unlock with corrupt policy metadata" +umount "$MNT" +mount "$DEV" "$MNT" +echo bad > "$MNT/.fscrypt/policies/$policy" +_expect_failure "echo hunter2 | fscrypt unlock '$dir'" + +_reset_filesystems + +_print_header "Try to unlock with missing policy metadata" +mkdir "$dir" +echo hunter2 | fscrypt encrypt --quiet --name=prot --skip-unlock "$dir" +rm "$MNT"/.fscrypt/policies/* +_expect_failure "echo hunter2 | fscrypt unlock '$dir'" + +_reset_filesystems + +_print_header "Try to unlock with missing protector metadata" +mkdir "$dir" +echo hunter2 | fscrypt encrypt --quiet --name=prot --skip-unlock "$dir" +rm "$MNT"/.fscrypt/protectors/* +_expect_failure "echo hunter2 | fscrypt unlock '$dir'" + +_print_header "Try to unlock with wrong policy metadata" +_reset_filesystems +mkdir "$MNT/dir1" +mkdir "$MNT/dir2" +echo hunter2 | fscrypt encrypt --quiet --name=dir1 --skip-unlock "$MNT/dir1" +echo hunter2 | fscrypt encrypt --quiet --name=dir2 --skip-unlock "$MNT/dir2" +policy1=$(find "$MNT/.fscrypt/policies/" -type f | head -1) +policy2=$(find "$MNT/.fscrypt/policies/" -type f | tail -1) +mv "$policy1" "$TMPDIR/policy" +mv "$policy2" "$policy1" +mv "$TMPDIR/policy" "$policy2" +_expect_failure "echo hunter2 | fscrypt unlock '$MNT/dir1'" diff --git a/cli-tests/t_v1_policy.out b/cli-tests/t_v1_policy.out new file mode 100644 index 0000000..f14f357 --- /dev/null +++ b/cli-tests/t_v1_policy.out @@ -0,0 +1,144 @@ + +# Set policy_version 1 + +# Try to encrypt as root +[ERROR] fscrypt encrypt: user must be specified when run as root + +When running this command as root, you usually still want to provision/remove +keys for a normal user's keyring and use a normal user's login passphrase as a +protector (so the corresponding files will be accessible for that user). This +can be done with --user=USERNAME. To use the root user's keyring or passphrase, +use --user=root. + +# Try to use --user=root as user +[ERROR] fscrypt encrypt: could not access user keyring for "root": setting uids: + operation not permitted + +You can only use --user=USERNAME to access the user keyring of another user if +you are running as root. + +# Try to encrypt without user keyring in session keyring +[ERROR] fscrypt encrypt: user keyring for "fscrypt-test-user" is not linked into + the session keyring + +This is usually the result of a bad PAM configuration. Either correct the +problem in your PAM stack, enable pam_keyinit.so, or run "keyctl link @u @s". + +# Encrypt a directory + +# Get dir status as user +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Get dir status as root +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: Partially (incompletely locked, or unlocked by another user) + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Create files in v1-encrypted directory + +# Try to lock v1-encrypted directory as user +[ERROR] fscrypt lock: inode cache can only be dropped as root + +Either this command should be run as root to properly clear the inode cache, or +it should be run with --drop-caches=false (this may leave encrypted files and +directories in an accessible state). +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Try to lock v1-encrypted directory as root without --user +[ERROR] fscrypt lock: user must be specified when run as root + +When running this command as root, you usually still want to provision/remove +keys for a normal user's keyring and use a normal user's login passphrase as a +protector (so the corresponding files will be accessible for that user). This +can be done with --user=USERNAME. To use the root user's keyring or passphrase, +use --user=root. +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Lock v1-encrypted directory +Encrypted data removed from filesystem cache. +"MNT/dir" is now locked. +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +cat: MNT/dir/file: No such file or directory + +# Testing incompletely locking v1-encrypted directory +Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use. +Encrypted data removed from filesystem cache. +[ERROR] fscrypt lock: Directory was incompletely locked because some files are + still open. These files remain accessible. + +Try killing any processes using files in the directory, for example using: + + find "MNT/dir" -print0 | xargs -0 fuser -k + +Then re-run: + + fscrypt lock "MNT/dir" +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: Partially (incompletely locked, or unlocked by another user) + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +ext4 filesystem "MNT" has 1 protector and 1 policy (only including ones owned by fscrypt-test-user or root). +All users can create fscrypt metadata on this filesystem. + +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +POLICY UNLOCKED PROTECTORS +desc1 No desc2 + +# Finishing locking v1-encrypted directory +Encrypted data removed from filesystem cache. +"MNT/dir" is now locked. +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +cat: MNT/dir/file: No such file or directory diff --git a/cli-tests/t_v1_policy.sh b/cli-tests/t_v1_policy.sh new file mode 100755 index 0000000..46ccdaf --- /dev/null +++ b/cli-tests/t_v1_policy.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Test using v1 encryption policies (deprecated). + +cd "$(dirname "$0")" +. common.sh + +_setup_session_keyring +trap _cleanup_user_keyrings EXIT + +dir="$MNT/dir" +mkdir "$dir" +chown "$TEST_USER" "$dir" + +_print_header "Set policy_version 1" +sed -E -i 's/"policy_version": +"2"/"policy_version": "1"/' "$FSCRYPT_CONF" + +_print_header "Try to encrypt as root" +_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" + +_print_header "Try to use --user=root as user" +_user_do_and_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=prot --user=root '$dir'" + +_print_header "Try to encrypt without user keyring in session keyring" +_user_do "keyctl unlink @u @s" +_user_do_and_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" +_user_do "keyctl link @u @s" + +_print_header "Encrypt a directory" +_user_do "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" + +_print_header "Get dir status as user" +_user_do "fscrypt status '$dir'" + +_print_header "Get dir status as root" +fscrypt status "$dir" + +_print_header "Create files in v1-encrypted directory" +echo contents > "$dir/file" +mkdir "$dir/subdir" +ln -s target "$dir/symlink" + +# Due to the limitations of the v1 key management mechanism, 'fscrypt lock' only +# works when run as root and with the --user argument. + +_print_header "Try to lock v1-encrypted directory as user" +_user_do_and_expect_failure "fscrypt lock '$dir'" +_user_do "fscrypt status '$dir'" + +_print_header "Try to lock v1-encrypted directory as root without --user" +_expect_failure "fscrypt lock '$dir'" +_user_do "fscrypt status '$dir'" + +_print_header "Lock v1-encrypted directory" +fscrypt lock "$dir" --user="$TEST_USER" +_user_do "fscrypt status '$dir'" +_expect_failure "cat '$dir/file'" + +# 'fscrypt lock' and 'fscrypt status' implement a heuristic that should detect +# the "files busy" case with v1. +_print_header "Testing incompletely locking v1-encrypted directory" +_user_do "echo hunter2 | fscrypt unlock '$dir'" +exec 3<"$dir/file" +_expect_failure "fscrypt lock '$dir' --user='$TEST_USER'" +_user_do "fscrypt status '$dir'" +# ... except in this case, because we can't detect it without a directory path. +_user_do "fscrypt status '$MNT'" +exec 3<&- +_print_header "Finishing locking v1-encrypted directory" +fscrypt lock "$dir" --user="$TEST_USER" +_user_do "fscrypt status '$dir'" +_expect_failure "cat '$dir/file'" diff --git a/cli-tests/t_v1_policy_fs_keyring.out b/cli-tests/t_v1_policy_fs_keyring.out new file mode 100644 index 0000000..9f0f0ab --- /dev/null +++ b/cli-tests/t_v1_policy_fs_keyring.out @@ -0,0 +1,75 @@ + +# Enable v1 policies with fs keyring + +# Try to encrypt directory as user +[ERROR] fscrypt encrypt: root is required to add/remove v1 encryption policy + keys to/from filesystem + +Either this command should be run as root, or you should set +'"use_fs_keyring_for_v1_policies": false' in /etc/fscrypt.conf, or you should +re-create your encrypted directories using v2 encryption policies rather than v1 +(this requires setting '"policy_version": "2"' in the "options" section of +/etc/fscrypt.conf). +[ERROR] fscrypt status: file or directory "MNT/dir" is not + encrypted + +# Encrypt directory as user with --skip-unlock +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" +mkdir: cannot create directory 'MNT/dir/subdir': Required key not available + +# Try to unlock directory as user +[ERROR] fscrypt unlock: root is required to add/remove v1 encryption policy keys + to/from filesystem + +Either this command should be run as root, or you should set +'"use_fs_keyring_for_v1_policies": false' in /etc/fscrypt.conf, or you should +re-create your encrypted directories using v2 encryption policies rather than v1 +(this requires setting '"policy_version": "2"' in the "options" section of +/etc/fscrypt.conf). + +# Unlock directory as root +Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use. +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: Yes + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Try to lock directory as user +[ERROR] fscrypt lock: root is required to add/remove v1 encryption policy keys + to/from filesystem + +Either this command should be run as root, or you should set +'"use_fs_keyring_for_v1_policies": false' in /etc/fscrypt.conf, or you should +re-create your encrypted directories using v2 encryption policies rather than v1 +(this requires setting '"policy_version": "2"' in the "options" section of +/etc/fscrypt.conf). + +# Lock directory as root +"MNT/dir" is now locked. +cat: MNT/dir/file: No such file or directory +"MNT/dir" is encrypted with fscrypt. + +Policy: desc1 +Options: padding:32 contents:AES_256_XTS filenames:AES_256_CTS policy_version:1 +Unlocked: No + +Protected with 1 protector: +PROTECTOR LINKED DESCRIPTION +desc2 No custom protector "prot" + +# Check that user can access file when directory is unlocked by root +Enter custom passphrase for protector "prot": "MNT/dir" is now unlocked and ready for use. +contents diff --git a/cli-tests/t_v1_policy_fs_keyring.sh b/cli-tests/t_v1_policy_fs_keyring.sh new file mode 100755 index 0000000..a8fd333 --- /dev/null +++ b/cli-tests/t_v1_policy_fs_keyring.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Test using v1 encryption policies (deprecated) with +# use_fs_keyring_for_v1_policies = true. + +# This works similar to v2 policies, except locking and unlocking (including +# 'fscrypt encrypt' without --skip-unlock) will only work as root. + +cd "$(dirname "$0")" +. common.sh + +_print_header "Enable v1 policies with fs keyring" +sed -E -e 's/"use_fs_keyring_for_v1_policies": +false/"use_fs_keyring_for_v1_policies": true/' \ + -e 's/"policy_version": +"2"/"policy_version": "1"/' \ + -i "$FSCRYPT_CONF" + +dir="$MNT/dir" +mkdir "$dir" +chown "$TEST_USER" "$dir" + +_print_header "Try to encrypt directory as user" +_user_do_and_expect_failure "echo hunter2 | fscrypt encrypt --quiet --name=prot '$dir'" +_expect_failure "fscrypt status '$dir'" + +_print_header "Encrypt directory as user with --skip-unlock" +_user_do "echo hunter2 | fscrypt encrypt --quiet --name=prot --skip-unlock '$dir'" +fscrypt status "$dir" +_expect_failure "mkdir '$dir/subdir'" + +_print_header "Try to unlock directory as user" +_user_do_and_expect_failure "echo hunter2 | fscrypt unlock '$dir'" + +_print_header "Unlock directory as root" +echo hunter2 | fscrypt unlock "$dir" +mkdir "$dir/subdir" +echo contents > "$dir/file" +fscrypt status "$dir" + +_print_header "Try to lock directory as user" +_user_do_and_expect_failure "fscrypt lock '$dir'" + +_print_header "Lock directory as root" +fscrypt lock "$dir" +_expect_failure "cat '$dir/file'" +fscrypt status "$dir" + +_print_header "Check that user can access file when directory is unlocked by root" +echo hunter2 | fscrypt unlock "$dir" +_user_do "cat '$dir/file'" |