Inzwischen bin ich ein heimlicher Fan von den günstigen TP-Link WLAN Routern, besonders dem TL-MR3420: Mit OpenWRT ausgestattet laufen die Geräte super stabil und haben für die verfügbaren Bandbreiten (seufz) ausreichend Leistung um ein VPN-Netz quer durch die Republik aufzuspannen.
OpenWRT Verschlüsselung Beitragsbild
Um ausreichend Platz für die nötigen Programmpakete zu schaffen muss das Dateisystem mit externem Speicher am USB-Anschluss erweitert werden. Leider bringt das ein nicht unerhebliches Risiko mit sich: Die geheimen VPN-Zugangsdaten liegen ungeschützt auf dem Stick — der unbemerkt abgezogen und ausgelesen werden könnte!
Natürlich lassen sich die Daten auch aus dem Router klauen. Dafür müsste man allerdings direkten Zugriff auf die Hardware haben, also zumindest den Failsafe-Modus aktivieren oder sogar den Flash direkt auslesen.

Abhilfe schafft die Verschlüsselung des Sticks mit einer im Router hinterlegten Schlüsseldatei. Ohne den Router ist der Stick damit unlesbar und wertlos, durch die Schlüsseldatei muss beim Einschalten trotzdem kein Passwort eingegeben werden.
Die folgende Anleitung bezieht sich auf den TP-Link TL-MR3420 und die aktuelle OpenWRT Version 15 „Chaos Calmer“, mit kleinen Änderungen bei der Paketauswahl sollte es auf fast allen Geräten ähnlich funktionieren.
Weitere Informationen zum Aufspielen der Images finden sich im OpenWRT Wiki zum TL-MR3420.

Update 10/2016: Erfolgreich getestet mit Chaos Calmer 15.05.1!

Funktionsweise


In dieser Anleitung kommt die Extroot/Pivot-Root Methode zum Einsatz. Dabei wird das gesamte Root-Dateiystem (/) auf den USB Stick kopiert und im PreInit beim Hochfahren statt dem Dateisystem im Gerätespeicher eingehangen. Übrigens ist das auch der Grund wieso nicht der gesamte Stick verschlüsselt werden kann: Das Dateisystem wird umgeschaltet noch bevor alle Kernelmodule geladen sind und die Init-Scripte in /etc/init.d/ ausgeführt werden. Hier einzugreifen ist riskant, aber bestimmt nicht unmöglich.

Was liegt wo?

  • Auf dem Router
    • OpenWRT Grundsystem
    • Upgrades (per sysupgrade.bin) werden hier gespeichert
    • Schlüsseldatei für die LUKS-Partition
  • Auf dem USB-Stick
    • Kopie des Grundsystem
    • Erweiterungen & Konfiguration
    • Verschlüsselte Partition mit VPN Zugangsdaten

Eigene Firmware bauen

Von nur 4MB eingebautem Flash-Speicher bleibt bei den regulären OpenWRT Images nur wenig Platz für die notwendigen Erweiterungen. Da ich den Router an einer bestehenden Internetverbindung betreibe habe ich die Module für Einwahlverbindungen (PPP, PPPoE) entfernt und stattdessen die USB-Treiber eingebaut. Die restlichen Module (besonders das Webinterface) bleiben dabei unverändert erhalten.

Ohne die lange „Package“ Liste erzeugt der ImageBuilder eine minimale Firmware ohne Webinterface, DHCP oder Firewall!

ImageBuilder 🔨

OpenWRT bietet für jede Architektur (hier: AR71xx) nicht nur fertige Binärdateien an sondern auch einen ImageBuilder, der angepasste Firmware Images erzeugen kann. Neben den benötigten Paketen können in diesem Schritt auch angepasste Einstellungen eingebunden werden.

Diese Konfiguration erzeugt das angepasste Image:

Neue Pakete:
  USB Stick: kmod-fs-ext4 kmod-usb-storage block-mount kmod-scsi-core

Entferne Pakete:
  PPP: -kmod-ppp -kmod-pppoe -kmod-pppox -luci-proto-ppp -ppp -ppp-mod-pppoe

