Помните, был такой фильм: хакеры добыли некую секретную информацию, которую можно было прочитать только на определенном компьютере, находящемся в особо охраняемом помещении, потому что на других компьютерах ее нельзя было расшифровать...
На самом деле это довольно несложно делается, попробую показать на примере.
(«промышленные», сертифицированные и прочие решения не рассматриваются потому что потому)
Итак, условие номер один: информация из некой секретной папки не должна быть доступна посторонним.
Очевидное решение — шифрование. При этом также очевидно, что в зашифрованном виде ее могут украсть, так или иначе, например вместе с компьютером.
Но у шифрования есть минус: вы знаете пароль, и если вас настоятельно попросить — вы его скажете. Значит, это и будет условие номер два: вы не можете рассказать пароль. Захотите — но не сможете.
Однако, не зная пароля — вы должны получать при этом доступ к своим файлам.
Это означает, что открываться защищенная папка должна сама, но только при «штатной» работе, и не открываться, если что‑то пошло не так.
Это условие номер три — работает автоматически.
Можно привязать пароль к каким‑то аппаратным свойствам компьютера — но если выкрасть весь компьютер — будет выкраден и ключ к папке. Значит, ключ должен находиться вне компьютера, но в доступе к нему.
Но что, если злоумышленники украли не только один компьютер, но и весь офис? А вам не дают ничего делать.
Тогда ключ должен быть таким, чтобы третье лицо могло в любой момент уничтожить ключ, возможно даже не зная что именно делает, «не вызывая подозрения санитаров».
Это — четвертое условие: ключ физически не привязан к компьютеру и не должен выглядеть как ключ для постороннего наблюдателя, зато должен быть легко уничтожим.
Вот это сейчас и реализуем:
В качестве средства шифрования используем 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 <disk_image> or <secret> 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 <<EOF Configurations: /etc/paranoid/users/<username>.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 <username> Create new or format existing cryptodisk with SECRET for user <username> $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" или подобное).
Если он в наличии - будет молча примонтирован, если нет - то нет.
