Безопасная эксплуатация ноутбуков, или Защита пользовательского ключа с помощью алгоритмов ГОСТ Р 34.10-2012

В третьей части мы настроили защиту мастер-ключа с помощью USB-токена, используя RSA, но теперь мы перейдем на алгоритмы ГОСТ Р 34.10-2012. Жаркие. Зимние. Твои. А еще они основаны на более перспективных эллиптических кривых, которым не нужны такие большие ключи, чтобы обеспечить более высокий уровень безопасности.


Схема работы с USB-токенами и необходимые компоненты

Государственный стандарт Р 34.10-2012 предусматривает работу с открытыми и закрытыми ключами, но поддерживает только создание и проверку цифровых подписей без возможности шифровать данные на асимметричных ключах. Вместе с тем, для безопасного обмена сообщениями между клиентом и сервером на ключах ГОСТ Р 34.10-2012 можно выработать общий ключ симметричного шифрования по схеме, которая отражена на рисунке 10.

Рисунок 10 — Схема согласования общего ключа симметричного шифрования на ключах ГОСТ Р 34.10-2012
Рисунок 10 — Схема согласования общего ключа симметричного шифрования на ключах ГОСТ Р 34.10-2012

Механизм выработки общего ключа работает на основе алгоритма Диффи-Хеллмана (в PKCS#11 константа, обозначающая этот механизм, называется CKM_GOSTR3410_2012_DERIVE), схема работы показана на рисунке 11. Эту же схему можно применить и для формирования ключа, с помощью которого шифровать мастер-ключ LUKS.

Рисунок 11— Формирование ключа для симметричного шифрования
Рисунок 11— Формирование ключа для симметричного шифрования

Далее общий ключ симметричного шифрования, полученный на основе ключей ГОСТ Р 34.10-2012, можно использовать для шифрования пользовательского ключа LUKS, как мы это делали в случае RSA (способ 1), или сразу в качестве пользовательского ключа LUKS для шифрования непосредственно мастер-ключа зашифрованного диска (способ 2).

Рисунок 12 — Способы использования общего ключа симметричного шифрования
Рисунок 12 ��� Способы использования общего ключа симметричного шифрования

Как известно, нет ничего хуже, чем изобретать велосипед, тем более, когда этот велосипед должен ездить на принципах криптографии. Коллеги из Selectel в прошлом году довольно основательно проехались по этой теме в своей статье Зашифруй или проиграешь. Мы тоже придерживаемся такого мнения, поэтому внимательно ознакомились с кодом упомянутой ранее службы systemd-cryptenroll и предлагаем использовать общий ключ симметричного шифрования непосредственно как пользовательский ключ LUKS, что существенно упростит схему работы (способ 2).

Для работы с отечественной криптографией из Linux можно обращаться к модулю librtpkcs11ecp.so напрямую (на Python это можно делать, например, с использованием ctypes) или вызывать утилиту openssl с использованием модулей расширения (интеграции) rtengine. С помощью модулей интеграции через стандартный интерфейс OpenSSL можно решать следующие задачи:

  • электронная подпись по ГОСТ Р 34-10.2001, ГОСТ Р 34-10.2012, RSA, ECDSA;

  • вычисление хеш-функции по ГОСТ Р 34-11.94, ГОСТ Р 34-11.2012;

  • симметричное шифрование и вычисление имитовставки по ГОСТ 28147-89;

  • распределение ключей по схеме VKO 34.10-2001 и VKO 34.10-2012;

  • электронная подпись и шифрование PKCS#7, CMS, S/MIME;

  • формирование запросов на сертификаты PKCS#10;

  • работа с сертификатами X.509 и списками отзыва(CRL);

  • клиентская часть протокола TLS-ГОСТ;

  • онлайн-проверка статуса сертификата (OCSP);

  • работа с временными метками (TSP).

Установка и настройка пакетов

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

sudo apt install --yes pcscd opensc jq

wget https://download.rutoken.ru/Rutoken/PKCS11Lib/2.18.1.0/Linux/x64/librtpkcs11ecp\_2.18.1.0-1\_amd64.deb
md5sum librtpkcs11ecp_2.18.1.0-1_amd64.deb | grep 9482c068baca79b3070c1f1f8d7605d3
if [ $? -eq 0 ]; then
    sudo dpkg -i librtpkcs11ecp_2.18.1.0-1_amd64.deb
fi

wget https://download.rutoken.ru/Rutoken/Utilites/rtAdmin/3.1/Linux/glibc-x86_64/rtadmin
md5sum rtadmin | grep 677712b1fe211b74ff3391b927114f73
if [ $? -eq 0 ]; then
    chmod +x rtadmin
    sudo cp rtadmin /usr/bin/
fi

Как мы уже говорили, библиотека openssl не поддерживает работу с отечественными алгоритмами, но такую поддержку можно добавить с помощью внешнего модуля rtengine от компании «Актив». Скачаем этот модуль с официального сайта с помощью утилиты wget:

wget https://download.rutoken.ru/Rutoken/Support_OpenSSL/Current/rtengine-1.6.2.zip

Проверим контрольную сумму и распакуем файлы в папку /usr/lib

md5sum rtengine-1.6.2.zip | grep 50624947e25e91c4dc9dd983fb1a46b6
if [ $? -eq 0 ]; then
    sudo unzip rtengine-1.6.2.zip -d /usr/lib/
fi

В конфигурационном файле /usr/lib/ssl/openssl.cnf потребуется установить значение параметра «openssl_conf = openssl_def» и добавить в конец строки для настройки модуля rtengine:

Файл /usr/lib/ssl/openssl.cnf

#openssl_conf = default_conf
openssl_conf = openssl_def
...

[openssl_def]
engines = engine_section

[engine_section]
rtengine = gost_section

[gost_section]
dynamic_path = /usr/lib/rtengine/1.6.2/Linux/x64/lib/librtengine.so
pkcs11_path = /usr/lib/librtpkcs11ecp.so
rand_token = pkcs11:manufacturer=Aktiv%20Co.;model=Rutoken%20ECP
default_algorithms = CIPHERS, DIGEST, PKEY
enable_rand = yes

Создание ключевой пары на USB-токене

Чтобы не вводить идентификатор системного диска вручную, запишем его значение в переменную UUID. Нам так же потребуется это значение без дефисов UUID_DIGITS и в формате строки байтов UUID_BYTES.

UUID=$(sudo blkid --match-token TYPE=crypto_LUKS --output value -s UUID)
UUID_DIGITS=$(echo $UUID | sed 's/-//g')
UUID_BYTES=$(echo -n $UUID_DIGITS | sed 's/-//g' | fold -w 2 | sed 's/^/%/g' | tr -d '\r\n')
echo $UUID vs $UUID_DIGITS vs $UUID_BYTES
Результат выполнения команд...
localadmin@pc:~$ UUID=$(sudo blkid --match-token TYPE=crypto_LUKS --output value -s UUID)
localadmin@pc:~$ UUID_DIGITS=$(echo $UUID | sed 's/-//g')
localadmin@pc:~$ UUID_BYTES=$(echo -n $UUID_DIGITS | sed 's/-//g' | fold -w 2 | sed 's/^/%/g' | tr -d '\r\n')
localadmin@pc:~$ echo $UUID vs $UUID_DIGITS vs $UUID_BYTES
453fa939-a585-4e1f-a030-b11991a78d3f vs 453fa939a5854e1fa030b11991a78d3f vs %45%3f%a9%39%a5%85%4e%1f%a0%30%b1%19%91%a7%8d%3

Перед началом использования устройства инициализируем токен:

rtadmin format --no-log --repair --label "Rutoken" \
  --new-so-pin 16613403 --new-user-pin 20439756 \
  --max-so-pin-retry-count 5 --max-user-pin-retry-count 10 \
  --min-so-pin 8 --min-user-pin 8 \
  --pin-change-policy user

Ключевую пару ГОСТ Р 34.10-2012 на токене создадим следующей командой:

pkcs11-tool --login --pin 20439756 --keypairgen \
  --key-type GOSTR3410-2012-256:B --usage-derive \
  --id $UUID_DIGITS \
  --module /usr/lib/librtpkcs11ecp.so

, где:

  • --key-type — устанавливает тип эллиптической кривой GOSTR3410-2012-256:B;

  • --usage-derive — определяет, что на закрытый ключ должен быть установлен флаг derive, который позволяет использовать его для формирования общего ключа симметричного шифрования.

Для ГОСТ Р 34.10-2012 доступно два поколения ключей:

  • GOSTR3410:{A, B, C} или более полно GOSTR3410-2001:{A, B, C} — стандарт от 2001 года с закрытыми ключами длиной 256 бит. Наборы параметров A, B и C обладают сопоставимой криптостойкостью и дают только вариативность. Стандарт считается устаревшим, не разрешен для применения и заменен на новый;

  • GOSTR3410-2012-256:{A, B, C, D} и GOSTR3410-2012-512:{A, B, C} — стандарт от 2012 года с закрытыми ключами длиной 256 и 512 бит соответственно. При обновлении стандарта был изменен алгоритм хеширования и добавлены 512-битных ключи, что привело также к добавлению новых эллиптических кривых.

    Ключи длиной 512 бит пока еще считаются избыточными, а из 256-битных ключей наибольшую популярность получила эллиптическая кривая GOSTR3410-2012-256:B, которой соответствует набор параметров id-GostR3410-2001-CryptoPro-A-ParamSet, поэтому мы рекомендуем использовать именно эту кривую. Обратим еще раз внимание, что набору параметров A соответствует эллиптическая кривая B, а о том, как это получилось, вы можете узнать из статьи «История возникновения многообразия».

Для подтверждения того, что ключевая пара была успешно сгенерирована на USB-токене, можем найти ее в списке объектов:

pkcs11-tool --login --pin 20439756 --list-objects \
  --module /usr/lib/librtpkcs11ecp.so
Результат выполнения команды...
localadmin@pc:~$ pkcs11-tool --login --pin 20439756 --list-objects \
>   --module /usr/lib/librtpkcs11ecp.so
Using slot 0 with a present token (0x0)
Private Key Object; GOSTR3410-2012-256
  PARAMS OID: 06072a850302022301
  label:      
  ID:         453fa939a5854e1fa030b11991a78d3f
  Usage:      sign, derive
  Access:     sensitive, always sensitive, never extractable, local
  Allowed mechanisms: GOSTR3410,GOSTR3410-WITH-GOSTR3411-12-256,GOSTR3410-DERIVE,GOSTR3410-12-DERIVE

Извлечь из USB-токена можно только открытый ключ, для этого можно воспользоваться утилитой openssl:

openssl pkey -in "pkcs11:id=$UUID_BYTES?pin-value=20439756" -inform engine -engine rtengine \
  -pubout -out pair1_gostpub.pem

, где:

  • pkey — команда для работы с ключами, см. man openssl-pkey;

  • -inform — определяет формат входящего файла. Обычно используют форматы DER или PEM, но в приведенном примере указано значение engine, которое определяет, что будет выполнено обращение к внешнему модулю;

  • -engine — определяет идентификатор внешнего модуля, который будет использован для всех доступных алгоритмов. В приведенном примере мы используем модуль rtengine, который настроили ранее в файле /usr/lib/ssl/openssl.cnf;

  • -in — указывает имя входящего файла. В приведенном примере в качестве имени используется строка URI, которая определяет адрес объекта на USB-токене, см. https://datatracker.ietf.org/doc/html/rfc7512. В строке URI используются два параметра:

    • id — определяет идентификатор объекта на токене (ключа);

    • pin-value — позволяет передать PIN-код, которым защищен объект на токене;

  • -pubout — определяет, что следует вывести открытый ключ. Без этого параметра утилита попытается по умолчанию извлечь и вывести закрытый ключ, что закончится ошибкой;

  • -out — определяет имя выходного файла для записи ключа.

При желании параметры открытого ключа можно посмотреть с помощью ключа -text_pub:

openssl pkey -in "pkcs11:id=$UUID_BYTES?pin-value=20439756" \
  -inform engine -engine rtengine \
  -text_pub -noout
Результат выполнения команды
localadmin@pc:~$ openssl pkey -in "pkcs11:id=$UUID_BYTES?pin-value=20439756" \
> -inform engine -engine rtengine \
> -text_pub -noout

engine "rtengine" set.
Public key:
   X:26820B44A03D1FA514755C87CF52EEFB26AB425FEC77D1DBEAF627824078466E
   Y:9B2CECF1648AFA787F4F45493576FFA19C6743998E89D5DC8E45D4FB3CD05A9E
Parameter set: id-GostR3410-2001-CryptoPro-A-ParamSet

Если потребуется удалить ключевую пару из токена, то закрытую и открытую части нужно будет удалять по отдельности с помощью команды --delete-object.

Пример команд для удаления ключей...
pkcs11-tool --login --pin 20439756 --delete-object \
  --type=privkey --id $UUID_DIGITS \
  --module /usr/lib/librtpkcs11ecp.so

pkcs11-tool --login --pin 20439756 --delete-object \
  --type=pubkey --id $UUID_DIGITS \
  --module /usr/lib/librtpkcs11ecp.so

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

openssl genpkey -out pair2_gostpriv.pem -algorithm gost2012_256 \
  -pkeyopt paramset:id-GostR3410-2001-CryptoPro-A-ParamSet 

, где:

  • genpkey — команда для генерации закрытого ключа;

  • -out — имя выходного файла;

  • -algorithm — алгоритм для формирования ключа, используется gost2012_256;

  • -pkeyopt — позволяет задать дополнительные параметры открытого ключа в формате параметр:значение.

    Для возможности согласования общего ключа симметричного шифрования нам требуются ключевые пары с одинаковыми параметрами эллиптической кривой. Но если при генерации ключевой пары с помощью утилиты pkcs11-tool мы указывали тип эллиптической кривой GOSTR3410-2012-256:B, то при генерации ключевой пары утилитой openssl нам нужно указать набор параметров id-GostR3410-2001-CryptoPro-A-ParamSet, который соответствует этой кривой. О том, почему кривой B соответствует набор параметров A, как мы уже говорили ранее, вы можете узнать из статьи «История возникновения многообразия».

Вычислим открытый ключ для сгенерированного закрытого ключа:

openssl pkey -in pair2_gostpriv.pem -pubout -out pair2_gostpub.pem

Общий ключ симметричного шифрования с использованием закрытого ключа на токене можно рассчитать следующей командой:

openssl pkeyutl --derive \
  -keyform engine -engine rtengine \
  -inkey "pkcs11:id=$UUID_BYTES?pin-value=20439756" \
  -peerkey pair2_gostpub.pem \
  -pkeyopt ukmhex:DeadBeefDeadBeef \
  -out symmetric-key.der

, где:

  • openssl-pkeyutl — команда для выполнения низкоуровневых операций с ключами;

  • --derive — указывает, что нужно рассчитать общий ключ симметричного шифрования;

  • -keyform — определяет формат ключа, допускает значения PEM, DER или ENGINE;

  • -engine — определяет идентификатор внешнего модуля, который будет использован для всех доступных алгоритмов;

  • -inkey — определяет имя файла с закрытым ключом в формате URI-адреса на USB-токене;

  • -peerkey — определяет имя файла с открытым ключом;

  • -pkeyopt — позволяет задать дополнительные параметры открытого ключа в формате параметр:значение.

    Параметр ukmhex определяет дополнительный материал ключа пользователя (англ. User Keying Material, UKM) в шестнадцатеричном формате (англ. Hexadecimal, HEX), который будет использован для формирования общего ключа симметричного шифрования. Строка должна быть длиной не менее 16 символов (8 байт) и может включать любую дополнительную информацию, специфичную для приложения. Например, для повышения безопасности обмена данными между клиентом и сервером в каждой пользовательской сессии может быть назначено уникальное значение UKM, сгенерированное случайным образом. 

    Учитывая, что по нашей схеме закрытый ключ USB-токена нужен только для разблокирования системного диска, параметр ukmhex можно было бы и не использовать. Однако для алгоритма ГОСТ Р 34.10-2012 этот параметр является обязательным, поэтому в качестве фиксированной последовательности символов мы можем взять Hexspeak-строку «DeadBeef», которую используют, например, для еще неинициализированной памяти. Фанатам Java можно предложить последовательность «CafeBabe», которая используется в качестве сигнатуры объектных файлов, а для олдов отлично подойдет «1ce1ceBabe».

  • -out — определяет имя выходного файла для записи ключа.

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

sudo cryptsetup luksAddKey /dev/sda5 symmetric-key.der

Хранение данных в заголовке LUKS

Для расчета общего ключа симметричного шифрования кроме закрытого ключа на USB-токене потребуется открытый ключ второй ключевой пары и значение параметра ukmhex. Если в качестве ukmhex использовать фиксированное значение, то в заголовке LUKS достаточно хранить только открытый ключ pair2_gostpub.pem. Способ хранения будем использовать тот же, что и для RSA.

Если вы ранее уже добавили запись в раздел tokens заголовка LUKS, то это можно будет увидеть с помощью команды luksDump:

sudo cryptsetup luksDump /dev/sda5
Результат выполнения команды...
localadmin@pc:~$ sudo cryptsetup luksDump /dev/sda5
LUKS header information
...
Tokens:
  1: aldpro-rutoken
        Keyslot:  1
...

Удалить старую запись можно будет командой token remove:

sudo cryptsetup token remove /dev/sda5 --token-id 1

Записать новые данные в заголовок можно с помощью команды token import утилиты cryptsetup:

sudo cryptsetup token import /dev/sda5 --token-id 1 <<EOF
{
  "type": "aldpro-rutoken",
  "keyslots": ["1"],
  "gostpub": "$(base64 --wrap=0 pair2_gostpub.pem)",
}
EOF

Для извлечения токена можно воспользоваться командой token export:

localadmin@pc:~$ sudo cryptsetup token export /dev/sda5 --token-id 1
{"type":"aldpro-rutoken","keyslots":["1"],"gostpub":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUdZd0h3WUlLb1VEQndFQkFRRXdFd1lIS29VREFnSWpBUVlJS29VREJ3RUJBZ0lEUXdBRVFITkJIMUZzeVMxKwpHcWRodmNrd0g5ZEQwWWp2K3h6dlluV2lkeE00NDh3VUQ5ZUVIMWgvTEZHRkIzRkVIZ1h3Y01wc3ltMEhabGROClROdXBjM1V5SkZZPQotLS0tLUVORCBQVUJMSUMgS0VZLS0tLS0K"}

Скрипт для работы с USB-токенами на алгоритмах ГОСТ Р 34.10-2012

Добавим опцию keyscript в файл /etc/crypttab, чтобы система запрашивала пользовательский ключ с помощью нашего кастомного скрипта:

# Удалим параметр keyscript из конца строк, если он есть
sudo sed -i 's|,keyscript=.*||' /etc/crypttab
 
# Добавим параметр keyscript с нужным нам значением
sudo sed -i 's|$|,keyscript=/bin/unlock_luks_with_token|' /etc/crypttab
 
# Проверим результат
cat /etc/crypttab
Результат выполнения команд...
localadmin@pc:~$ sudo sed -i 's|,keyscript=.*||' /etc/crypttab
localadmin@pc:~$ sudo sed -i 's|$|,keyscript=/bin/unlock_luks_with_token|' /etc/crypttab
localadmin@pc:~$ cat /etc/crypttab
sda5_crypt UUID=453fa939-a585-4e1f-a030-b11991a78d3f none luks,discard,keyscript=/bin/unlock_luks_with_token

Создадим или модифицируем скрипт unlock_luks_with_token для разблокирования системного диска с помощью USB-токена на алгоритмах ГОСТ Р 34.10-2012:

Файл /bin/unlock_luks_with_token
#!/bin/sh

set +e # Выключаем выход из скрипта при ошибке

# Переключаемся в текстовый режим и устанавливаем кириллический шрифт
/usr/bin/plymouth hide-splash
setfont /usr/share/consolefonts/CyrSlav-Fixed16.psf.gz >&2

# Возвращаем графический интерфейс
/usr/bin/plymouth show-splash
 

check_token() {
    # Если токен отсутствует, код возврата будет равен единице
    /usr/bin/opensc-tool -n >/dev/null 2>&1;
    TOKEN_NOT_FOUND=$?
}

get_enc_data() {
    PARTITION=$CRYPTTAB_SOURCE
    HDR_SIZE=$(od -A n --endian=big -t d8 -j 8 -N 8 $PARTITION 2>/dev/null)
    JSON_SIZE=$(( HDR_SIZE - 4096 ))
    JSONDATA=$(dd if=$PARTITION bs=1 skip=4096 count=$JSON_SIZE 2>/dev/null | sed 's/\x0//g' )
    GOSTPUB=$(echo $JSONDATA | jq -r '[.tokens[] | select(.type == "aldpro-rutoken")][0].gostpub')
}

read_pass_from_keyboard(){
    if [ ${#PASS} -eq 0 ]; then
        /usr/bin/plymouth display-message --text="Разблокирование диска с помощью пароля"
        /usr/bin/plymouth display-message --text="Вы можете нажать клавишу <Enter>, чтобы переключиться на загрузку с помощью USB-токена"
        PASS=$(/usr/bin/plymouth ask-for-password --prompt="Введите пароль" | base64)
        if [ ${#PASS} -eq 0 ]; then
            return 0
        fi
    fi
}

read_pass_from_token(){
    /usr/bin/plymouth display-message --text="Разблокирование диска с помощью USB-токена"
    check_token
    if [ $TOKEN_NOT_FOUND = 1 ]; then
        /usr/bin/plymouth display-message --text="Не удалось найти USB-токен"
        return 0 
    fi
    
    /usr/bin/plymouth display-message --text="USB-токен подключен к компьютеру"

    get_enc_data
    /usr/bin/plymouth display-message --text="Парсинг заголовка LUKS выполнен успешно" 

    if [ $GOSTPUB = "null" ]; then
        /usr/bin/plymouth display-message --text="Не удалось найти данные для USB-токена в заголовке LUKS"
        return 0
    fi
    
    /usr/bin/plymouth display-message --text="Данные USB-токена найдены в заголовке LUKS" 
    while [ ${#PASS} -eq 0 ]; do
        /usr/bin/plymouth display-message --text="Вы можете нажать клавишу <Enter>, чтобы переключиться на загрузку с помощью пароля"
        PIN=$(/usr/bin/plymouth ask-for-password --prompt="Введите PIN-код от USB-токена")
        if [ ${#PIN} -eq 0 ]; then
            return 0
        fi

        echo $GOSTPUB | base64 -d > /tmp/gostpub.pem
        PKCS11ID=$(echo -n $UUID | sed -e 's/-//g' | fold -w 2 | sed 's/^/%/g' | tr -d '\r\n')
        PASS=$(/usr/bin/openssl pkeyutl --derive -keyform engine -engine rtengine -inkey "pkcs11:id=$PKCS11ID?pin-value=$PIN" -peerkey /tmp/gostpub.pem -pkeyopt ukmhex:deadbeefdeadbeef -out /tmp/opensslpipe >&2 2>/dev/null && base64 /tmp/opensslpipe)

        if [ $? -ne 0 ]; then
            /usr/bin/rtadmin info >&2
            /usr/bin/plymouth display-message --text="Неверный PIN-код"
            PASS=
        else
            /usr/bin/plymouth display-message --text="Пользовательский ключ успешно извлечен, для продолжения загрузки требуется извлечь USB-токен..."
            while true; do
                sleep 1
                check_token
                if [ $TOKEN_NOT_FOUND = 1 ]; then
                    /usr/bin/plymouth display-message --text="USB-токен извлечен, продолжаем загрузку"
                    return 0
                fi
            done
        fi

    done

}

PASS=
UUID=$(blkid -s UUID -o value $CRYPTTAB_SOURCE)
/usr/bin/plymouth display-message --text="Пробуем разблокировать диск $UUID"

while [ ${#PASS} -eq 0 ]; do
    read_pass_from_token
    read_pass_from_keyboard
done

echo -n "$PASS" | base64 -d

Сделаем скрипт исполняемым:

sudo chmod +x /bin/unlock_luks_with_token

Для возможности использования утилит pkcs, jq, rtengine и других файлов на ранних стадиях загрузки системы создадим или модифицируем хук unlock_luks_with_token_hook:

Файл /etc/initramfs-tools/hooks/unlock_luks_with_token_hook
#!/bin/sh
PREREQ=""
prereqs()
{
     echo "$PREREQ"
}

add_file()
{
    # Удаляем файл, если такой уже есть, поскольку функция copy_exec не умеет перезаписывать существующие файлы
    rm -f ${DESTDIR}$1
    # Добавляем указанный файл в initramfs
    copy_exec $1 $1
}

add_package()
{
    for fname in `/usr/bin/dpkg -L $1`; do
        if [ -f "$fname" ]; then
           add_file "$fname"
        else
           mkdir -p "${DESTDIR}$fname"
        fi
    done
}

case $1 in
prereqs)
     prereqs
     exit 0
     ;;
esac

# Подключаем скрипт с функцией copy_exec
. /usr/share/initramfs-tools/hook-functions

# Добавляем файлы для возможности использования кириллического шрифта CyrSlav-Fixed16.psf.gz
add_package console-setup-linux

# Добавляем файлы для использования утилиты setfont
add_package kbd

# Добавляем файлы для использования утилит base64, dd, od
add_package coreutils

# Добавляем файлы для использования утилиты openssl
add_package openssl

# Добавляем файлы для использования утилиты jq
add_package jq

# Добавляем файлы службы, управляющей подключениями к смарт-картам
add_package pcscd
mkdir -p ${DESTDIR}/var/run/pcscd

# Добавляем файлы библиотеки, предоставляющей программные интерфейсы Windows(R) SCard для доступа к смарт-картам
add_package libpcsclite1

# Добавляем файлы библиотеки, реализующей интерфейс обработчика устройств
add_package libccid

# Добавляем файлы библиотеки для работы с Рутокен ЭЦП по стандарту PKCS#11
add_package librtpkcs11ecp

mkdir -p ${DESTDIR}/tmp

# Добавляем файл утилиты rtadmin
add_file /usr/bin/rtadmin

# Добавляем файлы утилит, включая pkcs11-tool и opensc-tool, которые упрощают работу со смарт-картами
add_package opensc

# Добавляем файлы для возможности использования rtengine
add_file /usr/lib/rtengine/1.6.2/Linux/x64/lib/librtengine.so
add_file /usr/lib/rtengine/1.6.2/Linux/x64/lib/librtengine.so.1.6.2
add_file /usr/lib/x86_64-linux-gnu/engines-1.1/gost.so
add_file /usr/lib/x86_64-linux-gnu/engines-1.1/gost.so.1.1
add_file /usr/lib/ssl/openssl.cnf

Сделаем файл хука исполняемым:

sudo chmod +x /etc/initramfs-tools/hooks/unlock_luks_with_token_hook

Для возможности работы с USB-токенами в ядро initramfs нужно включить модуль opensc, для чего в файл /etc/initramfs-tools/modules требуется добавить следующие строки: 

Файл /etc/initramfs-tools/modules

...
# Модуль для работы с USB-токенами
opensc

Соберем образ initramfs:

sudo update-initramfs -u

Если вы еще не читали первую часть статьи, то выполните дополнительно следующие команды, чтобы настроить plymouth. Мы рекомендуем использовать тему spinner, которая отображает текстовые сообщения в центре экрана под индикатором загрузки, что делает их достаточно заметными.

sudo apt install --yes plymouth-themes
sudo plymouth-set-default-theme spinner --rebuild-initrd
sudo sed -i 's|"quiet|"splash quiet|' /etc/default/grub
sudo update-grub

И можно проверять. Но главное, не забывайте, что кроме технических средств у злоумышленников есть еще средства прямого воздействия, поэтому следи за собой, будь осторожен!

Вместо заключения

Как мы уже говорили в третьей части статьи, использование USB-токенов нам кажется более привлекательным, чем модулей TPM, поскольку последние произведены за границей и не обладают достаточным уровнем доверия. Одним из наиболее перспективных направлений для дальнейшего развития является реализация параметра групповой политики для обеспечения централизованного управления ключами LUKS через службу каталога. В домене ALD Pro уже есть параметр LAPS для управления паролями локальных администраторов и паролями GRUB2 и возможность управления ключами LUKS станет отличным дополнением, поскольку поможет в восстановлении доступа к данным при утере парольной фразы или USB-токена. Но это будет уже совсем другая история.