Ergibt diese Paketliste:
	base-files busybox dnsmasq dropbear firewall fstools hostapd-common 
	ip6tables iptables iw jshn jsonfilter kernel kmod-ath kmod-ath9k 
	kmod-ath9k-common kmod-cfg80211 kmod-crypto-aes kmod-crypto-arc4 
	kmod-crypto-core kmod-gpio-button-hotplug kmod-ip6tables 
	kmod-ipt-conntrack kmod-ipt-core kmod-ipt-nat kmod-ipv6 
	kmod-ledtrig-usbdev kmod-lib-crc-ccitt kmod-mac80211 kmod-nf-conntrack 
	kmod-nf-conntrack6 kmod-nf-ipt kmod-nf-ipt6 kmod-nf-nat 
	kmod-nf-nathelper kmod-nls-base -kmod-ppp -kmod-pppoe -kmod-pppox 
	kmod-slhc kmod-usb-core kmod-usb2 libblobmsg-json libc libgcc libip4tc 
	libip6tc libiwinfo libiwinfo-lua libjson-c libjson-script liblua 
	libnl-tiny libubox libubus libubus-lua libuci libuci-lua libxtables lua 
	luci luci-app-firewall luci-base luci-lib-ip luci-lib-nixio 
	luci-mod-admin-full luci-proto-ipv6 -luci-proto-ppp luci-theme-bootstrap 
	mtd netifd odhcp6c odhcpd opkg -ppp -ppp-mod-pppoe procd rpcd swconfig 
	uboot-envtools ubox ubus ubusd uci uhttpd uhttpd-mod-ubus usign 
	wpad-mini kmod-fs-ext4 kmod-usb-storage block-mount kmod-scsi-core 

Angepasste Konfiguration (im ImageBuilder Verzeichnis ablegen):

./files/etc/config/dropbear (Dropbear nur im LAN erreichbar)
./files/etc/sysupgrade.conf (LUKS Schlüssel beim Upgrade sichern)

Image zusammenbauen:
make image PROFILE="TLMR3420" PACKAGES="[alle Pakete von oben]" FILES="files/"

Hinzuzufügende Dateien:

config dropbear
	option PasswordAuth 'on'
	option RootPasswordAuth 'on'
	option Port         '22'
#	option BannerFile   '/etc/banner'
	option Interface    'lan'
## This file contains files and directories that should
## be preserved during an upgrade.

/root/luks
/etc/crtab.conf
/etc/init.d/crtab

Das erzeugte Image liegt im Verzeichnis ./bin/ar71xx und kann wie eine vorcompilierte Version aufgespielt werden:

  • …factory.bin: Zum „Umflashen“ der ursprünglichen Firmware auf OpenWRT
  • …sysupgrade.bin: Zum Aktualisieren einer vorhandenen Installation (dabei unbedingt die Konfiguration zurücksetzen!)

OpenWRT Luci nach dem ersten Einschalten
Direkt nach dem Aufspielen sollte über Telnet (192.168.1.1:23) ein Root-Passwort vergeben werden um den SSH Zugang zu aktivieren. Selbstverständlich muss auch das Basissystem abgesichert werden, da es die Schlüssel zum Auslesen des USB-Sticks enthält. Mehr zur ersten Einrichtung und Absicherung steht im OpenWRT Wiki!

236kB freier Speicher trotz USB Treiber:

Filesystem                Size      Used Available Use% Mounted on
rootfs                  448.0K    212.0K    236.0K  47% /
/dev/root                 2.5M      2.5M         0 100% /rom
tmpfs                    14.0M     60.0K     14.0M   0% /tmp
tmpfs                    14.0M     48.0K     14.0M   0% /tmp/root
tmpfs                   512.0K         0    512.0K   0% /dev
/dev/mtdblock3          448.0K    212.0K    236.0K  47% /overlay
overlayfs:/overlay      448.0K    212.0K    236.0K  47% /

USB-Stick vorbereiten

USB Stick partitionieren (GParted)

Für den nächsten Schritt habe ich einen kleinen USB-Stick bestellt (Intenso Micro Line, 4GB) und formatiert:

  • sdX1: 2048MB Pivot Root (mit 4MB Offset)
  • sdX2: 512MB VPN Zugangsdaten
  • sdX3: 96MB SWAP
mkfs.ext4 -L pivot /dev/sdX1

Optional: Ausrichtung der Partition prüfen
parted /dev/sdX
align-check opt 1

Für alte Sticks: Vorher komplett löschen und in GParted eine neue MSDOS Partitionstabelle erstellen
dd if=/dev/zero of=/dev/sdX bs=4M

Damit ist die erste Partition bereit für extroot!


Grundsystem kopieren & extroot einrichten

Weiter geht es mit dem vorbereitetem Stick am Router. Wenn alles funktioniert wird die Partition vom „block“ Tool erkannt und mit ihrer UUID aufgelistet (wichtig für die fstab).

Partitionen anzeigen lassen mit:
block info

/dev/mtdblock2: UUID="xyz" VERSION="4.0" TYPE="squashfs"
/dev/mtdblock3: TYPE="jffs2"
/dev/sda1: UUID="" LABEL="pivot" NAME="EXT_JOURNAL" VERSION="1.0" TYPE="ext4"

