#!/usr/bin/env bash # packages: 7zip, shred, secure-delete, cracklib-runtime, openssl, curl set -o errtrace set -o nounset set -o pipefail IFS=$'\n\t' unix_seconds=$(date +%s) key_path="./private_ed25519_${unix_seconds}" signature_tag="file-integrity" out_dir="./out" inner_dir="$out_dir/contents" RED='\033[31m' GREEN='\033[32m' RESET='\033[0m' num_of_args="$#" all_args="$@" require_command() { if ! command -v "$1" >/dev/null 2>&1; then echo "Missing required command: $1" >&2 exit 1 fi } require_dependencies() { local deps=(bash shred srm openssl curl ssh-keygen 7z sha512sum awk grep realpath) for dep in "${deps[@]}"; do require_command "$dep" done } checkcode() { local retcode="${1:-}" if [[ -z "$retcode" ]]; then echo -e "\n${RED}ERROR!${RESET} checkcode missing return code parameter\n" >&2 exit 1 fi if [[ "$retcode" -ne 0 ]]; then echo -e "${RED}ERROR!${RESET} Response code: $retcode" >&2 exit "$retcode" fi printf ' %bOK!%b\n' "$GREEN" "$RESET" } run_cmd() { "$@" checkcode $? } reset() { printf 'Autoshredding known artifacts...\n' find . -maxdepth 1 -type f \( -name 'private_*' -o -name 'attribution_passphrase_*' -o -name '*.sha512' -o -name 'checksums*' -o -name '*.sig' -o -name '*.7z' -o -name 'anonymous_signer' \) -exec shred -uz {} + checkcode $? if compgen -G 'private_*' >/dev/null 2>&1; then printf 'Shredding errant private key files...\n' shred -uz private_* || true fi if compgen -G 'attribution_passphrase_*' >/dev/null 2>&1; then printf 'Shredding errant attribution passphrase files...\n' shred -uz attribution_passphrase_* || true fi printf 'Removing previous output directory...\n' rm -rf "$out_dir" checkcode $? printf 'Rebuilding output directory structure...\n' mkdir -p "$inner_dir" checkcode $? printf 'Writing placeholder README files...\n' echo 'put files to verifiably archive in here' > "$inner_dir/README.md" checkcode $? echo '# todo: make this nice' > "$out_dir/README.md" checkcode $? printf 'Copying verification helpers...\n' cp test_validate_passphrase.txt "$out_dir/test_validate_passphrase.sh" checkcode $? chmod +x "$out_dir/test_validate_passphrase.sh" checkcode $? cp verify-everything.txt "$out_dir/verify-everything.sh" checkcode $? chmod +x "$out_dir/verify-everything.sh" checkcode $? local housekeeping_dirs=(archives keystore) for dir in "${housekeeping_dirs[@]}"; do if [[ -d "$dir" ]]; then printf 'Hardening %s...\n' "$dir" chmod 700 "$dir" checkcode $? find "$dir" -mindepth 1 -type d -exec srm -r -z -l '{}' + >/dev/null 2>&1 || true find "$dir" -type f \( -name 'private_ed25519_*' -o -name 'attribution_passphrase_*' \) -exec shred -uz '{}' + >/dev/null 2>&1 || true find "$dir" -type f -exec chmod 600 '{}' + checkcode $? fi done } audit_passphrase() { local raw_password="$1" local check_password="$2" if [[ -z "$raw_password" ]]; then echo '[ERROR] No passphrase provided for validation.' >&2 return 2 fi if [[ -z "$check_password" ]]; then echo '[ERROR] No check passphrase provided for validation.' >&2 return 2 fi if [[ "$raw_password" != "$check_password" ]]; then echo '[ERROR] Passphrases do not match!' >&2 return 2 fi unset check_password 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." return 1 fi echo "[PASS] Length verification satisfied ($pass_len characters)." if command -v cracklib-check >/dev/null 2>&1; then if ! printf '%s' "$raw_password" | cracklib-check | grep -q 'OK$'; then echo '❌ REJECTED by cracklib-check.' return 1 fi echo '[PASS] Local dictionary and structural complexity audit clear.' else echo '[WARN] cracklib-check not found; skipping local dictionary audit.' >&2 fi local full_hash prefix suffix response full_hash=$(printf '%s' "$raw_password" | openssl dgst -sha1 | awk '{print toupper($2)}') prefix=${full_hash:0:5} suffix=${full_hash:5} if ! response=$(curl -fsS -A 'Bash-Passphrase-Audit-Script' "https://api.pwnedpasswords.com/range/$prefix"); then echo -e "${RED}[FATAL]${RESET} Failed to communicate with HIBP API." >&2 return 3 else echo -e "connected to hibp...${GREEN}OK${RESET}" fi if printf '%s\n' "$response" | grep -qi "^$suffix:"; then echo -e "${RED}[FATAL]${RESET} Passphrase has been leaked!" >&2 return 1 else echo -e "not leaked! (via hibp)... ${GREEN}OK${RESET}" fi return 0 } error_handle() { local exit_code=$? local script_path if command -v realpath >/dev/null 2>&1; then script_path=$(realpath "$0") else script_path="$PWD/$0" fi local hr='====================================================' echo echo "$hr" echo -e "🚨 ${RED}FATAL ERROR DETECTED${RESET}" echo "$hr" echo "-> Script : $0" echo "-> Num Script Args : $num_of_args" echo "-> Script Args : $all_args" echo "-> Shell : ${SHELL:-unknown}" echo "-> Script Path : $script_path" echo "-> Script (full) : $SHELL $script_path $all_args" echo "-> User : ${USER:-unknown}" echo "-> Working Directory : $PWD" echo "-> Failed Command : $BASH_COMMAND" echo "-> Line Number : $LINENO" echo "-> Exit Status : $exit_code" echo "-> Seconds Elapsed : $SECONDS" echo "-> Date Failed : $(date)" echo '-> Stack Trace' local frame=0 while caller "$frame"; do frame=$((frame + 1)) done echo echo "$hr" echo exit "$exit_code" } exit_cleanup() { printf "cleaning up on exit" reset > /dev/null 2>&1 checkcode $? # todo: uset vars } trap error_handle ERR trap exit_cleanup EXIT audit_passphrase 'yw0EKY6wbSxU6KJxWN3vFZDgnSjJ4F8wqFrAJMcDxfU' 'yw0EKY6wbSxU6KJxWN3vFZDgnSjJ4F8wqFrAJMcDxfU' require_dependencies printf 'Setting up environment...\n' reset printf '\n\n' read -n 1 -s -r -p "In another terminal/window, fill $inner_dir with whatever you please then press any key to continue..." printf '\n' printf 'ssh-keygen: creating new key: %s...\n' "$key_path" ssh-keygen -t ed25519 -f "$key_path" -C 'anonymous' -N '' >/dev/null 2>&1 checkcode $? printf 'ssh-keygen: fixing permissions on %s and %s...\n' "$key_path" "${key_path}.pub" chmod 600 "$key_path" "${key_path}.pub" checkcode $? printf 'ssh-keygen: creating %s/anonymous_signer...\n' "$out_dir" echo "anonymous namespaces=\"$signature_tag\" $(cat "${key_path}.pub")" > "$out_dir/anonymous_signer" checkcode $? printf 'Inject random data? (y/N): ' read -r random if [[ -z "$random" || "$random" =~ ^[nN]$ ]]; then echo -e "No random data added. ${GREEN}OK!${RESET}\n" else printf 'random: adding 1/2 random blocks of data (128 bytes) to outer archive...\n' openssl rand -out "$out_dir/.$RANDOM" 128 >/dev/null 2>&1 checkcode $? printf 'random: adding 2/2 random blocks of data (128 bytes) to inner archive...\n' openssl rand -out "$inner_dir/.$RANDOM" 128 >/dev/null 2>&1 checkcode $? fi printf '7z: compressing inner volume...\n' 7z a "$out_dir/contents.7z" "$inner_dir" >/dev/null 2>&1 checkcode $? printf 'Deleting %s...\n' "$inner_dir" rm -rf "$inner_dir" checkcode $? printf 'ssh: signing %s...\n' "$out_dir/contents.7z" ssh-keygen -Y sign -f "$key_path" -n "$signature_tag" "$out_dir/contents.7z" >/dev/null 2>&1 checkcode $? printf 'Changing directory to %s...\n' "$out_dir" cd "$out_dir" checkcode $? printf 'sha512: generating checksums...\n' sha512sum * > checksums.sha512 checkcode $? printf 'Changing directory back...\n' cd .. checkcode $? printf 'Enter attribution passphrase:\n' read -r -s attribution_passphrase printf '\nEnter attribution passphrase again:\n' read -r -s attribution_passphrase_check printf '\n' printf 'Auditing attribution passphrase...\n' ret=$(audit_passphrase "$attribution_passphrase" "$attribution_passphrase_check") echo "$ret" printf 'Unsetting attribution_passphrase_check...\n' unset attribution_passphrase_check printf 'Calculating attribution checksum...\n' { printf '%s' "$attribution_passphrase" cat "$out_dir/contents.7z" } | sha512sum | awk '{print $1}' > "$out_dir/attribution-checksum.sha512" checkcode $? printf 'Sanity checking: changing working directory to %s...\n' "$out_dir" cd "$out_dir" checkcode $? printf 'Sanity checking: verification...\n' bash verify-everything.sh "$attribution_passphrase" checkcode $? printf 'Sanity checking: validate attribution passphrase...\n' bash test_validate_passphrase.sh "$attribution_passphrase" checkcode $? printf 'Returning to project root...\n' cd .. checkcode $? printf 'Unsetting attribution_passphrase...\n' unset attribution_passphrase printf '7z archiving outer dir...\n' 7z a ./out.7z "$out_dir" >/dev/null 2>&1 checkcode $? printf 'Moving out.7z to archives...\n' mv out.7z "archives/verifiable_archive_${unix_seconds}.7z" checkcode $? printf 'Enter keystore passphrase:\n' read -r -s keystore_passphrase printf '\nEnter keystore passphrase again:\n' read -r -s keystore_passphrase_check printf '\n' printf 'Auditing keystore passphrase...\n' ret="$(audit_passphrase \"$keystore_passphrase\" \"$keystore_passphrase_check\")" echo -e "$ret" printf 'Unsetting keystore_passphrase_check...\n' unset keystore_passphrase_check printf 'Archiving keys...\n' set +u shopt -s nullglob private_files=(private_*) passphrase_files=(attribution_passphrase_*) shopt -u nullglob set -u if [[ ${#private_files[@]} -eq 0 && ${#passphrase_files[@]} -eq 0 ]]; then echo 'No key or attribution passphrase files found to archive.' >&2 exit 1 fi 7z a "keystore/keystore_${unix_seconds}.7z" "${private_files[@]}" "${passphrase_files[@]}" -p"$keystore_passphrase" -mhe=on >/dev/null 2>&1 checkcode $? printf 'Testing key archive...\n' 7z t "keystore/keystore_${unix_seconds}.7z" -p"$keystore_passphrase" >/dev/null 2>&1 checkcode $?