diff --git a/common/config b/common/config
index 479e50d1..85a7919c 100644
--- a/common/config
+++ b/common/config
@@ -228,6 +228,7 @@ export E2IMAGE_PROG="$(type -P e2image)"
 export BLKZONE_PROG="$(type -P blkzone)"
 export GZIP_PROG="$(type -P gzip)"
 export BTRFS_IMAGE_PROG="$(type -P btrfs-image)"
+export FSCRYPTCTL_PROG="$(type -P fscryptctl)"
 
 # use 'udevadm settle' or 'udevsettle' to wait for lv to be settled.
 # newer systems have udevadm command but older systems like RHEL5 don't.
diff --git a/common/encrypt b/common/encrypt
index d8e2dba9..e91e05c4 100644
--- a/common/encrypt
+++ b/common/encrypt
@@ -58,22 +58,21 @@ _require_scratch_encryption()
 	# If required, check for support for the specific type of encryption
 	# policy required by the test.
 	if [ $# -ne 0 ]; then
-		_require_encryption_policy_support $SCRATCH_MNT "$@"
+		_require_scratch_encryption_policy_support "$@"
 	fi
 
 	_scratch_unmount
 }
 
-_require_encryption_policy_support()
+_require_scratch_encryption_policy_support()
 {
-	local mnt=$1
-	local dir=$mnt/tmpdir
+	local dir=$SCRATCH_MNT/tmpdir
 	local set_encpolicy_args=""
 	local policy_flags=0
 	local policy_version=1
 	local c
 
-	OPTIND=2
+	OPTIND=1
 	while getopts "c:n:f:v:" c; do
 		case $c in
 		c|n)
@@ -94,6 +93,12 @@ _require_encryption_policy_support()
 	done
 	set_encpolicy_args=${set_encpolicy_args# }
 
+	if (( policy_flags & FSCRYPT_POLICY_FLAG_HW_WRAPPED_KEY )); then
+		echo "Checking whether $SCRATCH_DEV supports hardware-wrapped keys" \
+			>> $seqres.full
+		_require_hw_wrapped_key_support $SCRATCH_DEV
+	fi
+
 	echo "Checking whether kernel supports encryption policy: $set_encpolicy_args" \
 		>> $seqres.full
 
@@ -117,8 +122,15 @@ _require_encryption_policy_support()
 		# Both the kernel and xfs_io support v2 encryption policies, and
 		# therefore also filesystem-level keys -- since that's the only
 		# way to provide keys for v2 policies.
-		local raw_key=$(_generate_raw_encryption_key)
-		local keyspec=$(_add_enckey $mnt "$raw_key" | awk '{print $NF}')
+		if (( policy_flags & FSCRYPT_POLICY_FLAG_HW_WRAPPED_KEY )); then
+			local raw_key=$(_generate_raw_hw_key)
+			local keyspec=$(_add_hw_wrapped_key $SCRATCH_DEV \
+					$SCRATCH_MNT "$raw_key")
+		else
+			local raw_key=$(_generate_raw_encryption_key)
+			local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" | \
+					awk '{print $NF}')
+		fi
 	else
 		_require_command "$KEYCTL_PROG" keyctl
 		_new_session_keyring
@@ -143,6 +155,47 @@ _require_encryption_policy_support()
 	rm -r $dir
 }
 
+# Require that the filesystem accepts the "inlinecrypt" mount option.
+#
+# Note: this doesn't check whether $SCRATCH_DEV has any specific inline
+# encryption capabilities.  For encryption policies that require specific
+# capabilities, support is detected later by
+# _require_scratch_encryption_policy_support() (provided that inlinecrypt has
+# been added to $MOUNT_OPTIONS by that point).
+_require_scratch_inlinecrypt()
+{
+	_require_scratch
+	_scratch_mkfs &>> $seqres.full
+	if ! _try_scratch_mount -o inlinecrypt &>> $seqres.full; then
+		_notrun "filesystem doesn't support -o inlinecrypt"
+	fi
+}
+
+# Require that the given block device supports hardware-wrapped inline
+# encryption keys, and require that a command-line tool that supports
+# importing/generating/preparing them is available.
+_require_hw_wrapped_key_support()
+{
+	local dev=$1
+
+	_require_command "$FSCRYPTCTL_PROG" fscryptctl
+	if ! "$FSCRYPTCTL_PROG" --help | grep -q "import_hw_wrapped_key"; then
+		_notrun "fscryptctl too old; doesn't support hardware-wrapped inline encryption keys"
+	fi
+
+	if ! head -c $RAW_HW_KEY_SIZE /dev/urandom | \
+		"$FSCRYPTCTL_PROG" import_hw_wrapped_key "$dev" \
+		>/dev/null 2>$tmp.err
+	then
+		if grep -E -q \
+			"(Operation not supported)|(Inappropriate ioctl for device)" $tmp.err
+		then
+			_notrun "$dev doesn't support hardware-wrapped inline encryption keys"
+		fi
+		_fail "Unexpected error from fscryptctl import_hw_wrapped_key: $(< $tmp.err)"
+	fi
+}
+
 _scratch_mkfs_encrypted()
 {
 	case $FSTYP in
@@ -220,14 +273,24 @@ _generate_key_descriptor()
 # Generate a raw encryption key, but don't add it to any keyring yet.
 _generate_raw_encryption_key()
 {
+	local size=${1:-64}
 	local raw=""
 	local i
-	for ((i = 0; i < 64; i++)); do
+	for ((i = 0; i < $size; i++)); do
 		raw="${raw}\\x$(printf "%02x" $(( $RANDOM % 256 )))"
 	done
 	echo $raw
 }
 
+RAW_HW_KEY_SIZE=32
+
+# Generate a raw key of the proper size to be imported as a hardware-wrapped
+# key.
+_generate_raw_hw_key()
+{
+	_generate_raw_encryption_key $RAW_HW_KEY_SIZE
+}
+
 # Serialize an integer into a CPU-endian bytestring of the given length, and
 # print it as a string where each byte is hex-escaped.  For example,
 # `_num_to_hex 1000 4` == "\xe8\x03\x00\x00" if the CPU is little endian.
@@ -358,6 +421,21 @@ _add_enckey()
 	echo -ne "$raw_key" | $XFS_IO_PROG -c "add_enckey $*" "$mnt"
 }
 
