
Безопасная эксплуатация ноутбуков, или Защита пользовательского ключа с помощью USB-токена на примере Рутокен ЭЦП 3.0
Из второй части мы узнали, как настроить загрузку компьютера таким образом, чтобы для разблокирования системного диска использовались ключи, размещенные на внешнем USB-накопителе. Однако при краже компьютера вместе с этим накопителем злоумышленник сможет получить доступ к данным так, как если бы они не были защищены вовсе, поэтому наиболее привлекательным способом решения поставленной задачи видится использование USB-токенов и смарт-карт, таких как Рутокен ЭЦП 3.0 или JaCarta-2 ГОСТ. Токены представляют собой защищенные микроконтроллеры со встроенной энергонезависимой памятью, поэтому способны выполнять все вычисления самостоятельно без использования ресурсов центрального процессора, не допуская копирование закрытого ключа с устройства, что обеспечивает максимально высокий уровень безопасности.
Для того чтобы при загрузке компьютера не заставлять пользователя каждый раз вводить сложный пароль от диска, разработчики Windows предложили хранить его в доверенном платформенном модуле (Trusted Platform Module, TPM), и многие пользователи полагаются на BitLocker и модуль TPM, считая, что они надёжно защитят данные даже при краже устройства. На самом деле это не так, или даже совсем не так. Если TPM реализован как отдельный чип, то к его контактам можно подключиться и перехватить ключи с помощью аналого-цифрового преобразователя. Если же TPM встроен в процессор, то это тоже не даёт полной гарантии безопасности: для процессоров AMD существует атака faulTPM, позволяющая извлечь данные из памяти в открытом виде, а утечка исходных кодов Intel с закрытыми ключами ставит под сомнение надёжность технологии Intel Boot Guard. И это не считая того, что модули TPM произведены за границей, не обладают достаточным уровнем доверия, а компания Microsoft хранит резервные копии ключей Bitlocker в своем облаке исключительно ради заботы о пользователях и готова поделиться ими со всеми, кто очень попросит.
По нашему мнению, необходимый уровень защищенности могут обеспечить только активные USB-токены и смарт-карты, такие как Рутокен ЭЦП 3.0 или JaCarta-2 ГОСТ. Это защищенные микроконтроллеры со встроенной энергонезависимой памятью, которые способны выполнять все вычисления самостоятельно без использования ресурсов центрального процессора. Мы провели опрос в нашем сообществе, по результатам которого оказалось, что 56% респондентов разделяют наше мнение. Если вы считаете эту оценку заниженной или завышенной, подключайтесь к голосованию и поделитесь своим мнением.
Стоит отметить, что в подсистеме инициализации и управления службами systemd, начиная с версии 252, появился инструмент systemd-cryptenroll, который позволяет довольно просто регистрировать RSA-ключи аппаратных токенов для разблокирования разделов LUKS2. Но cryptenroll еще не поддерживает отечественные алгоритмы, поэтому мы решили задачу с использованием обычного keyscript. Сначала мы покажем пример на более понятных алгоритмах RSA, а затем перейдем к ГОСТ Р 34.10-2012.
Схема работы с USB-токенами и необходимые компоненты
Схема расшифровки диска LUKS с помощью закрытого RSA-ключа токена показана на рисунке 8 (сквозная нумерация рисунков, начало см. в первой части). Для извлечения данных, которые зашифрованы открытым ключом, приложению нужно отправить их на USB-токен, чтобы криптографический процессор устройства выполнил расшифровку с использованием неизвлекаемого закрытого ключа и вернул результат обратно. Существенным преимуществом USB-токенов и смарт-карт является возможность использования простых PIN-кодов для доступа к ключу и ограничения количество попыток ввода PIN-кодов, после расходования которых ключ полностью блокируется, что исключает возможность атак методом перебора.

