Pull to refresh

Rutoken, OpenSSL и локальный УЦ для подписи сообщений

Reading time6 min
Views4.5K
Некоторое время назад возникла необходимость в рамках проекта ограничить одновременное число компьютеров, имеющих доступ к web-приложению, работающему в рамках локальной сети заказчика.

Решение использовать аппаратные USB-токены для идентификации компьютера пришло само собой. Выбор остановился на Рутокен ЭЦП: работает без драйверов, для работы в Web-приложении необходим лишь плагин для браузера, выпускаемый разработчиком. Поскольку токен должен идентифицировать компьютер, а не пользователя, работа с ним должна быть полностью «прозрачной»: если он есть, то система просто молча работает, не задавая пользователю лишних вопросов.

Было принято решение: при логине в систему подписывать идентификационные данные пользователя неквалифицированной подписью сертификата, хранящегося на Рутокене, средствами Рутокен-плагина, а на сервере осуществлять проверку. После успешного входа средствами плагина же проверять физическое наличие того самого токена, а при его отсутствии выполнять выход из системы. В рамках упомянутого проекта этого было достаточно.

Возникает необходимость создания своего Удостоверяющего Центра (УЦ) для обмена подписанными сообщениями, а точнее, для передачи подписанных сообщений с клиента на сервер. Клиентские сертификаты должны быть расположены на USB-токенах в контейнерах закрытых ключей, а проверка подписи должна выполняться на сервере средствами OpenSSL

Итак, задача: установка и настройка на Linux-сервере УЦ. Развертывание клиентских сертификатов, идентифицирующих компьютеры, на USB-токенах.

