Общепринятой лучшей практикой считается использование отключенных от сети корневых удостоверяющих центров, a.k.a., offline root CA. Кроме того, не рекомендуется хранить закрытый ключ в файле, потому, что файл легко скопировать незаметно. Топовые HSM, типа Thales, стоят дорого: весь проект, включая пару HSM, установку и обучение сотрудников может встать в сотни тысяч долларов, что запретительно дорого для многих компаний. AWS CloudHSM обойдётся в сумму порядка десяти тысяч долларов в год.
Тут я расскажу, как из скриптов и палок собрать на коленке offline root CA с хранением закрытых ключей на YubiHSM. YubiHSM - это недорогой, стоимостью всего несколько сотен долларов, HSM, выполненный в виде USB модуля.
Я предполагаю, что тот, кто собрался делать offline CA, в курсе, как работает PKI, так что вводной части не будет - на Хабре и в Интернете достаточно руководств про это, например OpenSSL PKI Tutorial на readthedocs. У YubiHSM нет многих фич "больших" HSM, типа использования разделяемых по Шамиру ключей для резервного копирования или большого встроенного лога операций, но их вполне достаточно для 99% случаев.
Я рекомендую иметь как минимум два HSM - основной и резервный. Резервный, содержащий копии всех ключей и пользователей, нужно положить в сейф и периодически проверять, что он ещё там. Пользоваться основным.
Куча полезной информации про работу YubiHSM есть на сайте Yubico, настоятельно рекомендую ознакомиться перед внедрением.
Для начала нам нужен LiveUSB с Linux. Я взял Debian. Общение с YubiHSM производится посредством SDK и всё необходимое можно скачать отсюда: YubiHSM 2 libraries and tools. YubiHSM поддерживает udev hotplug, так что ещё нужно подложить файл с правилами для udev. Сам SDK работает через вспомогательный демон yubihsm-connector
. Он ждёт подключения клиента yubihsm-shell
. По умолчанию yubihsm-connector
слушает только на 127.0.0.1:12345, можно открыть его и в сеть, настроив аутентификацию по сертификатам, но это не нужно в нашем случае.
Итак, ставим нужный софт, правила для udev, и перезапускаем всё, что нужно:
sudo apt updat
sudo apt install -y libcurl4 opensc-pkcs11 libengine-pkcs11-openssl opensc libssl-dev pkg-config git libp11-3
tar xzvf yubihsm2-sdk-2022-06-debian11-amd64.tar.gz
cd yubihsm2-sdk/
sudo dpkg -i *.deb
cd ..
sudo install -o root -g root -m 0644 yubihsm-connector /etc/udev/rules.d
sudo systemctl restart udev
sudo systemctl restart yubihsm-connector
Обратите внимание, libp11 должна иметь версию не ниже 0.4.10.
А вот - содержимое файла yubihsm-connector с правилами для udev:
# This udev file should be used with udev 188 and newer
ACTION!="add|change", GOTO="yubihsm2_connector_end"
# Yubico YubiHSM 2
# The OWNER attribute here has to match the uid of the process running the Connector
SUBSYSTEM=="usb", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0030", OWNER="yubihsm-connector"
LABEL="yubihsm2_connector_end"
После это можно вставить YubiHSM в USB порт, обнаружить его в dmesg и попробовать подключиться к нему с помощью yubihsm-shell.
yubihsm> connect
Session keepalive set up to run every 15 seconds
yubihsm> session open 1 password
Created session 0
Пользователи пронумерованы, логином является нормер пользователя. Пароль административного пользователя 1 по умолчанию - "password". Чтобы сбросить YubiHSM к заводским настройкам (удалив всех пользователей и ключи, и пересоздав административного пользователя 1), нужно вставить ключ в USB порт и прижать палец к металлическому контакту на 10 секунд: Factory reset.
В yubihsm-shell (документация тут) можно отдавать различные команды HSM, но они достаточно низкоуровневые, чтобы работать с YubiHSM через PKCS11, нужно установить переменную окружения YUBIHSM_PKCS11_CONF с указанием файла конфигурацции. Этой переменной пользуется PKCS11 библиотека Yubico, которой в свою очередь пользуется libp11 и OpenSSL.
Вот минимальный файл конфигурации:
connector = http://127.0.0.1:12345
Кладём файл, например, в /etc/pki/yubihsm_pkcs11.conf
и устанавливаем переменную окружения:
export YUBIHSM_PKCS11_CONF=/etc/pki/yubihsm_pkcs11.conf
Теперь у вас должен работать не только yubihsm-shell
, но и PKCS11 библиотеки.
Теперь нужно настроить OpenSSL. Собственно, всё, что нужно сделать, это в отдельном файле конфигурации (скопируйте базовый и отредактируйте, выкинув лишнее) указать, что ключи нужно держать не в файле, а использовать библиотеку PKCS11. Вот релевантные части файла конфигурации OpenSSL:
openssl_conf = openssl_init
[openssl_init]
engines = engine_section
## Engine
[engine_section]
pkcs11 = pkcs11_section
[pkcs11_section]
engine_id = pkcs11
MODULE_PATH = /usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so
init = 0
Сохраним этот файл, как yubihsm.conf, теперь можно вызывать openssl
с этой конфигурацией, например, так:
openssl ca -config yubihsm.conf -engine pkcs11 -keyform engine ...
С технической частью покончили, перейдём к административной.
Как я упомянул выше, в YubiHSM есть пользователи, это не те пользователи, что в вашей операционной системе. Они нумеруются и им могут быть назначены различные права. В терминах Yubico права делятся на capabilities и delegations. Если вы знакомы с моделью безопасности Windows, capabilities - это как privileges, а delegations - это DACL на объекты в HSM.
Я не хочу обсуждать, какие варианты ролевой модели пользователей УЦ возможны, это тема отдельной статьи. У Microsoft есть немало подробных руководств на эту и другие темы вокруг CA и PKI: Public Key Infrastructure Design Guidance. Мне они показались очень полезными. В моем случае, я сделал три роли для пользователей:
администратор, может всё;
оператор, может выполнять операции с существующими ключами, но не может больше ничего, например, удалить их или создать новые;
аудитор, может просматривать, экспортировать и очищать встроенный лог YubiHSM.
В вашей ситуации устройство ролей может отличаться, но при дизайне я рекомендую учитывать два фактора:
сложная структура ролей требует операционной зрелости;
если всё делать от админа, можно случайно удалить ключ, а YubiHSM - не флешка с файловой системой, я не знаю, как их потом восстановить и думаю, что никак.
Мы решили завести двух пользователей в каждой роли, на случай увольнения или отпуска одного из них. Создание пользователя сводится к запуску yubihsm-shell с командой put-authentication-key, но для удобства я написал мини скрипт: yubihsm-adduser.
Вот мы и добрались до самого интересного - до создания ключей. Ключи можно создавать двумя способами - через PKCS11 или непосредственно в yubihsm-shell
, мы выбрали второй, потому, что так легче управлять правами. Ключей в YubiHSM может быть несколько, обратиться к конкретному можно используя номер объекта.
Поскольку мы живём в XXI веке с летающими ховербордами и блокчейном, мы решили не использовать RSA. Более подробно причины описаны в статье fuck RSA. Если вы - луддит, можете использовать RSA (в том числе с PSS), но помните, что длинные ключи будут тормозить, а 2048 бит маловато для корневого УЦ, который должен просуществовать лет 20. Вот так можно создать EC ключ P384:
yubihsm-shell --authkey 1 --password password --action generate-asymmetric-key --object-id 1 --label "Root CA key" --domains 1 --capabilities exportable-under-wrap:sign-ecdsa --algorithm ecp384
Опять же, чтобы было проще, я написал маленький исслюстративный скрипт, который создаёт два ключа - EC (для здоровых людей) и RSA (для курильщиков и луддитов) и нужных пользователей по схеме выше, всё это для двух HSM.
Теперь, когда закрытые ключи созданы, можно создать корневой сертификат на их основе. Это уже сделает OpenSSL. с нашим конфигом:
openssl req -new -sha256 -config yubihsm.conf -engine pkcs11 -keyform engine -key id_0001 -out root.pem -x509 -days 7300 -extensions root_ca_extensions -subj "CN=Enterprise Offline Root CA"
Обратите внимание, как сослаться на ключ в YubiHSM. Он доступен по номеру объекта, но номер обязательно должен быть четырёхзначным, с ведущими нулями, если необходимо. Это касается всех операций, использующих OpenSSL: он оперирует только понятием пина, и поэтому нужно имя пользователя и пин склеивать вместе, например, если у вас есть административный пользователь 1 с паролем q1w2e3r4, то в терминах OpenSSL пин будет 0001q1w2e3r4.
Вот секция "root_ca_extensions" из конфигурационного файла yubihsm.conf, на которую мы сослались при создании ключа:
[ root_ca_extensions ]
# Extensions for a typical CA (`man x509v3_config`).
# call with -extensions root_ca_extensions
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
Корневой УЦ готов! Его ключ - на HSM, а сертификат - в файле root.pem.
Дальше можно создавать остальную инфраструктуру, выпускающий online CA, CRL и тому подобное. Эти вопросы подробно описаны в других руководствах, так что не стану повторяться, напишу лишь команды для подписи CSR и CRL.
Так подписать CSR от промежуточного CA и выпустить сертификат:
openssl ca -config yubihsm.conf -engine pkcs11 -keyform engine -keyfile id_0001 -days 1095 -md sha256 -in inermediate.csr -extensions intermediate_ca_extensions
Вот секция "intermediate_ca_extensions" из конфига:
[ intermediate_ca_extensions ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
# call with -extensions intermediate_ca_extensions
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
crlDistributionPoints = URI:http://crl.example.com/root.crl
Обратите внимание, что сертификат промежуточного УЦ ссылается на CRL. Разумеется, этот CRL нужно периодически обновлять, чтобы валидация промежуточного сертификата (и все цепочки) была успешной. Поскольку наш УЦ отключен от сети, нужно периодически включать его, создавать CRL, и копировать его на нужный сайт. Вот так можно создать CRL:
openssl ca -config yubihsm.conf -engine pkcs11 -keyform engine -keyfile id_0001 -gencrl -out root.crl
Работа подкоманды openssl ca
требует секции "ca" в конфигурации, но её можно вполне оставить стандартной.
Вот и всё, буду рад ответить на вопросы.