[Из песочницы] Rutoken, OpenSSL и локальный УЦ для подписи сообщений

habr.png
Настраиваем «наш» 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
Опционально форматируем наш токен, используя утилиту rtAdminТеперь все готово к развертыванию УЦ.

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

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

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

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

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

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

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

Выпускаем самоподписанный сертификат CA:
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.

Успехов!

© Habrahabr.ru