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.
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.
Funktionsweise
Inhalt
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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:
1 2 3 4 5 6 |
config dropbear option PasswordAuth 'on' option RootPasswordAuth 'on' option Port '22' # option BannerFile '/etc/banner' option Interface 'lan' |
1 2 3 4 5 6 |
## 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!)
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:
1 2 3 4 5 6 7 8 |
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
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
1 2 3 4 5 6 7 8 |
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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Partitionen anzeigen lassen mit: block info /dev/mtdblock2: UUID="xyz" VERSION="4.0" TYPE="squashfs" /dev/mtdblock3: TYPE="jffs2" /dev/sda1: UUID="<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:
1 2 3 |
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.
1 2 3 4 5 6 7 8 |
config 'mount' option target '/' option uuid '<UUID aus "block info">' 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:
1 2 3 4 5 6 |
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.
1 2 |
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.
1 2 |
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.
1 2 3 4 5 6 7 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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="<SWAP-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:
1 2 3 4 |
"/etc/init.d/crtab" anlegen mit Inhalt aus Listing weiter unten Datei ausführbar machen: chmod +x /etc/init.d/crtab |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
#!/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:
1 2 3 4 |
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)
1 2 |
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:
1 |
/etc/init.d/crtab enable |
Geschafft! Ab jetzt steht der verschlüsselte Speicher unter /store zur Verfügung.
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?
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}
Ich habe versucht deiner Anleitung zu folgen, leider hast du nicht beschrieben was der Inhalt von
/etc/crtab.conf
sein soll.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?)