Как загрузить кодовую фразу LUKS с USB, возвращаясь к клавиатуре?

Я хочу настроить безголовый Linux (Debian Wheezy) с полным шифрованием диска, с возможностью разблокировки диска либо с помощью USB-накопителя, либо путем ввода ключевой фразы с клавиатуры. Моя начальная точка - это новая установка, использующая основную опцию полного шифрования диска в установщике Debian, которая управляет всем, кроме /boot, как группа логических томов, зашифрованная LUKS, и дает мне вариант с клавиатурой. Я опишу свое текущее решение в ответе, надеясь, что оно будет полезно и что другие могут улучшить его.

Вот некоторые из моих проблем:

  • Настройка парольной фразы и установка ее на USB-накопитель.

  • Загрузка модулей USB вовремя.

  • Ожидание того, что USB-накопитель распознается Linux, прежде чем пытаться его прочитать.

  • Идентификация правильного USB-накопителя (не какой-либо другой диск, который может быть вставлен).

  • Написание "ключей" для выведения парольной фразы с USB-накопителя.

  • Обеспечение того, чтобы спад на клавиатуре срабатывал во всех случаях сбоя USB.

Я соглашусь на ответ со значительными улучшениями и ответами, предлагающими вклады.

Ответ 1

Много моего решения получено из сообщения Использование USB-ключа для парольной фразы LUKS.

  • Создайте произвольную кодовую фразу:

    dd if=/dev/urandom bs=1 count=256 > passphrase
    
  • Вставьте USB-накопитель. dmesg выводится имя устройства; предположим /dev/sdd. Выясните его размер:

    blockdev --getsize64 /dev/sdd
    
  • Я решил установить кодовую фразу в конце необработанного устройства, полагая, что он может выжить при любом случайном использовании USB-накопителя.

    dd if=passphrase of=/dev/sdd bs=1 seek=<size-256>
    
  • Добавьте кодовую фразу в объем LUKS:

    cryptsetup luksAddKey /dev/sda5 passphrase
    

    Это не влияет на существующую введенную кодовую фразу от установщика. Файл парольной фразы можно удалить:

    rm passphrase
    
  • Найдите уникальное имя для USB-накопителя, поэтому мы можем идентифицировать его при наличии:

    ls -l /dev/disk/by-id | grep -w sdd
    

    Вы должны увидеть одну символическую ссылку. Я назову его /dev/disk/by-id/<ID>.

  • Изменить /etc/crypttab. Вы должны увидеть строку, например:

    sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 none luks
    

    Измените его на:

    sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-id/<ID> luks,keyscript=/bin/passphrase-from-usb
    
  • keyscript, о котором говорилось выше, необходимо будет прочитать парольную фразу с USB-устройства. Однако для этого нужно сделать больше. Чтобы понять, как он используется, отметьте /usr/share/initramfs-tools/scripts/local-top/cryptroot, script, который выполняется во время загрузки, чтобы разблокировать корневое устройство. Обратите внимание, когда установлен параметр keyscript, он просто запускается, а выход отправляется на luksOpen без какой-либо другой проверки. Невозможно подать сигнал об ошибке (USB-накопитель отсутствует) или вернуться на ввод с клавиатуры. Если кодовая фраза терпит неудачу, ключ-скрипт запускается снова в цикле, вплоть до некоторого количества раз; однако нам не говорят, на какую итерацию мы находимся. Кроме того, мы не контролируем, когда запускается keycript, поэтому мы не можем быть уверены, что Linux распознал USB-накопитель.

    Я обратился к этому с некоторыми хаками:

    • Опросите USB-накопитель и подождите 3 секунды, чтобы он появился. Это работает для меня, но я хотел бы узнать лучший способ.

    • Создайте фиктивный файл /passphrase-from-usb-tried при первом запуске, чтобы указать, что мы были запущены хотя бы один раз.

    • Если мы запускаем хотя бы один раз или USB-накопитель не может быть найден, запустите программу askpass, используемую cryptroot для ввода клавиатуры.

    Окончательный script:

    #!/bin/sh
    
    set -e
    
    if ! [ -e /passphrase-from-usb-tried ]; then
        touch /passphrase-from-usb-tried
        if ! [ -e "$CRYPTTAB_KEY" ]; then
            echo "Waiting for USB stick to be recognized..." >&2
            sleep 3
        fi
        if [ -e "$CRYPTTAB_KEY" ]; then
            echo "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) from USB key" >&2
            dd if="$CRYPTTAB_KEY" bs=1 skip=129498880 count=256 2>/dev/null
            exit
        else
            echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
        fi
    fi
    
    /lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME)\nEnter passphrase: "
    

    Наконец, нам нужно убедиться, что этот script доступен в initramfs. Создайте /etc/initramfs-tools/hooks/passphrase-from-usb, содержащий:

    #!/bin/sh
    
    PREREQ=""
    
    prereqs() {
            echo "$PREREQ"
    }
    
    case "$1" in
            prereqs)
                    prereqs
                    exit 0
            ;;
    esac
    
    . "${CONFDIR}/initramfs.conf"
    . /usr/share/initramfs-tools/hook-functions
    
    copy_exec /bin/passphrase-from-usb /bin
    
  • Драйверы USB не присутствовали в моих initramfs. (Похоже, что они по умолчанию в более поздних версиях Debian.) Мне пришлось добавить их, добавив в /etc/initramfs-tools/modules:

    uhci_hcd
    ehci_hcd
    usb_storage
    
  • Когда все будет сделано, обновите initramfs:

    update-initramfs -u
    