Прикладные приложения взаимодействуют с USB-токенами через программные интерфейсы доступа к криптографическим устройствам (Application Programming Interface, API), которые описаны в 11-м стандарте для криптографии с открытым ключом (Public-Key Cryptography Standards, PKCS #11). Указанные API определяют функции, необходимые для создания, использования, изменения и удаления криптографических объектов (таких как RSA-ключи, Сертификаты X.509, Ключи DES/Triple DES, и др.). В настоящей инструкции мы рассматриваем устройства Рутокен ЭЦП 3.0, для поддержки которых требуется установить следующие компоненты:
pcscd– служба, управляющая подключениями к смарт-картам на персональных компьютерах (Personal Computer/Smart Card Daemon, PCSCD). Она позволяет приложениям получать доступ к криптографическим устройствам через программные интерфейсы WinScard без доступа к информации о самой карте или считывателе;libpcsclite1– библиотека, предоставляющая программные интерфейсы Windows(R) SCard для доступа к смарт-картам (это программное обеспечение проекта M.U.S.C.L.E., в операционных системах Windows аналогом этой библиотеки является служба Smart Card Resource Manager). Пакет устанавливается как зависимостьpcscd;libccid– библиотека, реализующая интерфейс обработчика устройств (Interface Device Handler, IFD) для смарт-карт на персональных компьютерах (Personal Computer/Smart Card Daemon, PCSCD), совместимых с интерфейсом устройств для чип-карт (Chip Card Interface Device, CCID). Пакет устанавливается как зависимостьpcscd;librtpkcs11ecp– библиотека для работы с Рутокен ЭЦП по стандарту PKCS#11.
В качестве клиентских приложений для работы с USB-токенами мы будем использовать:
opensc– набор утилит, включаяpkcs11-tool, которые упрощают работу со смарт-картами из прикладных приложений;opensc-pkcs11– модуль pkcs11 для работы со смарт-картами. Пакет устанавливается как зависимостьopensc;rtadmin– утилита для администрирования устройств Рутокен, с помощью которой можно отформатировать устройство, изменить метку, установить PIN-код, управлять разделами Flash-памяти. При работе с утилитой рекомендуется не подключать к компьютеру более одного устройства Рутокен.
Взаимодействие между компонентами отражено на рисунке 9. Мы чуть упростили схему, чтобы она отражала в полной мере наш случай без лишних деталей, но документация у коллег сделана на высшем уровне, поэтому очень рекомендуем к ознакомлению.

И вы спросите: "А причем тут операционная система Windows?". Когда мы обратились с этим вопросом к коллегам из Компании «Актив», Кирилл @mkirya рассказал, что изначально технологию для работы со смарт-картами разработала компания Microsoft, а затем уже Людовик Руссо (Ludovic Rousseau) перенес ее на MacOS и так она попала в Linux. Как говорится, то, что нужно всем, очень быстро перестает быть уникальным, а то, что слишком долго остается в категории "аналоговнету", может быть, просто никому и не нужно.

Установка и настройка пакетов для работы с USB-токенами
Установим пакет pcscd и все его зависимости с помощью пакетного менеджера apt:
sudo apt install --yes pcscd
Загрузим deb-пакет с библиотекой librtpkcs11ecp с помощью wget , после чего проверим контрольную сумму пакета и установим его с помощью dpkg:
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
Установим пакет opensc и все его зависимости с помощью пакетного менеджера apt:
sudo apt install --yes opensc
Загрузим утилиту rtadmin с помощью wget и проверим контрольную сумму, после чего сделаем файл исполняемым и скопируем его в системную директорию:
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
Далее в скрипте нам потребуется парсить JSON-объект, поэтому поставим сразу утилиту jq:
sudo apt install --yes jq
Инициализация токена
Линейка устро��ств Рутокен предлагает большое число форм-факторов от классических "флешек" и "донглов" до беспроводных "колец всевластия". Как говорится, на вкус и цвет все фломастеры разные. Когда мы начинали исследование, коллеги отсыпали нам несколько "свистков", на которых мы и проводили свои эксперименты.

После подключения токена к компьютеру его можно будет увидеть в списке USB-устройств с помощью команды lsusb.
Результат выполнения команды...
localadmin@pc:~$ lsusb Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 003: ID 0a89:0030 Aktiv Rutoken ECP <-- Вот строка, соответствующая токену Bus 001 Device 002: ID 80ee:0021 VirtualBox USB Tablet Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Информацию о токене можно посмотреть с помощью команды info утилиты rtadmin.
Результат выполнения команды...
localadmin@pc:~$ rtadmin info General info: Label : Rutoken Serial number : 1195787982 Model : Рутокен ЭЦП 3.0 3120 Memory(KB) (free/all) : 83 / 128 User's PIN length : 8-249 Admin's PIN length : 8-249 User's PIN retry count (left/all) : 5 / 5 Admin's PIN retry count (left/all) : 5 / 5 PIN change policy : user Pin policy: Allow changing : true Remove on format : true Default PIN allowed : true Diff chars required : false Digit required : false Lowercase required : false Uppercase required : false Spec char required : false Min length : 0 History depth : 0
Перед началом использования устройства офицер безопасности должен инициализировать USB-токен и установить на него собственный PIN-код, который называется SO-PIN (Security Officer PIN), и PIN-код пользователя:
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
, где:
--no-log— отключает логирование. По умолчанию утилита пишет лог в ту же директорию, где находится исполняемый файл;-r (--repair)— переключает утилиту в режим форматирования без ввода текущего значения SO-PIN, который неизвестен администратору;-l (--label)— устанавливает текстовую метку для упрощения идентификации устройства;--new-so-pin— определяет новое значение PIN-кода офицера безопасности. Вместо этого параметра можно передать параметр --new-so-pin-input со значением console, тогда утилита запросит новое значение SO-PIN в интерактивном режиме. Если новое значение SO-PIN будет не определено, то утилита по умолчанию установит значение87654321;--new-user-pin— определяет новое значение PIN-кода пользователя. Вместо этого параметра можно передать параметр --new-user-pin-input со значением console, тогда утилита запросит новое значение PIN-кода в интерактивном режиме. Если новое значение PIN-кода будет не определено, то утилита по умолчанию установит значение12345678;--max-so-pin-retry-count— определяет максимально допустимое количество ошибок ввода SO-PIN администратора. По умолчанию администратору дается 10 попыток;--max-user-pin-retry-count— определяет максимально допустимое количество ошибок ввода PIN-кода пользователя. По умолчанию пользователю дается 10 попыток;--min-so-pin— определяет минимальную длину PIN-кода администратора;--min-user-pin— определяет минимальную длину PIN-кода пользователя;--pin-change-policy— определяет политику изменения PIN-кода:значение
user— означает, что PIN-код пользователя может сменить только сам пользователь;значение
so— означает, что PIN-код пользователя может сменить только офицер безопасности;значение
both– означает, что PIN-код пользователя может сменить как сам пользователь, так и офицер безопасности.
Если офицер безопасности захочет установить новое значение SO-PIN без форматирования устройства, он может воспользоваться командой set-so-pin. Например, изменим значение 87654321, которое устанавливается на токен по умолчанию:
rtadmin set-so-pin --no-log --auth-pin 87654321 --new-pin 16613403
, где:
set-so-pin— команда изменения SO-PIN, если известно текущее значение;-p (--auth-pin)— параметр, который определяет текущее значение SO-PIN. Вместо этого параметра можно передать параметр-P(--auth-pin-input) со значениемconsole, тогда утилита запросит текущее значение SO-PIN в интерактивном режиме;-n (--new-pin)— параметр, который определяет новое значение SO-PIN. Вместо этого параметра можно передать параметр-N(--new-pin-input) со значениемconsole, тогда утилита запросит текущее значение SO-PIN в интерактивном режиме.
После выдачи устройства пользователь может сменить свой PIN-код, если это будет разрешено политикой, см. параметр --pin-change-policy. Например, если офицер безопасности оставил значение 12345678, которое устанавливается на USB-токен по умолчанию, то его можно сменить следующей командой:
rtadmin set-user-pin --no-log --auth-as user --auth-pin 12345678 --new-pin 20439756
, где:
set-so-pin— команда изменения PIN-кода пользователя;--auth-as— параметр, который определяет, чей PIN-код будет предоставлен для авторизации:значение
user— будет предоставлен PIN-код пользователя,значение
so— будет предоставлен SO-PIN администратора;
-p(--auth-pin) — параметр, который определяет текущее значение PIN-кода пользователя или SO-PIN администратора. Вместо этого параметра можно передать параметр-P(--auth-pin-input) со значениемconsole, тогда утилита запросит текущее значение PIN-кода в интерактивном режиме;-n(--new-pin) — параметр, который определяет новое значение PIN-кода. Вместо этого параметра можно передать параметр-N(--new-pin-input) со значениемconsole, тогда утилита запросит текущее значение PIN-кода в интерактивном режиме.

Как мы уже обсуждали, преимущество USB-токенов и смарт-карт состоит в том, что PIN-код запомнить намного проще, чем пароль, но при этом физическое устройство гарантирует высокий уровень безопасности за счет ограничения количества попыток. Учитывая 10 попыток и длину PIN-кода в 8 цифр, злоумышленник сможет выиграть с вероятностью один шанс на десять миллионов, что, правда, все равно побольше, чем в лотерее, когда не купил ни одного билета.
Создание ключевой пары на токене
Чтобы не вводить идентификатор системного диска вручную, запишем его значение в переменную UUID:
UUID=$(sudo blkid --match-token TYPE=crypto_LUKS --output value -s UUID) echo $UUID
Результат выполнения команд...
localadmin@pc:~$ UUID=$(sudo blkid --match-token TYPE=crypto_LUKS --output value -s UUID) localadmin@pc:~$ echo $UUID 453fa939-a585-4e1f-a030-b11991a78d3f
Для шифрования пользовательского ключа LUKS нам нужна будет ключевая пара на токене, сгенерируем ее с помощью утилиты pkcs11-tool:
pkcs11-tool --login --pin 20439756 --keypairgen --key-type RSA:2048 \ --label $UUID \ --module /usr/lib/librtpkcs11ecp.so
, где:
--login— указывает, что выполнение команды потребует авторизации, поэтому сначала нужно пройти аутентификацию;-p(--pin) — определяет текущее значение PIN-кода пользователя или SO-PIN администратора. По умолчанию вход выполняется от имени пользователя, поэтому если потребуется выполнить вход от имени администратора, то нужно будет дополнительно определить параметр--login-typeсо значениемso;--keypairgen— определяет, что нужно сгенерировать ключевую пару;--key-type— определяет тип ключевой пары. В приведенном примере мы используем алгоритм RSA с длиной ключа 2048 бит;--label— определяет имя (метку) объекта, по которому его можно будет найти на токене. В приведенном примере мы записали в качестве метки идентификатор дискаUUID;--module— определяет путь к модулюlibrtpkcs11ecp. По умолчанию утилита будет использовать модуль/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so.
Для подтверждения того, что ключевая пара была успешно сгенерирована на USB-токене, можем найти ее в списке объектов:
pkcs11-tool --login --pin 20439756 --list-objects \ --module /usr/lib/librtpkcs11ecp.so
, где:
-O(--list-objects) — определяет, что нужно отобразить список всех ключей.
Результат выполнения команды
localadmin@pc-1:~$ pkcs11-tool --login --pin 20439756 --list-objects \ > --module /usr/lib/librtpkcs11ecp.so Using slot 0 with a present token (0x0) Private Key Object; RSA label: 453fa939-a585-4e1f-a030-b11991a78d3f Usage: decrypt, sign Access: sensitive, always sensitive, never extractable, local Allowed mechanisms: RSA-PKCS,RSA-PKCS-PSS,RSA-X-509,MD5-RSA-PKCS,SHA1-RSA-PKCS,SHA224-RSA-PKCS,SHA256-RSA-PKCS,SHA384-RSA-PKCS,SHA512-RSA-PKCS,SHA1-RSA-PKCS-PSS,SHA224-RSA-PKCS-PSS,SHA256-RSA-PKCS-PSS,SHA384-RSA-PKCS-PSS,SHA512-RSA-PKCS-PSS,RSA-PKCS-OAEP Public Key Object; RSA 2048 bits label: 453fa939-a585-4e1f-a030-b11991a78d3 Usage: encrypt, verify Access: local
Извлечем открытый ключ, чтобы использовать его для шифрования данных:
pkcs11-tool --read-object --type pubkey \ --label $UUID \ --output-file pubkey.der \ --module /usr/lib/librtpkcs11ecp.so
, где:
-r(--read-object) — определяет, что нужно извлечь данные из токена, используется вместе с параметром--type;-у(--type) — определяет тип извлекаемого объекта, например,cert,privkey,pubkey,secrkey,data;-a(--label) — определяет имя (метку) объекта, по которому его можно будет найти на токене;-o(--output-file) — определяет исходящий файл, в который нужно записать полученные данные.
Файл открытого ключа будет представлять собой бинарные данные в формате DER (англ. Distinguished Encoding Rules — особые правила кодирования), а нам в дальнейшем нужен будет ключ в формате PEM (англ. Privacy-Enhanced Mail — почта с повышенной секретностью). Преобразование ключа выполним с помощью утилиты openssl следующей командой:
openssl rsa -inform DER -pubin -in pubkey.der -outform PEM -out pubkey.pem
, где:
rsa— определяет, что нужно выполнить операцию с ключами RSA;-inform— определяет формат входящих данных;-pubin— указывает, что нужно вычитать публичный ключ;-in— определяет имя файла с входными данными;-outform— определяет формат исходящих данных;-out— определяет имя файла, в который нужно записать выходные данные.
Если нужно будет удалить ключевую пару с токена, то это можно сделать командой --delete-object. Для настройки токена этого не требуется.
Пример команд для удаления ключей с токена...
pkcs11-tool --login --pin 20439756 --delete-object --type=privkey --id 007f0101 \ --module /usr/lib/librtpkcs11ecp.so pkcs11-tool --login --pin 20439756 --delete-object --type=pubkey --id 007f0101 \ --module /usr/lib/librtpkcs11ecp.so
Шифрование пользовательского ключа
Если вы еще не читали вторую часть статьи, то выполните дополнительно следующие команды, чтобы ��генерировать случайный пользовательский ключ и записать его в заголовок LUKS:
dd if=/dev/urandom bs=1 count=32 > $UUID.lek sudo cryptsetup luksAddKey /dev/sda5 $UUID.lek
Теперь мы можем зашифровать пользовательский ключ LUKS открытым ключом USB-токена, чтобы расшифровать такие данные можно было только с помощью закрытого ключа на токене. Шифрование будем выполнять с помощью утилиты openssl следующей командой:
openssl rsautl -encrypt -pubin -inkey pubkey.pem \ -in $UUID.lek \ -out $UUID.lek.rsaenc
, где:
rsautl— команда для использования функций асимметричного шифрования RSA, см.man openssl-rsautl;-encrypt— указывает, что нужно выполнить шифрование входящих данных с использованием алгоритма RSA;-pubin— определяет, что для шифрования будет предоставлен открытый ключ RSA. Параметр следует указывать, поскольку по умолчанию предполагается, что будет передан закрытый ключ и нужно создать цифровую подпись;-inkey— определяет файл ключа;-in– определяет входящий файл, данные которого нужно зашифровать;-out– определяет исходящий файл, в который нужно записать зашифрованные данные.
Расшифровку ключа в keyscript можно будет выполнить с помощью следующей команды:
pkcs11-tool --decrypt --label $UUID --pin 20439756 --mechanism RSA-PKCS \ --input-file $UUID.lek.rsaenc \ --output-file $UUID.lek.raw \ --module /usr/lib/librtpkcs11ecp.so
, где:
--decrypt— определяет, что нужно выполнить расшифровку данных;--label— определяет метку закрытого ключа на USB-токене, с помощью которого нужно выполнить расшифровку данных;-p(--pin) — определяет PIN-код пользователя, которому принадлежит закрытый ключ;-m(--mechanism) — указывает необходимость использования механизма RSA-PKCS, полный перечень можно получить с помощью параметра-M(--list-mechanisms);-i(--input-file) — определяет входящий файл для расшифровки;-o(--output-file) — определяет исходящий файл, в который нужно записать результат.
Хранение данных в заголовке LUKS
Пользовательский ключ LUKS, зашифрованный открытым ключом USB-токена, можно хранить непосредственно на USB-токене, но это противоречит лучшим практикам, поскольку зашифрованные данные рекомендуется хранить отдельно от ключа, которым они зашифрованы. Чтобы не использовать дополнительных USB-накопителей, запишем эти данные непосредственно в заголовок LUKS, в раздел tokens объекта JSON, управлять которым можно с помощью команд утилиты cryptsetup (см. man cryptsetup-token).
Записать данные в заголовок LUKS можно с помощью команды import:
sudo cryptsetup token import /dev/sda5 --token-id 1 <<EOF { "type": "aldpro-rutoken", "keyslots": ["1"], "rsaenc": "$(base64 --wrap=0 $UUID.lek.rsaenc)", } EOF
После этого токен будет отображаться в дампе заголовка LUKS:
localadmin@pc-1:~$ sudo cryptsetup luksDump /dev/sda5 ... Tokens: 1: aldpro-rutoken Keyslot: 1 ...
Работать с токеном по его идентификатору можно с помощью параметра --token-id. Для извлечения токена можно воспользоваться командой export:
localadmin@pc:~$ sudo cryptsetup token export /dev/sda5 --token-id 1 {"type":"aldpro-rutoken","keyslots":["1"],"rsaenc":"1jQMvsk4rkcmeEuB1ePES\/GLfxKUKSTCAHYdbLGSMMsp21Spqls4VOpYzOczpCDYdDVGfzcXwnsb6CHbXDMox\/4yv9a4ynt5VhlSjwtpShlxx9EXSXC3j\/fzqAGicNOfkqe6QxIAdl5oWTGXAW\/YtCgEbWT5XZFkTmBVCdBEP76cdpIQqhEXm1XvctAGyZBXTMzbVYWvM7gD\/0v7xxjhnzVTpTPHrjVdHkI5\/kpHlN7+ecGW7D42F6cLom+DRWdTsJg3AZWtu3Orr6gEBujADkbZP+0ioubOYroo1a1tDqUmZyDqhz+3BTg0E+Qy76nK28sgMP3fnu44+EKa\/HhD3Q=="}
Но в скрипте keyscript нам потребуется извлекать токен по его метке, а в утилите cryptsetup никаких подходящих параметров нет, поэтому Вадим @NotMusk предложил выполнять парсинг JSON-объекта напрямую.
# Имя раздела LUKS PARTITION=/dev/sda5 # Размер заголовка определяют 8 байт со смещением в 8 байт HDR_SIZE=$(sudo od -A n --endian=big -t d8 -j 8 -N 8 $PARTITION 2>/dev/null) # Размер JSON-заголовка меньше на один кластер (4096 байт), в котором находится бинарный заголовок JSON_SIZE=$(( HDR_SIZE - 4096 )) # Заголовок JSON находится сразу за первым кластером, поэтому смещение должно быть в 4096 байт JSONDATA=$(sudo dd if=$PARTITION bs=1 skip=4096 count=$JSON_SIZE 2>/dev/null | sed 's/\x0//g' ) # Находим в словаре tokens первый элемент с значением type равным aldpro-rutoken и берем атрибут encdata RSAENC=$(echo $JSONDATA | jq -r '[.tokens[] | select(.type == "aldpro-rutoken")][0].rsaenc') echo $RSAENC
Если нужно будет удалить токен из заголовка LUKS, это можно сделать командой token remove. Для настройки токена этого не требуется.
Пример команды для удаления токена из заголовка LUKS...
sudo cryptsetup token remove /dev/sda5 --token-id 1
Скрипт для работы с USB-токенами
Добавим опцию 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-токена:
Файл /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' ) RSAENC=$(echo $JSONDATA | jq -r '[.tokens[] | select(.type == "aldpro-rutoken")][0].rsaenc') } 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 [ $RSAENC = "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 PASS=$(echo -n $RSAENC | base64 -d | /usr/bin/pkcs11-tool --decrypt --label $UUID --pin $PIN --mechanism RSA-PKCS --output-file /tmp/opensslpipe --module /usr/lib/librtpkcs11ecp.so >&2 && 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 и других файлов на ранних стадиях загрузки системы создадим хук 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
Сделаем файл хука исполняемым:
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-токен, перезагрузим машину, введем PIN-код и нажмем клавишу <Enter>. При необходимости пользователь сможет разблокировать диск и без USB-токена, используя сложный пароль, записанный на бумажке из сейфа.


В четвертой заключительной части мы расскажем о том, как заменить буржуйский зарубежный алгоритм RSA на доверенный ГОСТ Р 34.10-2012.