Для её решения понадобятся:


  1. Создаем каталог, где будет располагаться УЦ и копируем туда конфиг OpenSSL, входящий в комплект поставки системы (на последних версиях Ubuntu это /etc/ssl/openssl.cnf)
  2. Настраиваем «наш» openssl.cnf:

    а) Добавляем в начало файла директивы для подключения движка токена:

    openssl_conf = openssl_def
    [ openssl_def ]
    engines = engine_section
    
    [ engine_section ]
    rtengine = gost_section
    
    [ gost_section ]
    dynamic_path = /path/to/rutoken/openssl/connector/librtengine.so
    MODULE_PATH = /path/to/rutoken/pkcs11/librtpkcs11ecp.so
    RAND_TOKEN = pkcs11:manufacturer=Aktiv%20Co.;model=Rutoken%20ECP
    default_algorithms = CIPHERS, DIGEST, PKEY, RAND

    б) раскомментируйте строку

    # req_extensions = v3_req # The extensions to add to a certificate request

    в) в секции [ v3_req ] укажите следующие параметры:

    subjectSignTool = ASN1:FORMAT:UTF8,UTF8String:Наш Рутокен ЭЦП
    extendedKeyUsage=emailProtection
    keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
    

    г) в секции [ v3_ca ] надо убрать опцию critical из параметра basicConstraints:

    basicConstraints = CA:true

    Для чего? Честный ответ: не знаю. Однако все примеры корневых сертификатов, которые я скачивал в процессе попыток разобраться в теме, были без признака critical. Адресую вопрос «для чего?» более опытным в вопросе коллегам.


    д) опционально устанавливаем значения по умолчанию, которые будут предлагаться при выпуске самоподписанных сертификатов и генерации запросов на выпуск клиентских сертификатов. Эти параметры находятся в секции [ req_distinguished_name ]

    Параметр с постфиксом _default — то самое значение по умолчанию. Пример:

    countryName			= Country Name (2 letter code)
    countryName_default		= AU
    countryName_min		= 2
    countryName_max		= 2

    Когда система попросит ввести параметр countryName, то в квадратных скобочках укажет, что по умолчанию оставит значение AU.


    На этом настройка конфига OpenSSL завершена. Осталось указать OpenSSL, что использовать надо именно его. Для этого устанавливаем переменную окружения OPENSSL_CONF:

    export OPENSSL_CONF=/path/to/your/openssl.cnf
  3. Создаем структуру каталогов, где будет храниться информация о нашем УЦ.
    Для этого переходим в созданный каталог с только что отредактированным openssl.cnf, и выполняем следующие действия:

    а) создаем в нем подкаталоги:

    demoCA
    demoCA/private
    demoCA/newcerts

    Примечание: имя demoCA прописано в секции [ CA_default ] файла openssl.cnf. Вы можете изменить его (на шаге 2) и далее вместо demoCA работать именно с ним.

    б) в каталоге demoCA создаем пустой файл index.txt и файл serial, который открываем текстовым редактором и записываем туда строку 01. Это счетчик выпущенных сертификатов. После выпуска каждого очередного сертификата значение в этом файле увеличивается на единицу.
  4. Опционально форматируем наш токен, используя утилиту rtAdminТеперь все готово к развертыванию УЦ.

    Алгоритм действий в общих чертах прост:

    а) выпускаем корневой сертификат удостоверяющего центра, используя алгоритм ГОСТ:

    • генерируем закрытый ключ для выпуска самоподписанного сертификата CA
    • генерируем самоподписанный сертификат X509, используя сгенерированный ключ

    б) на каждом из USB-токенов

    • генерируем ключевую пару (т.н. контейнер закрытого ключа)
    • генерируем запрос на подпись сертификата, используя сгенерированный ключ токена
    • выпускаем сертификат по этому запросу
    • сохраняем сертификат на токене в контейнере закрытого ключа

    Ниже приведена реализация этого алгоритма для одного токена:

    Генерация закрытого ключа для сертификата CA (используем алгоритм ГОСТ):

    openssl genpkey -algorithm gost2012_256 -pkeyopt paramset:A -outform PEM -out demoCA/private/cakey.pem

    Выпускаем самоподписанный сертификат CA:

    <b>openssl req -new -x509 -key demoCA/private/cakey.pem -out demoCA/certs/cacert.pem -extensions v3_ca -days +3650 -outform PEM 

    Обратите внимание: мы указали в командной строке, что необходимо использовать расширения v3_ca из конфига openssl_cnf. Именно там прописано, что это наш CA. Срок действия 10 лет. Обычное дело для CA. Но можно и больше.

    В процессе выпуска сертификата система попросит ввести значения параметров, которые находятся в секции [ req_distinguished_name ] нашего файла openssl.cnf

    Теперь приступаем к операциям с токеном. Если токен новый, либо отформатированный со значениями по умолчанию, то PIN пользователя на нем 12345678. Я исхожу из предположения, что это именно так. Иначе необходимо указать корректный PIN пользователя и вообще стараться, чтобы в приведенных ниже примерах имена уже существующих на токене объектов не пересекались с вводимыми.

    Первым делом сгенерируем ключевую пару. OpenSSL не умеет выполнять эту операцию на Рутокене, поэтому воспользуемся утилитой pkcs11-tool из пакета OpenSC:

    pkcs11-tool --module /path/to/your/librtpkcs11ecp.so --login --pin 12345678 --keypairgen  --key-type GOSTR3410:A --id 303030303031 --label 'client01'

    Важное замечание: мы указали id 303030303031. Каждые две цифры этого id ни что иное как ASCII-код символов «0» и «1» соответственно. При операциях с OpenSSL это будет выглядеть как «id=000001»

    Генерируем запрос на сертификат:

    openssl req -utf8 -new -keyform engine -key 'pkcs11:id=000001' -engine rtengine -out demoCA/newcerts/client01.csr

    Если все было сделано верно, то система

    • запросит PIN
    • запросит параметры имени сертификата (из секции [ req_distinguished_name ])
    • выпустит файл запроса на подпись сертификата

    Используя этот запрос, подписываем клиентский сертификат (в примере срок действия сертификата составляет 1825 дней. Важно, чтобы этот срок не превышал срок действия вашего корневого сертификата):

    openssl ca -utf8 -days +1825 -keyfile demoCA/private/cakey.pem -cert demoCA/certs/cacert.pem -in demoCA/newcerts/client01.csr -outdir demoCA/newcerts -out demoCA/certs/client01.pem

    Система отобразит сертификат, спросит о решении подписать его (отвечаем «y»), и о решении сохранить новый сертификат (снова отвечаем «y»).

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

    pkcs11-tool --module /path/to/your/librtpkcs11ecp.so --login --pin 12345678 --id=303030303031 -w demoCA/certs/client01.pem -y cert
    

    Все.

    Тестируем созданное «чудо». Для этого подписываем и проверяем подпись фразы «Hello, world!»:

    echo Hello,world! | openssl cms -nodetach -sign -signer demoCA/certs/client01.pem -keyform engine -inkey "pkcs11:id=000001" -engine rtengine -binary -noattr -outform PEM | openssl cms -verify -CAfile demoCA/certs/cacert.pem -inform PEM

    Если все сделано верно, то система запросит PIN, подпишет сообщение, затем проверит подпись и в случае успеха выведет на экран исходное сообщение и результат проверки («успех»)

    Замечание. Возвращаясь к титульной задаче и подписи средствами плагина, необходимо отметить, что по умолчанию результат подписания плагин отдает не в формате PEM, а в формате DER, закодировав в base64. Поэтому для проверки подписи необходимо сначала декодировать из base64, а при проверке указывать входной формат DER.

    Успехов!
Tags:
Hubs:
+10
Comments2

Articles