Ответ 2

Это было бы идеально для меня, если бы я мог просто иметь небольшой USB-накопитель, содержащий кодовую фразу, которая будет разблокируйте диск. Мало того, что это будет удобно для серверов (где вы можете оставить USB-накопитель в сервер - цель состоит в том, чтобы иметь возможность возвращать сломанные жесткие диски, не беспокоясь о конфиденциальных данных), это также отлично подходит для моего ноутбука: вставьте USB-накопитель при загрузке и удалите его после разблокировка криптодиска.

Теперь я написал патч, который будет искать корневой каталог всех устройств для файла cryptkey.txt и попробовать дешифровать с каждой строкой в ​​качестве ключа. Если это не удастся: верните ввод текста фразы.

Это означает, что ключ не может содержать \n, но это применимо и к любому набранному ключу. Хорошая часть заключается в том, что вы можете использовать один и тот же USB-диск для хранения ключа для нескольких компьютеров: вам не нужен отдельный USB-диск для каждого. Поэтому, если у вас есть USB-накопитель в вашем физическом ключевом кольце, вы можете использовать тот же диск для всех загружаемых вами машин при физическом закрытии.

Вы добавляете ключ с помощью:

cryptsetup luksAddKey /dev/sda5

И затем введите тот же ключ, что и строка в файле на диске USB/MMC с именем "cryptkey.txt". Патч находится здесь:

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=864647

Если драйверы USB, драйверы MMC или файловые системы отсутствуют в ваших initramfs, вам необходимо добавить их, добавив в /etc/initramfs -tools/modules:

uhci_hcd
ehci_hcd
usb_storage
nls_utf8
nls_cp437
vfat
fat
sd_mod
mmc_block
tifm_sd
tifm_core
mmc_core
tifm_7xx1
sdhci
sdhci_pci

Когда все будет сделано, обновите initramfs:

update-initramfs -u

Ответ 3

Чтобы сопровождать отличные ответы выше, см. C-процедуры, которые вы могли бы использовать для записи/генерации и чтения необработанного ключа блока устройств. "Readkey.c" извлекает ключ заданного размера из блочного устройства, а "writekey.c" может генерировать или записывать существующий ключ на необработанное устройство. Скомпилированный "readkey.c" может использоваться в пользовательском script для извлечения ключа известного размера из необработанного блочного устройства следующим образом:

readkey </path/to/device> <keysize>

Чтобы увидеть использование для "writekey", после компиляции запустите его без флагов.
Для компиляции просто используйте:

gcc readkey.c -o readkey

gcc writekey.c -o writekey

Я тестировал как на USB-накопителе Verbatim 16GB USB 2.0 с пользовательским "keyscript =" в crypttab, также опубликованном ниже. Идея "crypto-usb.sh" взята из руководства "cryptsetup" debian etch.

crypto-usb.sh:

#!/bin/sh
echo ">>> Trying to get the key from agreed space <<<" >&2
modprobe usb-storage >/dev/null 2>&1
sleep 4
OPENED=0
disk="/sys/block/sdb"
boot_dir="/boot"
readkey="/boot/key/readkey"
echo ">>> Trying device: $disk <<<" >&2
F=$disk/dev
if [ 0`cat $disk/removable` -eq 1 -a -f $F ]; then
    mkdir -p $boot_dir
    mount /dev/sda1 $boot_dir -t ext2 >&2
    echo ">>> Attempting key extraction <<<" >&2
    if [ -f $readkey ]; then
        # prints key array to the caller
        $readkey /dev/sdb 4096
        OPENED=1
    fi
    umount $boot_dir >&2
fi


if [ $OPENED -eq 0 ]; then
    echo "!!! FAILED to find suitable key !!!" >&2
    echo -n ">>> Try to enter your password: " >&2
    read -s -r A
    echo -n "$A"
