#!/bin/bash

# Lizenz / license:
# GPL v3 or later: http://www.gnu.org/licenses/gpl-3.0

# Parameters:
# --no-infotext : optional : skip the first screen (info text)
# --exit-on-threshold : optional : exit (with code 0) if enough entropy has been collected (and the user has stopped typing)
#
# Envorinment variables:
# THRESHOLD : optional : sets $finished_threshold
#
# Signals:
# USR1: immediately start countdown to switch back

# 1.6, 14.04.2014, Hauke Laging, hauke.laging (emailzeichen) openpgp-schulungen.de
VERSION=1.6

READ_WAKEUP_THRESHOLD=32
INFOTEXT_DEFAULT=yes
THRESHOLD_ITERATIONS=5
READTIMEOUT=1
STOPTYPING_ITERATIONS=3 # seconds
SESSIONNAME="keygeneration"
WINDOW_NAME_MAIN="main"
TMP_DIR_USER="/tmp/${USER}/entropy"
PID_FILE="collect_entropy.pid"

infotext="$INFOTEXT_DEFAULT"
mode=below
exit_on_threshold="no"
switchback="no"

usage () {
	echo "Aufruf:"
	echo "'$0' [--no-infotext] [--exit-on-threshold]"
}

if [ --help = "$1" ]; then
	echo "collect_entropy.sh ${VERSION}"
	usage
	exit 0
fi

trap 'switchback="yes"' USR1

if [ yes = "$USE_SCREEN" ]; then
	sleep 1
fi

while [ $# -gt 0 ]; do
	case "$1" in
		--no-infotext)
			infotext=no
		;;
		--exit-on-threshold)
			exit_on_threshold=yes
		;;
		*)
			echo "Illegal argument; aborting."
		;;
	esac
	shift
done

trap 'stty echo' EXIT

if ! [ -f /proc/sys/kernel/random/poolsize ]; then
	cat <<EOT
FEHLER (Abbruch)
======================================================================

/proc/sys/kernel/random/poolsize nicht verfügbar; Abbruch.
EOT
	exit 1
fi
poolsize=$(</proc/sys/kernel/random/poolsize)


if [ -f /proc/sys/kernel/random/read_wakeup_threshold ]; then
	if [ -w /proc/sys/kernel/random/read_wakeup_threshold ]; then
		echo "$READ_WAKEUP_THRESHOLD" >/proc/sys/kernel/random/read_wakeup_threshold
	fi
	read_wakeup_threshold=$(</proc/sys/kernel/random/read_wakeup_threshold)
else
	read_wakeup_threshold=64
fi

if mkdir -p "${TMP_DIR_USER}"; then
	# COMMENT: keep PID file open to make it easier to check that the PID is correct from the outside
	exec 3>"${TMP_DIR_USER}/${PID_FILE}"
	echo $$ >&3
fi

if [ yes = "$infotext" ]; then
	clear
	cat <<EOT
INFORMATION (kann man mit Return überspringen, muss man nicht lesen)
======================================================================

Für die Erzeugung der Schlüssel werden Zufallszahlen benötigt. Leider stehen
die auf sicheren Systemen gerade nicht in ausreichender Menge zur Verfügung. 
Es werden normalerweise drei Schlüssel zu je 2048 Bit Länge erzeugt, dazu 
einer von 1024 Bit Länge. Linux hält maximal ${poolsize} Bit vorrätig. Das 
eine Problem ist, dass GnuPG für die Erzeugung der Schlüssel viel mehr 
Zufallsbits braucht, als die letztlich enthalten (etwa um den Faktor 20 mehr); 
das andere, dass Entropie auf sicheren Systemen nur langsam nachgeliefert 
wird. Deshalb muss der Benutzer sie durch Tastaturanschläge erzeugen. Es 
scheint so zu sein, dass man mit maximal schnellem, "chaotischen" Tippen 
am schnellsten Entropie generiert.