Dateisystem auf Stick kopieren
mkdir /mnt/cproot
mount /dev/sda1 /mnt/cproot

mkdir -p /tmp/cproot
mount --bind / /tmp/cproot
tar -C /tmp/cproot -cvf - . | tar -C /mnt/cproot -xf -
umount /tmp/cproot
umount /mnt/cproot
sync

Die Reihenfolge der Schritte ist durchaus wichtig: Nachdem das minimal konfigurierte Grundsystem kopiert wurde werden jetzt die geheimen Schlüssel erzeugt. Die sollen natürlich nur auf dem Router liegen und nicht kopiert werden!
Als kleine Hilfe habe ich an dieser Stelle die Datei /etc/banner in der Dropbear Konfiguration aktiviert und einen Hinweis eingefügt. So wird man beim SSH-Login sofort benachrichtigt wenn das System nicht vom USB-Stick hochgefahren wurde.

Verschlüsselung vorbereiten 🔓

Für die mit LUKS verschlüsselte Partition wird noch eine Schlüsseldatei benötigt:

mkdir /root/luks
dd if=/dev/urandom of=/root/luks/store.key bs=1024 count=4
chmod 0400 /root/luks/store.key

Extroot aktivieren

Damit der Stick beim Hochfahren automatisch eingebunden wird muss die fstab erweitert werden. Die Zuordnung erfolgt über die UUID der Partition oder das Label „pivot“. Wenn man hier nur die UUID zulässt wird jeder anders formatierte Stick abgelehnt.

config 'mount'
        option target		'/'
        option uuid		''
	option label		'pivot'
	option fstype		'ext4'
	option options		'rw,sync,relatime'
        option enabled		'1'
	option enabled_fsck	'1'

Nach einem Neustart fährt der Router ab jetzt automatisch vom USB-Stick hoch!

Jetzt steht massig Speicherplatz zur Verfügung:

Filesystem                Size      Used Available Use% Mounted on
rootfs                    1.9G     12.6M      1.8G   1% /
/dev/root                 2.5M      2.5M         0 100% /rom
tmpfs                    14.0M    300.0K     13.7M   2% /tmp
/dev/sda1                 1.9G     12.6M      1.8G   1% /
tmpfs                   512.0K         0    512.0K   0% /dev

Router einrichten

Bevor es weiter geht muss eine Internetverbindung zum Nachladen der nächsten Pakete eingerichtet werden, dabei sollte auch gleich die passende Zeitzone, NTP-Server, Hostname usw. eingerichtet werden.

Verschlüsselung: cryptsetup kmod-dm kmod-crypto-xts kmod-crypto-iv kmod-crypto-misc kmod-crypto-sha256
Tools: swap-utils e2fsprogs wget ca-certificates nano

Die Schlüsseldatei liegt im Flash-Speicher des Routers, der seit der Einrichtung von extroot nicht mehr eingehangen wird. Um trotzdem Zugriff zu bekommen wird das ursprüngliche Root-Dateisystem unter /overlay-boot eingehangen.

mkdir /overlay-boot
chmod 444 /overlay-boot

Das automatische Einhängen übernimmt (wieder mal) ein Eintrag in der fstab. Beim TL-MR3420 liegt das Dateisystem im Flash-Block #3 (/dev/mtdblock3), für andere Router findet man die Belegung meistens im OpenWRT Wiki. Bis zum nächsten Neustart kann die Partition mit „mount -o ro -t jffs2 /dev/mtdblock3 /overlay-boot“ eingehangen werden, jeweils ohne Schreibzugriff.

config mount
	option target	/overlay-boot
	option device	/dev/mtdblock3
	option fstype	jffs2
	option options	ro,sync
	option enabled	1
	option enabled_fsck 0

Verschlüsselte Partition anlegen

Nach den Vorbereitungen kann jetzt endlich die verschlüsselte Partition erstellt werden!

Eine Besonderheit ist die dritte Partition (SWAP): Sie wird beim Hochfahren mit einem “Wegwerfschlüssel” (aus /dev/random) verschlüsselt und neu formatiert. Dadurch bleiben die Schlüssel geheim auch wenn der Arbeitsspeicher auf den Stick gesichert wird. Alternativ könnte auch die Option „mlock“ in der OpenVPN Konfiguration gesetzt werden.
Um nicht versehentlich (z.B. USB-Stick vertauscht) eine wichtige Partition zu überschreiben muss der SWAP-Bereich eindeutig gekennzeichnet werden. Dazu wird ein kleines (1MB) ext2-Dateisystem erstellt, das ein Volume-Label und eine UUID im Header enthält. Das crypttab-Script verwendet einen Offset (2048) und formatiert den freien Bereich hinter der ext2-Partition, so bleibt das Label erhalten und die Zuordnung eindeutig.

