zur Übersicht: Linux: meine Software und Konfigurationstipps
19.04.2005 (aktualisiert: 30.08.2006)
Es gibt viele gute – private wie geschäftliche – Gründe, seine Daten zu verschlüsseln. Dies ist unter Linux nicht schwer; SuSE und sicher auch andere Distributionen stellen komfortable Möglichkeiten der Nutzung verschlüsselter Laufwerke für die eigenen Daten zur Verfügung. Nur in seltenen Fällen sind von dieser Verschlüsselung auch die Laufwerke mit den Systemverzeichnissen (/usr, /var usw.) betroffen.
Sicherheit ist aber erst dann gegeben, wenn alle Laufwerke, auf denen sensible Daten landen können, verschlüsselt sind. Ein Risiko besteht hier in erster Linie durch Anwendungsprogramme (also solche mit Userrechten). Diese können nicht in Systemverzeichnisse schreiben (insbesondere an /var ist hier zu denken) – mit Ausnahme von /tmp. Gänzlich ungeplant und unkontrollierbar können Daten auf dem Swaplaufwerk landen. Dieses lässt sich allerdings durch entsprechende Änderung des fstab-Eintrags bequem bei jedem Booten mit einer Zufallspassphrase verschlüsseln:
/dev/hda3 swap swap loop=/dev/loop7,encryption=twofish 0 0
Auch hier gilt die unten erläuterte Problematik der Kernelmodule. Daher habe ich in der boot.local noch swapoff -a;swapon -a
stehen, damit die Verschlüsselung auch wirklich durchgeführt wird. Im Erfolgsfall gibt swapon
(bzw. das davon aufgerufenemkswap
die Meldung Swapbereich Version 1 wird angelegt, Größe 518180 KBytes
aus.
Ärgerlich ist, dass – im Gegensatz zu mount
ein konkretes loop device angegeben werden muss, das Stichwort loop
alleine reicht nicht. Außerdem greift die Verschlüsselung absurderweise nur bei swapon -a
, nicht aber bei etwa swapon /dev/hda3
.
Diese Möglichkeit, die für Swaplaufwerke schon besteht, habe ich auf /tmp erweitert.
Dieses Bootscript (Shellscript) sucht in fstab nach einem Eintrag mit Mountpunt /tmp und den Mountoptionen noauto, loop=/dev/loop<n> und encryption=<cipher>. Wenn so ein Eintrag gefunden wird, setzt das Script das angegebene loop device mit der angegebenen Verschlüsselungsart und einer Zufallspassphrase auf das entsprechende Volume, formatiert das loop device und mountet es anschließend auf /tmp.
Sinnvollerweise linkt man /var/tmp irgendwo in /tmp, da man ansonsten nur das halbe Problem gelöst hat.
Wenn man diese Möglichkeit nutzen will, aber kein Laufwerk mehr frei hat, kann man einen Trick anwenden. loop devices nehmen nicht nur block devices als Ziel, sondern sind auch mit Dateien zufrieden. Man kann also einfach eine entsprechend große Datei anlegen. Eine 200 MiB große Datei mit dem Namen tmploopdevfile legt man im Rootverzeichnis mit folgendem Kommando an:
dd if=/dev/zero of=/tmploopdevfile bs=1M count=200
In fstab wird dann einfach statt beispielsweise /dev/hda6 der Pfad /tmploopdevfile eingetragen.
Inzwischen weiß ich, dass es auch einfacher geht. In meiner man page für losetup
findet sich folgender Hinweis:
- -H phash
Hash function random does not ask for password but sets up random keys and attempts to put loop to multi-key mode. When random/1777 hash type is used as mount option for mount program, mount program will create new file system on the loop device and construct initial permissions of file system root directory from octal digits that follow the slash character.
WARNING! DO NOT USE RANDOM HASH TYPE ON PARTITION WITH EXISTING IMPORTANT DATA ON IT. RANDOM HASH TYPE WILL DESTROY YOUR DATA.
Nach der desaströsen Installation von SuSE 10.1 habe ich dies mal ausprobiert, und siehe da – es funktioniert. Man muss natürlich dafür sorgen, dass die benötigten Kernelmodule (in meinem Fall cryptoloop und twofish) rechtzeitig geladen werden. Das heißt, dass man sie entweder in die initrd packt oder aber /tmp nicht direkt über fstab mounten lässt. Ich habe das Problem für mich folgendermaßen gelöst. Mein fstab-Eintrag:
/dev/hda5 /tmp reiserfs noauto,defaults,loop,encryption=twofish256,phash=random/1777 0 0
Das wird kombiniert mit folgendem Eintrag in meiner /etc/init.d/boot.local:
awk '{if ($2=="/tmp") {mp="found";exit(0);}};END {if(mp=="found") exit(0); else exit (1);}' /proc/mounts || { echo "Mounting /tmp with randomly encrypted file system (via fstab, phash=random)" mount /tmp; } mkdir -p /tmp/.ICE-unix /tmp/var chown root:root /tmp/.ICE-unix chmod 1777 /tmp/.ICE-unix /tmp/var
Der awk
-Aufruf erfolgt nur der Ordnung halber, im Prinzip reicht mount /tmp
aus. Die Folgezeilen sind nur nötig, weil SuSE blöd ist und es nicht gebacken kriegt, diese Verzeichnisse richtig anzulegen, wenn sie fehlen. Wenn man sich dann als User einloggt, ist Ende. Das Verzeichnis lege ich an, weil mein /var/tmp darauf verlinkt.
Wenn die benötigten Kernelmodule in der initrd stünden, könnte man sich das noauto in der fstab und den boot.local-Eintrag sparen.
#! /bin/bash # # This is a boot script for SuSE Linux and it probably has to be # adapted to other distributions. Look for "# SuSE specific [...]" lines # for "rc_failed", "rc_status" and "rc_exit". The former can be deleted # (they set and show the status), rc_exit has to be replaced by exit. # # CHANGELOG # Version 1.2 # - Line length limited # - The volume given in fstab need not be a block device but can # be a regular file, too. # - Deleted unsupported arguments (like reload) # - mount -f only on success of mount # - suppress mount output # Version 1.1, 19.04.2005, Hauke Laging # - Bugfix: chmod auf /tmp (rwxrwxrwt) # Version 1.0.1, 23.02.2005, Hauke Laging # Version 1.0, 15.01.2005, Hauke Laging, http://www.hauke-laging.de/ # software@hauke-laging.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # ### BEGIN INIT INFO # Provides: boot.mount_tmp # Required-Start: boot.localfs # Should-Start: # Required-Stop: # Should-Stop: # Default-Start: B # Default-Stop: # Short-Description: Mount /tmp on randomly encrypted volume # Description: Look for a /tmp entry with loop and noauto options # in /etc/fstab, set up the loop device with an random # passphrase, create the file system specified in # /etc/fstab on the loop device and mount it to /tmp. # This makes sure that any information in /tmp is securely # lost after rebooting. This closes a kind of security gap for # users who encrypt their data volumes and the swap file. ### END INIT INFO # # Check for missing binaries (stale symlinks should not happen) # Note: Special treatment of stop for LSB conformance # debug for prog in awk losetup grep mount do type $prog &>/dev/null || { echo "'$prog' not installed"; if [ "$1" = "stop" ]; then exit 0; else exit 5; fi; } done mount_point="/tmp" shopt -s extglob # SuSE specific - BEGIN . /etc/rc.status # Reset status of this service rc_reset # SuSE specific - END case "$1" in start) echo -n "Mounting ${mount_point} on a randomly encrypted volume " mount_point_tmp="${mount_point//\//}" test 0 -lt ${#mount_point_tmp} && rm -rf "${mount_point}/"* # Check for fstab entry # - must be a device (not e.g. tmpfs) # - mount point must match exactly (^$) # - loop device must be given in mount options (must match /dev/loop[0-9]*) # - encryption must be given in mount options # check if mount point is already in use awk '$2 == "'"${mount_point}"'" {found=1};END{if(found==1)exit(0);else exit(1)}' /proc/mounts && { echo "Mount point '$mount_point' is already in use (according to /proc/mounts); aborting"; rc_failed;rc_exit; } # get and check the (volume) device device="$(awk '$1 ~ "^/dev/" && $2 ~ "^'"${mount_point}"'/?$" && $4 ~ "loop=/dev/loop" && $4 ~ '\ '"encryption=" {print NR "_" $1;matched++};END{if(matched==0) exit(1); else if(matched==1) '\ 'exit(0);else exit(2)}' /etc/fstab)";rc_awk=$? fstab_line="${device%%_*}" device="${device#*_}" test $rc_awk -eq 1 && { rc_status -s;rc_exit; } test $rc_awk -gt 1 && { echo "Several matches for '${mount_point}' in /etc/fstab; aborting."; rc_failed;rc_exit; } test -b "$device" || test -f "$device" || { echo "'$device' is neither a valid block device nor a regular file; aborting"; rc_failed;rc_exit; } # get and check the loop device loop_device="$(awk 'NR == '"${fstab_line}"' {pos=match($4,"(,|^)loop=/dev/loop[0-9]*(,|$)");'\ 'if(pos==0) exit(1); else {loop=substr($4,RSTART+5,RLENGTH-5);gsub(",","",loop);sub("^[^l]","",loop);'\ 'print loop}}' /etc/fstab)";rc_awk=$? test $rc_awk -ne 0 && { echo "loop device could not be found in /etc/fstab; aborting."; rc_failed;rc_exit; } test -b "$loop_device" || { echo "'$loop_device' is not a valid block device; aborting"; rc_failed;rc_exit; } losetup "$loop_device" &>/dev/null;rc_losetup=$? test "$rc_losetup" -eq 0 && { echo "'$loop_device' is already in use; aborting"; rc_failed;rc_exit; } test "$rc_losetup" -eq 2 && { echo "'$loop_device' is not a valid loop device; aborting"; rc_failed;rc_exit; } # get and check/prepare the encryption encryption="$(awk 'NR == '"${fstab_line}"' {pos=match($4,"(,|^)encryption=[^,]*(,|$)");'\ 'if(pos==0) exit(1); else {enc=substr($4,RSTART+11,RLENGTH-11);gsub(",","",enc);sub("^[^e]","",enc);print enc}}' \ /etc/fstab)";rc_awk=$? test $rc_awk -ne 0 && { echo "encryption type could not be found in /etc/fstab; aborting."; rc_failed;rc_exit; } # SuSE specific - BEGIN case "$encryption" in twofish*([0-9])) crypto_modules=(cryptoloop loop_fish2 twofish) for module in "${crypto_modules[@]}" do modprobe "$module" || { echo "modprobe '${module}' failed"; if [ "$1" = "stop" ]; then exit 0; else rc_failed;rc_exit; fi; } done ;; *) echo "${warn}Encryption type '${encryption}' is not supported." echo "Setting up the loop device may fail due to this.${norm}" ;; esac # SuSE specific - END # get and check the file system filesystem="$(awk 'NR == '"${fstab_line}"' {print $3}' /etc/fstab)";rc_awk=$? grep -q "$filesystem" /proc/filesystems || { echo "'$filesystem' is not currently supported by kernel (according to /proc/filesystems); aborting"; rc_failed;rc_exit; } case "$filesystem" in ext[23]) type mke2fs &>/dev/null || { echo "The file system builder for '${filesystem}' could not be found; aborting."; rc_failed;rc_exit; } ;; reiserfs) type mkreiserfs &>/dev/null || { echo "The file system builder for '${filesystem}' could not be found; aborting."; rc_failed;rc_exit; } ;; reiser4) type mkfs.reiser4 &>/dev/null || { echo "The file system builder for '${filesystem}' could not be found; aborting."; rc_failed;rc_exit; } ;; *) echo "The file system '${filesystem}' is not supported by this script; aborting." rc_failed rc_exit ;; esac # get and prepare the mount options # strip off options that are not useful patterns=(user users auto noauto loop="${loop_device}" encryption="${encryption}" loop) patterns_count=${#patterns[*]} awk_patterns= for((i=0;i<patterns_count;i++)) do if [ 0 -ne "$i" ] then awk_patterns="${awk_patterns};" fi awk_patterns="${awk_patterns}pat[$i]=\"${patterns[$i]}\"" done mopts="$(awk 'BEGIN{matches=0;'"${awk_patterns}"'};NR == 11 {FS=",";$0=$4;'\ 'for(i=1;i<=NF;i++)for(j=0;j<2;j++){if($i==pat[j]){result[matches]=$i;matches++;break}}};'\ 'END{for(i=0;i<matches;i++){if(i!=0)printf ",";printf result[i]};print ""}' /etc/fstab)";rc_awk=$? # setting up the loop device passphrase="${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}"\ "${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}" test -z "$passphrase" && { echo "passphrase could not be generated; aborting."; rc_failed;rc_exit; } echo "$passphrase" | losetup -p 0 -e "$encryption" -C 1 "$loop_device" "$device" &>/dev/null;rc_losetup=$? test 0 -ne "$rc_losetup" && { echo "losetup failed; aborting."; rc_failed;rc_exit; } # create the file system case "$filesystem" in ext2) mke2fs "$loop_device" &>/dev/null;rc_mkfs=$? ;; ext3) mke2fs -j "$loop_device" &>/dev/null;rc_mkfs=$? ;; reiserfs) mkreiserfs -q "$loop_device" &>/dev/null;rc_mkfs=$? ;; reiser4) mkfs.reiser4 --quiet "$loop_device" &>/dev/null;rc_mkfs=$? ;; *) echo "The file system '${filesystem}' is not supported by this script; aborting." rc_failed rc_exit ;; esac # mount the file system to /tmp but don't write to /etc/mtab as this # wouldn't work due to the option "loop" test -n "$mopts" && use_mopts="-o ${mopts}" || use_mopts= mount -t "$filesystem" -n $use_mopts "$loop_device" "$mount_point" &>/dev/null;rc_mount=$? if [ 0 -ne "$rc_mount" ] then echo "mount failed; aborting" losetup -d "$loop_device" rc_failed rc_exit else # create the /etc/mtab entry, show the real device and the loop device as mount option nec_mtab_mopts="loop=${loop_device},encryption=${encryption}" test -n "$mopts" && mtab_mopts="${mopts},${nec_mtab_mopts}" || mtab_mopts="${nec_mtab_mopts}" mount -t "$filesystem" -f -o "$mtab_mopts" "$device" "$mount_point" mkdir /tmp/.ICE-unix chmod -R 777 /tmp chmod -R o+t /tmp fi # Remember status and be verbose rc_status -v ;; stop) echo -n "Unmounting randomly encrypted '${mount_point}' " umount "${mount_point}" # Remember status and be verbose rc_status -v ;; restart) ## Stop the service and regardless of whether it was ## running or not, start it again. $0 stop $0 start # Remember status and be quiet rc_status ;; status) echo -n "Checking for encrypted volume for ${mount_point} " encryption_active=yes while true # use a pseudo loop so that we can leave it from everywhere like a function do # check if mount point is already in use awk '$2 == "'"${mount_point}"'" {found=1};END{if(found==1)exit(0);else exit(1)}' /proc/mounts || { encryption_active=no;break; } device="$(awk '$1 ~ "^/dev/" && $2 ~ "^'"${mount_point}"'/?$" {print $1;matched++};'\ 'END{if(matched==0) exit(1); else if(matched==1) exit(0);else exit(2)}' /proc/mounts)" || { encryption_active=no;break; } losetup "$device" &>/dev/null || { encryption_active=no;break; } losetup -a | grep -qE "^${device}.*encryption" || { encryption_active=no;break; } break done # Return value is slightly different for the status command: # 0 - service up and running # 3 - service not running (unused) test yes = "$encryption_active" || bash -c "exit 3" # NOTE: rc_status knows that we called this init script with # "status" option and adapts its messages accordingly. rc_status -v ;; *) echo "Usage: $0 {start|stop|status}" exit 1 ;; esac rc_exit