В прошлый роз я писал про замечательный пакет opensc-pkcs11, в котором есть программа pkcs11-tool, позволяющая работать с USB-токенами, и в частности, с Rutoken (если скачать и установить нужную библиотеку-модуль librtpkcs11ecp.so).
Можно использовать USB-токен как аппаратное хранилище для паролей шифрования, защищенное пинкодом.
А сейчас - о том, как можно использовать USB-токен в качестве средства авторизации пользователей при входе на сайты.
Но начнем с конца:
Вместе с pkcs11-tool есть в пакете и программа pkcs11-register.
Что она делает - она "учит" другие программы работать с PKCS#11, например, браузеры.
Запускается она примерно вот так, однократно:
pkcs11-register --module librtpkcs11ecp.so
при этом она создает ряд записей в настроечных файлах.
Например, для Firefox будет создан файл ~/.mozilla/firefox/-current-profile-/pkcs11.txt, в котором будут указаны настройки для работы с PKCS#11, с правильным модулем и т.д.
Если после этого запустить Firefox, зайти в настройки, в раздел Privacy & Security, найти кнопку Security Devices - там должен появиться подключенный к компьютеру Rutoken.
Но есть нюанс: Rutoken Lite, о котором шла речь в прошлой статье, тут будет совершенно бесполезен: он не понимает «сертификаты», а браузер, в свою очередь, не понимает «просто записи».
Зато с сертификатами умеет работать полноценный Rutoken ЭЦП, со встроенным криптомодулем. Тот, который в описании у продавцов «подходит для ЕГАИС» (ну и разумеется, его аналоги, но их у меня нет, остановлюсь на Рутокене ЭЦП).
Отличие в том, что ЭЦП-версия не только умеет записывать данные типа data, но понимает разницу между публичными, приватными ключами и сертификатами, умеет создавать ключевые пары, верифицировать сертификаты и т.д. - и всё это через ту же самую программу pkcs11-tool, либо через соотвествующие библиотеки от нее (с которыми теперь умеет работать и Firefox).
То есть, заметно функциональнее.
А теперь — как можно это использовать к своей выгоде:
Можно настроить «клиентский сертификат» — сертификат будет записан на токене и отправляться при соединении с сервером, сервер сможет проверить правильность сертификата, и тем самым определить, кто к нему подключился.
То есть, вместо логина с паролем, двухфакторной авторизации и прочего — вставить в компьютер ключ, зайти на сайт, ввести свой пинкод, выбрать свой сертификат — и залогиниться в систему под нужными правами.
Разумеется, это такая больше корпоративная тема, «для своих» (выдача сертификатов, ключей, контроль доступа — вот это всё).
Допустим, нам надо такое - попробуем это настроить:
Прежде всего, понадобится CA, он же - Удостоверяющий Центр, УЦ.
Задача УЦ - удостоверить, что сертификат Васи Пупкина выдан действительно Васе Пупкину, а не сгенерирован каким-то посторонним челом.
Строго говоря, Вася Пупкин может и сам сгенерировать себе сертификат, главное - подписать его в УЦ, но сейчас не будем лишний раз усложнять.
Для организации УЦ существует специальный софт, также существуют специальные наборы скриптов типа EasyRSA, но традиционно напишу свой велосипед, чисто для понимания как оно устроено.
По сути всё просто:
в УЦ есть свой приватный и открытый ключи
сгенерированные сертификаты (неважно кем) подписываются на приватном ключе УЦ, что может сделать только держатель УЦ
если кто-то предьявляет сертификат - его можно проверить на открытом ключе УЦ, и понять, настоящий он или поддельный
также проверяется владение приватным ключом хозяина сертификата - то есть нельзя просто так взять и отправить чужой сертификат вместо своего
поскольку приватные ключи на токене неизвлекаемые - только тот, кто держит токен и знает пинкод, может отправить правильный сертификат, а значит скорее всего это хозяин и есть (исключая ректальный криптоанализ или иные силовые методы).
Для всего этого понадобятся несколько скриптов и системные утилиты, часть из которых уже установлена
apt install opensc-pkcs11 openssl libengine-pkcs11-openssl
Первый скрипт будет собственно для создания CA/УЦ:
нужно подготовить рабочие каталоги, списки для выданных и отозванных сертификатов, и пару ключей.
#!/bin/bash # Create CA DIR='./ca' mkdir -p ${DIR}/{certs,crl,newcerts,private} chmod 700 ${DIR}/private touch ${DIR}/index.txt echo 1000 > ${DIR}/serial echo 1000 > ${DIR}/crlnumber cat <<EOF > ${DIR}/openssl.cnf [ ca ] default_ca = CA_default [ CA_default ] dir = './ca' database = \$dir/index.txt serial = \$dir/serial private_key = \$dir/private/ca.key.pem certificate = \$dir/certs/ca.cert.pem crl = \$dir/crl/ca.crl.pem new_certs_dir = \$dir/newcerts crl_dir = \$dir/crl certs = \$dir/certs crlnumber = \$dir/crlnumber default_md = sha256 default_crl_days = 40 policy = policy_any [ policy_any ] commonName = supplied countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional emailAddress = optional EOF # private key CA openssl genrsa -out ${DIR}/private/ca.key.pem 4096 chmod 400 ${DIR}/private/ca.key.pem # public key CA openssl req -x509 \ -days 3650 \ -key ${DIR}/private/ca.key.pem \ -out ${DIR}/certs/ca.cert.pem # empty revoke list openssl ca -gencrl -config ${DIR}/openssl.cnf -out ${DIR}/crl/ca.crl.pem # check openssl x509 -in ${DIR}/certs/ca.cert.pem -noout -text |more
Скрипт создает каталоги, инициирует файлы, создает ключи, а также подготавливает пустой список отозванных сертификатов - он нужен для того, чтобы сразу подключить его к сайту, хотя ни одного сертификата еще нет.
Теперь у нас есть публичный ключ CA, список отозванных сертификатов CRL, и всё это нужно подключить к сайту.
На примере Nginx это будет выглядеть так:
server { listen 443 ssl; server_name example.com; # это СЕРВЕРНЫЕ ключи, для HTTPS, это никак не связано! ssl_certificate /etc/ssl/server.crt; ssl_certificate_key /etc/ssl/server.key; # а вот это - контроль клиентов ssl_client_certificate /etc/ssl/ca/ca.cert.pem; ssl_crl /etc/ssl/ca/ca.crl.pem; ssl_verify_client optional; # пробросим сертификат в backend proxy_set_header X-SSL-CERT $ssl_client_escaped_cert; proxy_set_header X-SSL-CLIENT-SUBJECT $ssl_client_s_dn; proxy_set_header X-SSL-CLIENT-VERIFY $ssl_client_verify; ... ... }
Что это дает? Теперь при входе на сайт браузер предложит выбрать клиентский сертификат, он будет проверен на публичном ключе ca.cert.pem, по списку отозванных ca.crl.pem, и если пройдет валидацию — в бекенд будут переданы заголовки HTTP, содержащие весь сертификат целиком, его Subject и флаг успешной валидации.
Останется только разобрать эти заголовки, и принять решение, например, какие права выдавать юзеру с такими параметрами.
Теперь нужно создать сам клиентский сертификат. Для начала — простой, файлами, для понимания принципа:
#!/bin/bash # Create user certificate DIR='./ca' read -e -p "Enter user nickname: " user_nick if [ "x$user_nick" = "x" ]; then exit 0 fi if [ ! -f "${DIR}/newcerts/$user_nick.key.pem" ]; then openssl genrsa -out "${DIR}/newcerts/$user_nick.key.pem" 2048 fi if [ ! -f "${DIR}/newcerts/$user_nick.key.pem" ]; then echo "ERROR: private key is not created!" exit 1 fi rm -f "${DIR}/newcerts/$user_nick.csr.pem" openssl req -new \ -key "${DIR}/newcerts/$user_nick.key.pem" \ -out "${DIR}/newcerts/$user_nick.csr.pem" \ -subj "/CN=$user_nick" if [ ! -f "${DIR}/newcerts/$user_nick.csr.pem" ]; then echo "ERROR: certificate request is not created!" exit 1 fi rm -f "${DIR}/newcerts/$user_nick.cert.pem" openssl ca -config ${DIR}/openssl.cnf \ -days 365 -notext -md sha256 \ -in "${DIR}/newcerts/$user_nick.csr.pem" \ -out "${DIR}/newcerts/$user_nick.cert.pem" if [ ! -f "${DIR}/newcerts/$user_nick.cert.pem" ]; then echo "ERROR: certificate is not signed!" exit 1 fi ls ca/*/${user_nick}.* echo "Signed certificate: ${DIR}/newcerts/$user_nick.cert.pem" openssl verify \ -CAfile ${DIR}/certs/ca.cert.pem \ "${DIR}/newcerts/$user_nick.cert.pem"
Создается закрытый ключ, затем запрос на сертификацию, затем запрос подписывается ключом CA, затем просто проверка, что получилось.
А получились два файла, приватный ключ и публичный сертификат (запрос не считаем).
Теперь то же самое — для USB‑токена:
#!/bin/bash # Create user certificate export PKCS11_MODULE_PATH=/usr/lib/librtpkcs11ecp.so DIR='./ca' read -e -p "Enter user nickname: " user_nick if [ "x$user_nick" = "x" ]; then exit 0 fi read -s -p "Enter token PIN: " user_pin if [ "x$user_pin" = "x" ]; then exit 0 fi UPIN="$user_pin" pkcs11-tool --module $PKCS11_MODULE_PATH --pin env:UPIN \ --keypairgen --key-type rsa:2048 \ --label "$user_nick" openssl req -new -engine pkcs11 \ -keyform engine \ -key "pkcs11:object=$user_nick;type=private" \ -subj "/CN=$user_nick" \ -out "${DIR}/newcerts/$user_nick.csr.pem" openssl ca -config ${DIR}/openssl.cnf \ -days 365 -notext -md sha256 \ -in "${DIR}/newcerts/$user_nick.csr.pem" \ -out "${DIR}/newcerts/$user_nick.cert.pem" UPIN="$user_pin" pkcs11-tool --module $PKCS11_MODULE_PATH --pin env:UPIN \ --write-object "${DIR}/newcerts/$user_nick.cert.pem" --type cert \ --label "$user_nick" echo "Signed certificate: ${DIR}/newcerts/$user_nick.cert.pem" openssl verify \ -CAfile ${DIR}/certs/ca.cert.pem \ "${DIR}/newcerts/$user_nick.cert.pem" UPIN="$user_pin" pkcs11-tool --module $PKCS11_MODULE_PATH --pin env:UPIN -O --type cert
Создается пара ключей на токене, затем на их основании создается запрос на сертификацию, подписывается, сертификат помещается обратно на токен.
И наконец, еще одна нужная процедура — отзыв сертификата. Если по какой‑то причине сертификат необходимо отменить до истечения срока действия — он отзывается принудительно:
#!/bin/sh DIR='./ca' if [ "x$1" = "x" ]; then exit 0 fi if [ ! -f "$1" ]; then exit 0 fi openssl ca -config ${DIR}/openssl.cnf \ -revoke "$1" \ -keyfile ${DIR}/private/ca.key.pem \ -cert ${DIR}/certs/ca.cert.pem openssl ca -gencrl -config ${DIR}/openssl.cnf -out ${DIR}/crl/ca.crl.pem openssl crl -in ${DIR}/crl/ca.crl.pem -noout -text openssl verify -crl_check \ -CRLfile ${DIR}/crl/ca.crl.pem \ -CAfile ${DIR}/certs/ca.cert.pem \ "$1"
Останется скопировать полученный ca.crl.pem на сервер, что можно вообще делать по крону автоматически. Включенные в него сертификаты не пройдут проверку.
Теперь подключаем USB‑токен к компьютеру, заходим на сайт с настроенной проверкой сертификатов — всё должно работать.
Если проверка прошла успешно — в http‑запросе на сервере появляются заданные заголовки, как их будет обрабатывать бекенд — это уже выходит за рамки данной статьи.
Скрипты собрал в один и закинул на Гитхаб.