else
    echo ">>> Success loading key <<<" >&2
fi

При генерации ключа размер ключа должен быть предоставлен, сгенерированный ключ сохраняется в файле .tmpckey с разрешениями на использование файлов 0600 для последующего использования. При записи существующего ключа размер определяется путем измерения существующего размера ключа. Это похоже на сложный подход, однако, скомпилированный с простым "gcc", он может обеспечить простой способ управления содержимым исходного ключа.

readkey.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(int argc, char *argv[])
{
    int blockSize = 512;
    int keySize = 2048; 

    FILE *device;       

    if (  argc == 3 
           && (sizeof(argv[1]) / sizeof(char)) > 1
           && (sizeof(argv[2]) / sizeof(char)) > 1
       && (atoi(argv[2]) % 512) == 0
       ) {
        device = fopen(argv[1], "r");
        if(device == NULL) { 
            printf("\nI got trouble opening the device %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }
        keySize = atoi(argv[2]);        
    }
    else if (  argc == 2 
            && (sizeof(argv[1]) / sizeof(char)) > 1
        ) {
        device = fopen(argv[1], "r");
        if(device == NULL) { 
            printf("\nI got trouble opening the device %s\n", argv[1]);
            exit(EXIT_FAILURE);
        }

    }
    else {

        printf("\nUsage: \n");
        printf("\nKey Size Provided: \n");
        printf("\n\t\treadkey </path/to/device> <keysize> \n");
        printf("\nDefault key size: %d\n", keySize);
        printf("\n\t\treadkey </path/to/device>\n");
        exit(1);
    }

    int count;

    char *block;

    /* Verify if key is multiple of blocks */
    int numBlocks = 0;
    if (keySize % 512 != 0) {
       printf("\nSory but key size is not multiple of block size, try again. TA.\n");
       exit(1);
    }

    /* Seek till the end to get disk size and position to start */
    fseek(device, 0, SEEK_END);

    /* Determine where is the end */
    long endOfDisk = ftell(device);

    /* Make sure we start again */
    rewind(device); // Do I need it ???

    /* Get the required amount minus block size */
    long startFrom = endOfDisk - blockSize - keySize;

    /* Allocate space for bloc */
    block = calloc(keySize, sizeof(char));

    /* Start reading from specified block */
    fseek(device, startFrom, SEEK_SET);
    fread(block, 1, keySize, device);

    /* Do something with the data */
    for(count = 0; count < keySize/*sizeof(block)*/; count++){
        printf("%c", block[count]);
    }

    /* Close file */
    fclose(device);

    /* Make sure freed array is zeroed */
    memset(block, 0, keySize);
    free(block);
}

writekey.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int blockSize = 512;
    int keySize = 2048;

    int count;

    unsigned char *block;

    /*
        Thing to always remember that argv starts from 0 - the name of the program, and argc starts from 1 i.e. 1 is the name of the program.
    */
    if ( argc == 3 
       && strcmp(argv[1], "genwrite") != 0
       && (sizeof(argv[2]) / sizeof(char)) > 2
       ) {
        char ch;
        FILE *keyF;
        keyF = fopen(argv[1], "r");
        if (keyF == NULL) exit(EXIT_FAILURE);

        /* Tell key Size */
        fseek(keyF, 0, SEEK_END);
        keySize = ftell(keyF);
        rewind(keyF);
        printf("\nKey Size: %d\n", keySize);

        block = calloc(keySize, sizeof(char));
        printf("\n-- Start Key --:\n");
                for(count = 0; count < keySize/*sizeof(block)*/; count++){
            char ch = fgetc(keyF);
                        block[count] = ch;
            /*
              Uncomment below to see your key on screen
            */
            // printf("%c",ch);

                }
        printf("\n-- End Key --:\n");
        fclose(keyF);
    }
    else if (  argc == 3 
        && strcmp(argv[1], "genwrite") == 0 
        && (sizeof(argv[2]) / sizeof(char)) > 2
        ) 
        {
        printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
        block = calloc(keySize, sizeof(char));
        int count;
        for(count = 0; count < keySize/*sizeof(block)*/; count++){
            block[count] = (char) rand();
        }
        FILE *tmpfile;
        tmpfile = fopen(".tmpckey", "w");
        if(tmpfile == NULL) exit(EXIT_FAILURE);
        fwrite(block, 1, keySize, tmpfile);
        fclose(tmpfile);
        chmod(".tmpckey", 0600);
    }
    else if (  argc == 4 
        && strcmp(argv[1], "genwrite") == 0
        && (sizeof(argv[2]) / sizeof(char)) > 2
        && ((atoi(argv[3]) % 512) == 0)
        ) 
        {
        keySize = atoi(argv[3]);
        printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
        block = calloc(keySize, sizeof(char));
        int count;
        for(count = 0; count < keySize/*sizeof(block)*/; count++){
            block[count] = (char) rand();
        }
        FILE *tmpfile;
        tmpfile = fopen(".tmpckey", "w");
        if(tmpfile == NULL) exit(EXIT_FAILURE);
        fwrite(block, 1, keySize, tmpfile);
        fclose(tmpfile);
        chmod(".tmpckey", 0600);
    }   
    else {
        printf("\n");
        printf("################################################################################\n");
        printf("#                                                                              #\n");
        printf("#                              Usage:                                          #\n");
        printf("#                                                                              #\n");
        printf("################################################################################\n");
        printf("#> To write existing key to device:                                            #\n");
        printf("#                                                                              #\n");
        printf("#     writekey </path/to/keyfile> </path/to/removable/sd*>                     #\n");
        printf("#                                                                              #\n");
        printf("#> To generate and write pseudo random key,                                    #\n");
        printf("#> key will be saved to temporary file .tmpckey                                #\n");
        printf("#                                                                              #\n");
        printf("#     writekey genwrite </path/to/removable/sd*> <keysize in multiples of 512> #\n");
        printf("#                                                                              #\n");
        printf("#> When keysize is not provided default size is set to %d.                     #\n", keySize);
        printf("#                                                                              #\n");
        printf("################################################################################\n");
        exit(1);
    }

    /*
        Some printf debugging below, uncomment when needed to see what is going on.
    */
    /*
    printf("\nNumber of Args: %d\n", argc);
    printf("\nCurrently block array contains: \n");
    for(count = 0; count < keySize; count++){
        printf("%c", block[count]);
    }
    printf("\n-- End block -- \n");
    */
    /* Open Device itp... */
    FILE *device = fopen(argv[2], "a");
    if(device == NULL) exit(EXIT_FAILURE);

    printf("\nDevice to write: %s\n", argv[2]);

    fseek(device, 0, SEEK_END);

    /* Determine where is the end */
    long endOfDisk = ftell(device);
    printf("\nDevice Size: %ld\n", endOfDisk);

    /* Verify if key is multiple of blocks */
    int numBlocks = 0;
    if (keySize % 512 != 0 || endOfDisk < (blockSize + keySize) ) {
            printf("\nSorry but key size is not multiple of block size or device you trying to write to is too small, try again. TA.\n");
        fclose(device);
            exit(1);
    }



    /* Make sure we start again */
    rewind(device);

    /* Get the required amount sunbstracting block size */
    long startFrom = endOfDisk - blockSize - keySize;

    /* Write some data to the disk */
    printf("\nWriting data starting from: %ld\n", startFrom);
    fseek(device, startFrom, SEEK_SET);
    fwrite(block, 1, keySize, device);
    printf("\nBlock Position after data write procedure : %ld\n", ftell(device));

    /*
        Below is just for convenience, to read what was written,
        can aid in debugging hence left commented for later.
    */
    /*
    printf("\nAmount of Data written : %ld\n", ftell(device) - startFrom);

    // Start reading from specified block 
    printf("\n>>>>>>>> DEBUGGING SECTION <<<<<<<<<\n");
    rewind(device); //
    fseek(device, startFrom, SEEK_SET);
    printf("\nBlock Position before read attempted: %d\n", ftell(device));
    printf("\nKey size: %d\n", keySize);
    fread(block, 1, keySize, device);

    // Do something with the data
    printf("\nBlock Position startFrom: %ld\n", startFrom);
    printf("\nBlock Position after read: %d\n", ftell(device));
    printf("\n-- Buffer Read: --\n");
    for(count = 0; count < keySize; count++){
        printf("%c", block[count]);
    }
    printf("\n-- End block -- \n");
    printf("\n--  -- \n");
    printf("\n--  -- \n");
    */

    /* Close file */
    fclose(device);

    /* Make sure freed array is zeroed */
    memset(block, 0, keySize);
    free(block);

/* Return success, might change it to be useful return not place holder */
return 0;
}

Чтобы проверить, что ключ, записанный в raw-устройство, совпадает с файлом в файле (ниже ничего не выводится, если ключи идентичны):

diff -B <(./readkey </path/to/device> 4096) <(cat .tmpckey)

Или для существующего ключа, сгенерированного с использованием собственных средств:

diff -B <(./readkey </path/to/device> <generated elsewhere key size>) <(cat </path/to/keyfile>)

Спасибо