Verschlüsselte Partition erstellen & bekannt machen:
cryptsetup --cipher=aes-xts-plain64 --key-size=512 --hash=sha256 --use-random --iter-time=2500 --key-file=/overlay-boot/upper/root/luks/store.key --key-slot=0 luksFormat /dev/sdX2
sync

Prüfen ob der LUKS Header erstellt wurde:
cryptsetup luksDump /dev/sdX2

Manuell entschlüsseln und formatieren:
cryptsetup open --type luks /dev/sdX2 store --key-file=/overlay-boot/upper/root/luks/store.key
mkfs.ext4 -L store /dev/mapper/store

UUID für Swap anlegen (1MB ext2 Partition):
mkfs.ext2 -L swap /dev/sdX3 1M

Partitionen anzeigen lassen mit:
block info

/dev/mtdblock2: UUID="xyz" VERSION="4.0" TYPE="squashfs"
/dev/mtdblock3: TYPE="jffs2"
/dev/sda1: UUID="abc" LABEL="pivot" NAME="EXT_JOURNAL" VERSION="1.0" TYPE="ext4"
/dev/sda3: UUID="" LABEL="swap" VERSION="1.0" TYPE="ext2"
/dev/mapper/store: UUID="def" LABEL="store" NAME="EXT_JOURNAL" VERSION="1.0" TYPE="ext4"

Änderungen sichern und Partition schließen:
sync
cryptsetup close store

Einhängepunkt für verschlüsselten Bereich erstellen:
mkdir /store
chmod 444 /store

Automatische Entschlüsselung 🔑

Um die Partitionen nicht bei jedem Hochfahren manuell entschlüsseln zu müssen habe ich ein Initscript geschrieben:

"/etc/init.d/crtab" anlegen mit Inhalt aus Listing weiter unten

Datei ausführbar machen:
chmod +x /etc/init.d/crtab
#!/bin/sh /etc/rc.common
# crtab launcher
#
# tab format: action device mapper key mount/swap-id
# luks-key /dev/sdX1 crypt /my.key /secret
# cryptswap <uuid> swap /dev/rand <swap-label>
#
# Copyright (C) 2015 luani.de

# Init sequence
START=45	#after fstab
STOP=95		#before umount

# Path to crtab file
CRTAB=/etc/crtab.conf
 
start() {        
	if [ ! -r "$CRTAB" ]; then
		exit
	fi
	while read action device mapper key mount
	do
		if [ ! -n "$action$device$mapper$key" ]; then continue; fi
		case $action in
			luks-key)
				echo "Opening LUKS device $device using keyfile"

				cryptsetup isLuks $device
				if [ $? -ne 0 ]; then
					echo "No LUKS partition found!"
					continue
				fi

				cryptsetup open --type luks $device $mapper --key-file=$key
				local cs=$?
				if [ $cs -eq 0 ]; then
					echo "Device opened as /dev/mapper/$mapper"
					if [ -n "$mount" ]; then
						mount /dev/mapper/$mapper $mount
						if [ $? -eq 0 ]; then echo "Mounted /dev/mapper/$mapper as $mount"; else echo "Mount failed"; fi
					fi
				else
					echo "LUKS status is $cs"
				fi
				;;

			cryptswap)
				# get device by uuid
				local devLn=$(devbyid $device)

				if [ ! -n "$devLn" ]; then
					echo "No device assigned to UUID $device"
					continue
				fi

				echo "Creating encrypted SWAP on $devLn"

				#Offset 2048 to preserve UUID (ext2 header)
				cryptsetup --cipher=aes-xts-plain64 --offset=2048 --key-size=256 --key-file=$key create $mapper $devLn
				local cs=$?
				if [ $cs -eq 0 ]; then
					if [ -n "$mount" ]; then mkswap -L $mount /dev/mapper/$mapper; else mkswap -L $mapper /dev/mapper/$mapper; fi
					
					if [ $? -eq 0 ]; then
						swapon /dev/mapper/$mapper
						if [ $? -eq 0 ]; then echo "Enabled SWAP on /dev/mapper/$mapper"; else echo "SWAP formatted, not enabled"; fi
					else
						echo "Formatting failed"
					fi
				else
					echo "LUKS status is $cs"
				fi
				;;

			*)
				echo "No action specified: $action, Usage: {luks-key|cryptswap}"
				;;
		esac
	done < "$CRTAB"
}                 
 
