Если у вас паранойя…
Помните, был такой фильм: хакеры добыли некую секретную информацию, которую можно было прочитать только на определенном компьютере, находящемся в особо охраняемом помещении, потому что на других компьютерах ее нельзя было расшифровать…
На самом деле это довольно несложно делается, попробую показать на примере.
(«промышленные», сертифицированные и прочие решения не рассматриваются потому что потому)
Итак, условие номер один: информация из некой секретной папки не должна быть доступна посторонним.
Очевидное решение — шифрование. При этом также очевидно, что в зашифрованном виде ее могут украсть, так или иначе, например вместе с компьютером.
Но у шифрования есть минус: вы знаете пароль, и если вас настоятельно попросить — вы его скажете. Значит, это и будет условие номер два: вы не можете рассказать пароль. Захотите -, но не сможете.
Однако, не зная пароля — вы должны получать при этом доступ к своим файлам.
Это означает, что открываться защищенная папка должна сама, но только при «штатной» работе, и не открываться, если что-то пошло не так.
Это условие номер три — работает автоматически.
Можно привязать пароль к каким-то аппаратным свойствам компьютера -, но если выкрасть весь компьютер — будет выкраден и ключ к папке. Значит, ключ должен находиться вне компьютера, но в доступе к нему.
Но что, если злоумышленники украли не только один компьютер, но и весь офис? А вам не дают ничего делать.
Тогда ключ должен быть таким, чтобы третье лицо могло в любой момент уничтожить ключ, возможно даже не зная что именно делает, «не вызывая подозрения санитаров».
Это — четвертое условие: ключ физически не привязан к компьютеру и не должен выглядеть как ключ для постороннего наблюдателя, зато должен быть легко уничтожим.
Вот это сейчас и реализуем:
В качестве средства шифрования используем LUKS. Это непринципиально, просто cryptsetup уже есть.
Чтобы не знать пароль — нужно использовать ключевой файл. LUKS работает с 512-битными ключами — это 64 байта данных, запомнить и рассказать вы не сможете.
Управлять подключением секретного диска можно через PAM на компьютере — всё будет происходить аввтоматически.
Получить ключ можно по сети, да вот хотя бы по http с какого-нибудь веб-сервера. Причем это может быть хоть корпоративный сервер, хоть удаленный сайт, хоть плата esp8266 в стене за гипсокартоном.
И чтобы ключ не выглядел как ключ для посторонних — в качестве ключа можно использовать самый обычный файл, хоть картинку, хоть текст. Взяв от него 512-битный хеш — и получим тот самый уникальный секретный ключ.
А удалить его очень просто: заменить картинку на сайте, пусть даже на похожую. Как именно это сделать — совсем другой вопрос, но он решаемый.
Для этого пишем такой скрипт (/usr/local/bin/paranoid):
#!/bin/bash
# на всякий случай подключаем /sbin
PATH=/sbin:$PATH
# установка общих переменных
setvars(){
HOME_DIR=$(getent passwd "$USER" | cut -d: -f6)
MOUNT_POINT="$HOME_DIR/edisk"
TMPFS_DIR="/tmp/tmpfs_key_$USER"
IMG_FILE="$TMPFS_DIR/image.bin"
KEY_FILE="$TMPFS_DIR/secret.key"
CRYPT_NAME="edisk_$USER"
}
# проверка наличия установленных программ
checks(){
which awk grep mount umount wget sha512sum xxd cryptsetup mkfs.ext4
if [ $? -ne 0 ] ; then
# если чего-то нет - завершаем работу, но "успешно", чтобы не вызывать ошибок
exit 0
fi
}
# читаем конфигурационный файд ля юзера
getconfig(){
CONFIG="/etc/paranoid/users/$USER.conf"
DATA_DIR=$(sudo awk -F= '/^data_dir/ {gsub(/^ +| +$/, "", $2); print $2}' $CONFIG)
DISK_IMAGE=$(sudo awk -F= '/^disk_image/ {gsub(/^ +| +$/, "", $2); print $2}' $CONFIG)
SECRET=$(sudo awk -F= '/^secret/ {gsub(/^ +| +$/, "", $2); print $2}' $CONFIG)
if [ "x$DISK_IMAGE" = "x" ] || [ "x$SECRET" = "x" ] || [ "x$DATA_DIR" = "x" ] ; then
echo "No or found!"
exit 0
fi
DISK_IMAGE="$DATA_DIR/$DISK_IMAGE"
}
# проверка на "открытость"
is_mapped() {
ls /dev/mapper | grep -q "$CRYPT_NAME"
}
# проверка на смонтированность
is_mounted() {
mount | grep -q "$MOUNT_POINT"
}
# самая главная часть - получение ключа
fetch_key() {
mkdir -p "$TMPFS_DIR"
if [ $? -ne 0 ] ; then
exit 0
fi
sudo chmod 700 "$TMPFS_DIR"
# монтируем временную ФС
sudo mount -t tmpfs -o size=1M tmpfs "$TMPFS_DIR"
# получаем ключ по указанному URL
# ничего не мешает получать по 2-3 ключа из разных источников и
# конкатенировать их в один файл
wget -q -O "$IMG_FILE" "$SECRET"
if [ $? -ne 0 ] || [ ! -f "$IMG_FILE" ] ; then
echo "No key file loaded!"
sudo umount "$TMPFS_DIR"
rmdir "$TMPFS_DIR"
exit 0
fi
# формируем 512-битный ключ
sha512sum "$IMG_FILE" | awk '{print $1}' | xxd -r -p > $KEY_FILE
# затираем и удаляем источник - в памяти ничего не остается
fsize=$(stat -c "%s" "$IMG_FILE")
dd if=/dev/urandom of="$IMG_FILE" bs=$fsize count=1 status=none
rm -f "$IMG_FILE"
chmod 600 "$KEY_FILE"
}
# очистка ключа - затираем и удаляем
cleanup_key() {
if [[ -f "$KEY_FILE" ]]; then
dd if=/dev/urandom of="$KEY_FILE" bs=64 count=1 status=none
rm -f "$KEY_FILE"
fi
sudo umount "$TMPFS_DIR"
rmdir "$TMPFS_DIR"
}
# процедура форматирования/создания диска
format_file(){
setvars;
getconfig;
sudo mkdir -p $DATA_DIR
# если диска нет - создаем
if [ ! -f "$DISK_IMAGE" ] ; then
echo -n "Enter disk size (MB): "
read size; size=$(( $size * 1 ));
if [ $size -gt 0 ] ; then
sudo dd if=/dev/urandom of="$DISK_IMAGE" bs=1M count=$size status=progress
else
echo "Size must be greater than 0!"
fi
fi
# если есть и не используется - меняем ключ
if [ -f "$DISK_IMAGE" ] ; then
if is_mapped; then
echo "Encripted disk already opened"
else
fetch_key
echo "Open encryped disk..."
sudo cryptsetup luksFormat "$DISK_IMAGE" "$KEY_FILE"
sudo cryptsetup luksOpen "$DISK_IMAGE" "$CRYPT_NAME" --key-file "$KEY_FILE"
sudo mkfs.ext4 "/dev/mapper/$CRYPT_NAME"
sudo cryptsetup luksClose "$CRYPT_NAME"
cleanup_key
fi
fi
}
# процедура монтирования
mount_disk() {
setvars;
getconfig;
if [ -f "$DISK_IMAGE" ] ; then
if is_mapped; then
echo "Encripted disk already opened"
else
fetch_key
echo "Open encryped disk..."
sudo cryptsetup luksOpen "$DISK_IMAGE" "$CRYPT_NAME" --key-file "$KEY_FILE"
cleanup_key
fi
if is_mapped; then
if is_mounted; then
echo "Already mounted $MOUNT_POINT."
else
echo "Mount..."
mkdir -p "$MOUNT_POINT"
if [ $? -eq 0 ] ; then
sudo mount /dev/mapper/"$CRYPT_NAME" "$MOUNT_POINT"
sudo chown $USER "$MOUNT_POINT"
sudo chmod 700 "$MOUNT_POINT"
fi
fi
else
echo "Incorrect key"
fi
fi
}
# процедура отключения диска
unmount_disk() {
setvars;
if [ ! $UMOUNT_FORCE ] ; then
# проверка на наличие рабочих сессий - для автоотключения
USER_SESSIONS=$(who | grep -w "$USER" | wc -l)
USER_PROCESSES=$(ps -u "$USER" --no-headers | wc -l)
fi
if [ "$USER_SESSIONS" -gt 1 ] || [ "$USER_PROCESSES" -gt 0 ]; then
echo "User $USER logged in ($USER_SESSIONS sessions, $USER_PROCESSES processes)"
else
echo "Last session $USER ended"
if is_mounted; then
echo "Unmount..."
sudo umount "$MOUNT_POINT"
rmdir "$MOUNT_POINT"
else
echo "Already unmounted."
fi
if is_mapped; then
echo "Close LUKS-volume..."
sudo cryptsetup luksClose "$CRYPT_NAME"
else
echo "LUKS-volume closed."
fi
fi
fi
}
usage(){
cat <.conf:
=============================
data_dir = /var/spool/paranoid
disk_image = data.dump
secret = http://url/secret_file.ext
=============================
Interactive mode:
$0 format
Create new or format existing cryptodisk with SECRET
$0 format
Create new or format existing cryptodisk with SECRET for user
$0 mount
Mount cryptodisk (if exists)
$0 unmount
Unmount cryptodisk (if mounted)
PAM mode:
/etc/pam.d/common-session:
session optional pam_exec.so $0
If used fscrypt - do it twice, before and after pam_fscrypt.so
EOF
}
#=============================
checks;
uid=$(id -u)
case "$1" in
mount)
mount_disk
;;
unmount)
UMOUNT_FORCE=1
unmount_disk
;;
format)
if [ "x$2" != "x" ] ; then
if [ $uid -eq 0 ] ; then
USER="$2"
echo "Run for user $USER"
else
echo "User change ignored"
exit 0
fi
fi
format_file;
;;
*)
case "$PAM_TYPE" in
open_session)
USER=$PAM_USER
mount_disk
;;
close_session)
USER=$PAM_USER
unmount_disk
;;
*)
usage
;;
esac
;;
esac
Теперь нужно создать конфиг для нашего юзера — вон как там написано:
/etc/paranoid/users/vasya.conf:
data_dir = /var/spool/paranoid
disk_image = data.dump
secret = http://image172.jpg
Если работаем под vasya:
paranoid format
Если под рутом:
paranoid format vasya
Указываем размер диска в мегабайтах, и если всё на своих местах — файл будет создан, зашифрован и отформатирован.
Подключаем — отключаем:
paranoid mount
paranoid unmount
Для автоматизации внесем записи в /etc/pam.d/common-session:
session optional pam_exec.so /usr/local/bin/paranoid
Если использовалось fscrypt для шифрования домашнего каталога — то дважды:
session optional pam_exec.so /usr/local/bin/paranoid
session optional pam_fscrypt.so
session optional pam_exec.so /usr/local/bin/paranoid
Смысл в том, что скрипты вызываются и при входе, и при выходе -, но при входе он сработает только после fscrypt, а при выходе его надо вызывать до fscrypt.
Повторный запуск скрипту не мешает.
Теперь при логине в систему в домашнем каталоге юзера появляется примонтированный секретный диск, который исчезает при выходе из системы.
Либо можно принудительно его закрыть командой.
Если по любой причине ключевой файл недоступен или изменился — диск останется зашифрованным.
Если при этом запустить paranoid format — то еще и перешифрованным под новый ключ, с потерей всего что там было ранее записано.
Более того, файл диска не обязан быть именно файлом на диске — это может быть вообще подключаемая флешка или ISCSI-диск (data_dir = »/dev», disk_image = «sdb1» или подобное).
Если он в наличии — будет молча примонтирован, если нет — то нет.