Dieses Script zeigt an, wie viel Bit Entropie gerade vorhanden sind. Es ist
sinnvoll, die Zeit vor der Schlüsselerzeugung zu nutzen, um den Entropiepool
aufzufüllen, allerdings kann der nur bis zum Maximum von ${poolsize} wachsen.

Wenn gpg (oder ein anderes Programm) den Entropiepool ausliest, während dieses 
Script läuft, dann wird der Pool kaum größer werden als ${read_wakeup_threshold}, weil er bei 
Erreichen dieses Wertes immer gleich wieder geleert wird. Dass Sie genug 
Entropie eingegeben haben, erkennen Sie daran, dass der angezeigte Wert 
kontinuierlich größer wird (über diesen Schwellenwert hinaus), wenn Sie tippen.

Weiter mit Return...
EOT
	read cont
fi
iter=0
finished_threshold=$((read_wakeup_threshold+1))
finished_threshold=${THRESHOLD:-$finished_threshold}

while true; do
	clear
	cat <<EOT
AKTION ERFORDERLICH: tippen
======================================================================

Drücken Sie nun nach Belieben Tasten (bis auf Enter). Tasten gedrückt zu
halten, bringt allerdings nichts.

Ziel: konstant über ${finished_threshold}

EOT
	stty -echo
	echo -n "aktuelle Größe des Entropie-Pools: "
	while true; do
		ebits=$(</proc/sys/kernel/random/entropy_avail)
		printf "%4d" "$ebits"
		if [ "$mode" = below ]; then
			if [ "$ebits" -gt "$finished_threshold" ]; then
				((iter++))
				if [ "$iter" -ge "$THRESHOLD_ITERATIONS" ]; then
					mode=above
					echo
					echo "FERTIG: Es scheint nun genug Entropie für die laufende Aktion vorhanden "
					echo "        zu sein."
					echo
					if [ "no" = "$exit_on_threshold" ]; then
						if [ "yes" = "$USE_SCREEN" ]; then
							echo
							echo "               HÖREN SIE JETZT BITTE AUF ZU TIPPEN!"
							stop_iter=0
							while true; do
								if ! read -s -t 1 -n 1 dummy; then
									((stop_iter++))
									if [ "$stop_iter" -ge "$STOPTYPING_ITERATIONS" ]; then
										break
									fi
								else
									stop_iter=0
								fi
							done
							echo
							echo "    Es wird nun zum Hauptscript zurückgeschaltet (nicht tippen!)"
							echo
							echo -n "...3..."
							sleep 1
							echo -n "2..."
							sleep 1
							echo -n "1..."
							sleep 1
							switchback="no"
							screen -S "$SESSIONNAME" -X select "$WINDOW_NAME_MAIN"
							continue 2
						else
							echo " Sie können dennoch weitertippen, um Entropie für ggf. noch folgende "
							echo "        Aktionen anzusammeln."
							echo
							echo -n "aktuelle Größe des Entropie-Pools: "
							continue
						fi
					else
						echo
						echo "               HÖREN SIE JETZT BITTE AUF ZU TIPPEN!"
						stop_iter=0
						while true; do
							if ! read -s -t 1 -n 1 dummy; then
								((stop_iter++))
								if [ "$stop_iter" -ge "$STOPTYPING_ITERATIONS" ]; then
									echo
									echo
									echo "Dieses Script beendet sich jetzt."
									sleep 1
									exit 0
								fi
							else
								stop_iter=0
							fi
						done
					fi
				fi
			else
				iter=0
			fi
		else # "$mode" = above
			if [ "yes" = "$switchback" ]; then
				switchback="no"
				mode="below"
				iter=0
			fi
			if [ "$ebits" -le "$finished_threshold" ]; then
				((iter++))
				if [ "$iter" -ge "$THRESHOLD_ITERATIONS" ]; then
					mode="below"
					continue 2
				fi
			else
				iter=0
			fi
		fi
		read -s -t "$READTIMEOUT" dummy
		erasestring="\b\b\b\b"
		echo -ne "$erasestring"
	done
done