+# Create a hardware-wrapped key from the given raw key using the given block
+# device, add it to the given filesystem, and print the resulting key
+# identifier.
+_add_hw_wrapped_key()
+{
+	local dev=$1
+	local mnt=$2
+	local raw_key=$3
+
+	echo -ne "$raw_key" | \
+		$FSCRYPTCTL_PROG import_hw_wrapped_key "$dev" | \
+		$FSCRYPTCTL_PROG prepare_hw_wrapped_key "$dev" | \
+		$FSCRYPTCTL_PROG add_key --hw-wrapped-key "$mnt"
+}
+
 _user_do_add_enckey()
 {
 	local mnt=$1
@@ -771,6 +849,7 @@ FSCRYPT_MODE_ADIANTUM=9
 FSCRYPT_POLICY_FLAG_DIRECT_KEY=0x04
 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64=0x08
 FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32=0x10
+FSCRYPT_POLICY_FLAG_HW_WRAPPED_KEY=0x20
 
 FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR=1
 FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER=2
@@ -800,6 +879,7 @@ _fscrypt_mode_name_to_num()
 #	'direct':		test the DIRECT_KEY policy flag
 #	'iv_ino_lblk_64':	test the IV_INO_LBLK_64 policy flag
 #	'iv_ino_lblk_32':	test the IV_INO_LBLK_32 policy flag
+#	'hw_wrapped_key':	test the HW_WRAPPED_KEY policy flag
 #
 _verify_ciphertext_for_encryption_policy()
 {
@@ -832,6 +912,9 @@ _verify_ciphertext_for_encryption_policy()
 		iv_ino_lblk_32)
 			(( policy_flags |= FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 ))
 			;;
+		hw_wrapped_key)
+			(( policy_flags |= FSCRYPT_POLICY_FLAG_HW_WRAPPED_KEY ))
+			;;
 		*)
 			_fail "Unknown option '$opt' passed to ${FUNCNAME[0]}"
 			;;
@@ -855,6 +938,10 @@ _verify_ciphertext_for_encryption_policy()
 		elif (( policy_flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 )); then
 			crypt_util_args+=" --iv-ino-lblk-32"
 		fi
+		if (( policy_flags & FSCRYPT_POLICY_FLAG_HW_WRAPPED_KEY )); then
+			crypt_util_args+=" --enable-hw-kdf"
+			crypt_util_contents_args+=" --use-inlinecrypt-key"
+		fi
 	else
 		if (( policy_flags & ~FSCRYPT_POLICY_FLAG_DIRECT_KEY )); then
 			_fail "unsupported flags for v1 policy: $policy_flags"
@@ -891,11 +978,18 @@ _verify_ciphertext_for_encryption_policy()
 	crypt_util_filename_args+="$crypt_util_args"
 
 	echo "Generating encryption key" >> $seqres.full
-	local raw_key=$(_generate_raw_encryption_key)
 	if (( policy_version > 1 )); then
-		local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" \
-				| awk '{print $NF}')
+		if (( policy_flags & FSCRYPT_POLICY_FLAG_HW_WRAPPED_KEY )); then
+			local raw_key=$(_generate_raw_hw_key)
+			local keyspec=$(_add_hw_wrapped_key $SCRATCH_DEV \
+					$SCRATCH_MNT "$raw_key")
+		else
+			local raw_key=$(_generate_raw_encryption_key)
+			local keyspec=$(_add_enckey $SCRATCH_MNT "$raw_key" | \
+					awk '{print $NF}')
+		fi
 	else
+		local raw_key=$(_generate_raw_encryption_key)
 		local keyspec=$(_generate_key_descriptor)
 		_new_session_keyring
 		_add_session_encryption_key $keyspec $raw_key
