#!/bin/sh
# Verify that a Debian 13 (trixie) host stores passwords with yescrypt, a salted,
# memory-hard key derivation function (IA-5(1)(d)).
#
# Debian 13 already does this, so the component configures nothing: it asserts the
# default and makes the assertion checkable. Password changes through PAM are
# hashed by pam_unix's own option; tools that bypass PAM use ENCRYPT_METHOD in
# /etc/login.defs. Both are inspected.
#
# By default no account is created or modified, so this is safe on a production
# host. --with-test-account also sets a password on a throwaway account and checks
# the hash actually stored, which is the only behavioural test.
#
# Exit status: 0 if nothing FAILs, 1 otherwise.

set -u

WITH_TEST_ACCOUNT=0
[ "${1:-}" = "--with-test-account" ] && WITH_TEST_ACCOUNT=1

PASS_COUNT=0
FAIL_COUNT=0

report() {
	case "$1" in
		PASS) PASS_COUNT=$((PASS_COUNT + 1)) ;;
		*) FAIL_COUNT=$((FAIL_COUNT + 1)) ;;
	esac
	printf '%-8s %-13s %s\n' "$1" "$2" "$3"
}

CONFIG_OK=1
if grep -qE '^[^#]*pam_unix\.so.*[[:space:]]yescrypt([[:space:]]|$)' /etc/pam.d/common-password; then
	report PASS "pam_unix" "pam_unix.so hashes with yescrypt in /etc/pam.d/common-password"
else
	CONFIG_OK=0
	report FAIL "pam_unix" "pam_unix.so in /etc/pam.d/common-password does not name yescrypt"
fi
if grep -qE '^[[:space:]]*ENCRYPT_METHOD[[:space:]]+YESCRYPT' /etc/login.defs; then
	report PASS "login.defs" "ENCRYPT_METHOD YESCRYPT in /etc/login.defs"
else
	CONFIG_OK=0
	report FAIL "login.defs" "ENCRYPT_METHOD is not YESCRYPT in /etc/login.defs"
fi

if [ "$WITH_TEST_ACCOUNT" -eq 1 ]; then
	# Long enough, and free of words, runs and repeats, for any password policy
	# another component may have applied.
	TEST_USER="ismhash$$"
	if useradd -M -s /usr/sbin/nologin "$TEST_USER" >/dev/null 2>&1 &&
		echo "$TEST_USER:vaultottermangoquiltravenfjordzebraplinth" | chpasswd >/dev/null 2>&1; then
		HASH=$(getent shadow "$TEST_USER" | cut -d: -f2)
		userdel -f "$TEST_USER" >/dev/null 2>&1
		case "$HASH" in
			'$y$'*) report PASS "ia-5.1_smt.d" "a password set through PAM is stored as a yescrypt hash" ;;
			*)      report FAIL "ia-5.1_smt.d" "a password set through PAM is stored as '$(printf '%s' "$HASH" | cut -c1-4)...', not yescrypt" ;;
		esac
	else
		userdel -f "$TEST_USER" >/dev/null 2>&1
		report FAIL "ia-5.1_smt.d" "could not set a password on a throwaway account to test with"
	fi
elif [ "$CONFIG_OK" -eq 1 ]; then
	report PASS "ia-5.1_smt.d" "configured for yescrypt (re-run with --with-test-account to check a stored hash)"
else
	report FAIL "ia-5.1_smt.d" "not configured for yescrypt"
fi

echo
echo "passed: $PASS_COUNT  failed: $FAIL_COUNT"
[ "$FAIL_COUNT" -eq 0 ] || exit 1