stop() {          
	if [ ! -r "$CRTAB" ]; then
		exit
	fi
	while read action device mapper key mount
	do
		if [ ! -n "$action$device$mapper$key" ]; then continue; fi
		case $action in
			luks*)
				echo "Closing LUKS /dev/mapper/$mapper"

				local ms=$(cat /proc/mounts | grep /dev/mapper/$mapper)
				if [ -n "$ms" ]; then
					umount $mount
					if [ $? -ne 0 ]; then 
						echo "Device busy, remounting read-only"
						umount -r $mount
						continue
					else
						echo "Unmounted $mount"
					fi
				fi
 
				cryptsetup close $mapper 
				local cs=$?
				if [ $cs -eq 0 ]; then
					echo "Device closed: $device"
				else
					echo "LUKS status is $cs"
				fi
				;;

			cryptswap)
				# get device by uuid
				local devLn=$(devbyid $device)

				if [ ! -n "$devLn" ]; then
					echo "No device assigned to UUID $device"
					continue
				fi

				echo "Closing SWAP /dev/mapper/$mapper"

				local ms=$(swapon -s | grep /dev/mapper/$mapper)
				if [ -n "$ms" ]; then
					swapoff /dev/mapper/$mapper
					if [ $? -ne 0 ]; then 
						echo "SWAP busy"
						continue
					else
						echo "Disabled SWAP on /dev/mapper/$mapper"
					fi
				fi
 
				cryptsetup close $mapper 
				local cs=$?
				if [ $cs -eq 0 ]; then
					echo "Device closed: $devLn"
				else
					echo "LUKS status is $cs"
				fi
				;;

			*)
				echo "No action specified: $action, Usage: {luks-key|cryptswap}"
				;;
		esac
	done < "$CRTAB"
}

devbyid() {
	local uuid=$1
 
	local bli=$(/sbin/block info | grep $uuid)
	if [ -n "$bli" ]; then
		echo "$bli" | cut -d ":" -f 1
	fi
}

Crtab

Die Zuordnung der Partitionen, Schlüssel und Mapper erledigt die Datei /etc/crtab.conf (ähnlich wie die fstab). Trotz der Namensähnlichkeit unterstützt diese „Crypttab“ allerdings nur die hier vorgestellten Optionen, das Initscript ist nicht kompatibel mit der Debian Crypttab!

Für die SWAP Partition muss die UUID der kleinen ext2-Partition herausgefunden werden:

block info | grep swap

Liefert z.B.
/dev/sda3: UUID="[UUID]" LABEL="swap" VERSION="1.0" TYPE="ext2"

In die Crtab eintragen:
(action) (device) (mapper) (key) (mount/swap-id)

luks-key	/dev/sda2	store	/overlay-boot/upper/root/luks/store.key	/store
cryptswap	[UUID]		swap	/dev/urandom	cryptswap

Aktivieren

Dieser Aufruf startet das Initscript und legt die Symlinks für den automatischen Start an:

/etc/init.d/crtab enable

Geschafft! Ab jetzt steht der verschlüsselte Speicher unter /store zur Verfügung.

4 Comments

  1. Vielen Dank. Jetzt funktioniert es. Tolle Anleitung! Eine Frage: Welche Veränderungen an dem Script müsste ich machen, damit ich zusätzlich eine externe LUKS verschlüsselte USB HDD einbinden kann?

    tristank
    1. Die zusätzliche Partition müsstest du genauso vorbereiten wie den „store“ Bereich auf dem Stick, nur mit anderem Namen, Mapper/Mountpoint usw.
      Für die automatische Entschlüsselung reicht dann einfach ein weiterer Eintrag in der /etc/crtab.conf, angepasst für die HDD:

      luks-key /dev/sdb{X} {mapper} /path/to/your.key /{mountpoint}

      Leo-Andres
    1. Hi, danke für die Info, das habe ich falsch beschrieben!
      Ich hatte die Konfiguration erst als ‚/etc/crypttab‘ gespeichert und wegen der Verwechslung mit Debian später in ‚/etc/crtab.conf‘ umbenannt. Im Beitrag ist das noch vertauscht, ich schaue mir das nachher im Router an und korrigiere den Absatz so wie es tatsächlich läuft.

      Versuche es bis dahin mal mit diesen zwei Zeilen:
      luks-key /dev/sda2 store /overlay-boot/upper/root/luks/store.key /store
      cryptswap [UUID] swap /dev/urandom cryptswap

      [UUID] ist die ID der SWAP Partition (sda3?)

      Leo-Andres

Schreibe einen Kommentar zu Leo-Andres Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.