92 lines
3.3 KiB
Bash
92 lines
3.3 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Enable strict error management
|
|
set -o errexit
|
|
set -o errtrace
|
|
set -o pipefail
|
|
|
|
# ==============================================================================
|
|
# Comprehensive Passphrase Audit Function
|
|
# ==============================================================================
|
|
audit_passphrase() {
|
|
local raw_password="$1"
|
|
|
|
if [[ -z "$raw_password" ]]; then
|
|
echo "[ERROR] No passphrase provided for validation." >&2
|
|
exit 2
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# GATE 1: Minimum Length Verification (35+ Characters)
|
|
# --------------------------------------------------------------------------
|
|
local pass_len="${#raw_password}"
|
|
if [ "$pass_len" -lt 35 ]; then
|
|
echo "❌ REJECTED: Passphrase is too short ($pass_len characters). Minimum length required is 35."
|
|
exit 1
|
|
fi
|
|
echo " [PASS] Length verification satisfied ($pass_len characters)."
|
|
|
|
# --------------------------------------------------------------------------
|
|
# GATE 2: Local Dictionary Check (cracklib-check)
|
|
# --------------------------------------------------------------------------
|
|
# cracklib-check reads from stdin and outputs 'password: status'
|
|
# If secure, the status string reads "OK"
|
|
if ! command -v cracklib-check &> /dev/null; then
|
|
echo "[WARN] cracklib-check binary not found. Skipping dictionary audit." >&2
|
|
else
|
|
local cracklib_result
|
|
cracklib_result=$(echo "$raw_password" | cracklib-check | cut -d':' -f2 | xargs)
|
|
|
|
if [[ "$cracklib_result" != "OK" ]]; then
|
|
echo "❌ REJECTED by cracklib-check: $cracklib_result"
|
|
exit 1
|
|
fi
|
|
echo " [PASS] Local dictionary and structural complexity audit clear."
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# GATE 3: Remote Anonymized Leak Check (HIBP API via k-Anonymity)
|
|
# --------------------------------------------------------------------------
|
|
local full_hash
|
|
full_hash=$(echo -n "$raw_password" | openssl dgst -sha1 | awk '{print toupper($2)}')
|
|
|
|
local prefix="${full_hash:0:5}"
|
|
local suffix="${full_hash:5}"
|
|
local api_url="https://api.pwnedpasswords.com/range/$prefix"
|
|
local response
|
|
|
|
if ! response=$(curl -s -H "User-Agent: Bash-Passphrase-Audit-Script" "$api_url"); then
|
|
echo "[FATAL] Failed to communicate with HIBP API." >&2
|
|
exit 3
|
|
fi
|
|
|
|
local match
|
|
match=$(echo "$response" | grep -i "^$suffix:")
|
|
|
|
if [[ -n "$match" ]]; then
|
|
local pwn_count
|
|
pwn_count=$(echo "$match" | cut -d':' -f2 | tr -d $'\r')
|
|
echo "❌ VULNERABLE: This passphrase has appeared in $pwn_count known public breaches."
|
|
exit 1
|
|
else
|
|
echo "✅ SUCCESS: Passphrase meets all local criteria and was not found in HIBP records."
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# ==============================================================================
|
|
# Execution Example
|
|
# ==============================================================================
|
|
|
|
echo "=== System Passphrase Security Enforcer ==="
|
|
read -r -s -p "Enter a new secure passphrase (35+ chars): " user_input
|
|
echo ""
|
|
|
|
# Run the complete pipeline
|
|
audit_passphrase "$user_input"
|
|
exit_status=$?
|
|
|
|
# Instantly wipe raw secret text from memory space
|
|
unset user_input
|
|
|
|
exit "$exit_status" |