В данной статье будет расписана описана типичная схема подобной интеграции, основанная на следующих сценариях использования плагина:
...
Плагина:
Table of Contents
...
Данные сценарии предполагают клиент-серверное взаимодействие, написание клиентских скриптов на JavaScript и соответствующих им серверных вызовов openssl.OpenSSL.
Info | ||
---|---|---|
| ||
Если вы открываете локальную страницу, убедитесь, что в настройках расширения Адаптер Рутокен Плагин включена опция "Разрешить открывать локальные файлы по ссылкам" |
Общие операции
Так как все функции Плагина выполняются в отдельных потоках, не заставляя браузер ждать выполнения операций, его интерфейс постороен на промисах. Промисы - это способ организации асинхронного кода. Универсальный метод добавления обработчиков:
Code Block | ||||
---|---|---|---|---|
| ||||
promise.then(onFulfilled, onRejected) // onFulfilled – функция, которая будет вызвана с результатом при успешном выполнении асинхронной функции. // onRejected – функция, которая будет вызвана с ошибкой при ошибке выполнения асинхроннной функции. |
Операции с устройством
Поиск подключенных устройств
Любой клиентский сценарий начинается с поиска подключенных к компьютеру USB-устройств Рутокен.
...
Рутокен Плагин определяет все подключенные к компьютеру USВ-устройства Рутокен ЭЦП. Поэтому следующим шагом следует определить тип устройства.
Получение информации об устройстве
Для определения типа устройства следует использовать функцию getRutokenModelName() репозитория https://github.com/AktivCo/blade-runner.github.iorutoken-model-by-hw-features-js
Параметр функции getDeviceInfo TOKEN_INFO_DEVICE_TYPE – устарел и не поддерживается.
...
- модель устройства
- метку устройства
- серийный номер устройства
- информацию о том, залогинен ли пользователь на устройство
Смена PIN-кода
Code Block | ||||
---|---|---|---|---|
| ||||
// вызывается метод Promise, который должен вернуть Id устройства .then(function(rutokenHandle){ options={} return plugin.changePin(rutokenHandle, "12345678", "12345678", options) }) .then(function() { alert("PIN изменен успешно"); }, handleError) |
Здесь первым параметром выступает старый PIN-код, а вторым новый PIN-код.
Работа с сертификатами
1. На токене могут храниться 3 4 категории сертификатов:
- пользовательские (константа в плагине CERT_CATEGORY_USER)
Это сертификаты, связанные с закрытым ключом пользователя. Применяются, например, для подписи CMS/PKCS#7, аутентификации пользователя в протоколе TLS.
Если сертификат импортируется как пользовательский, то при импорте будет произведен поиск в устройстве соответствующего ему закрытого ключа. Если такой ключ будет найден, то сертификат будет «привязан» к этому ключу. Если ключ найден не будет, то вернется ошибка. - корневые (константа в плагине CERT_CATEGORY_CA)
Это сертификаты издателей сертификатов, применяющиеся для проверки подписи под сертификатами. Подобная проверка подписи (построение цепочки доверия) позволяет определить, доверяет ли пользователь подписи другого пользователя. Например, в функции плагина verify есть режим проверки сертификата, на котором было подписано сообщение. При этом используется хранилище корневых сертификатов на токене, созданное импортом корневых сертификатов на токен. - Неуточненная категория сертификатов (CERT_CATEGORY_UNSPEC)
- другие (константа в плагине CERT_CATEGORY_OTHER)
Это сертификаты, которые не связаны с закрытым ключом и не являются корневыми.
...
4. Сертификат можно распарсить вызовом функции parseCertificate и получить из него DN Subject, DN Issuer, расширения, значение открытого ключа, подпись, серийный номер, срок действия и т.п.
5. Сертификат можно записать на устройство.
Code Block | ||||
---|---|---|---|---|
| ||||
var certpem = "-----BEGIN CERTIFICATE----- MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+ -----END CERTIFICATE-----"; // ... // вызывается метод Promise, который должен вернуть Id устройства и Id сертификата .then(function(rutokenHandle, certPem) { return plugin.importCertificate(rutokenHandle, certPem, plugin.CERT_CATEGORY_USER); }) // Отображение подписанных данных в формате PEM .then(function() { alert("Сертификат импортирован успешно"); }, handleError) |
6. Вызовом функции deleteCertificate можно удалить сертификат с токена.
Работа с ключевыми парами ГОСТ
1. Для получения дескрипторов ключевых пар, хранящихся на устройстве, требуется ввод PIN-кода. Следует понимать, что само значение закрытого ключ получено быть не может, так как ключ является неизвлекаемым.
...
2. Для генерации ключевой пары требуется ввод PIN-кода. При генерации ключа можно задать можно задать параметры ключевой пары. Если опция не задана, будут выбраны параметры А для любого из алгоритмов ГОСТ.
...
- "A"
- "B"
- "C"
- "XA"
- "XB"
...
.
...
Code Block | ||||
---|---|---|---|---|
| ||||
// вызывается метод Promise, который должен вернуть Id устройства .then(function(rutokenHandle) { return plugin.generateKeyPair(rutokenHandle, undefined, marker, options); }) .then(function(keyId) { alert("Ключевая пара сгенерирована успешно"); }, handleError) |
3. С помощью функции deleteKeyPair ключевая пара может быть удалена с токена.
Создание и проверка электронной подписи
1. Для создание подписи используется метод sign:
Code Block | ||||
---|---|---|---|---|
| ||||
// Подпись
.then(function(certHandle, textToSign) {
var options = { rsaHashAlgorithm: plugin.HASH_TYPE_SHA256 };
return plugin.sign(rutokenHandle, certHandle, textToSign, plugin.DATA_FORMAT_PLAIN, options);
) |
Последние аргумент – опции функции. Их подробный список можно найти в документации, некоторые из них:
- detached:bool (false) - генерировать отсоединенную подпись
- addUserCertificate:bool (true) - включить в подпись сертификат пользователя
- addSignTime:bool (false) - включить в подпись время подписи
- useHardwareHash:bool (false) - производить аппаратное хеширование данных на ключах ГОСТ
- rsaHashAlgorithm:enum - алгоритм хеширования при использовании ключей RSA, варианты: HASH_TYPE_MD5, HASH_TYPE_SHA1, HASH_TYPE_SHA256, HASH_TYPE_SHA512. Нужно обязательно указывать при подписи на RSA ключах.
Предпоследний аргумент определяет формат подписываемых данных:
...
- data:string (null) - подписанные данные (текстовые или base64-encoded), только в случае detached подписи;
- base64:bool (false) - указывает, закодированы ли данные, переданные в data, в base64;
- certificates:string[] (null) - набор сертификатов в PEM формате, на которых необходимо проверять подпись, при этом сертификаты, которые содержатся в cms, будут проигнорированы;
- CA:string[] (null) - список дополнительных корневых сертификатов в PEM формате для проверки сертификата, кроме них берутся сертификаты с устройства;
- CRL:string[] (null) - список отозванных сертификатов в PEM формате;
- useHardwareHash:bool (false) - производить хеширование на устройстве (игнорируется для алгоритмов отличных от ГОСТ Р 34.10-2001);
- verifyCertificate:bool (true) - проверить сертификат пользователя;
Конфигурирование openssl
См. инструкцию Интеграция ГОСТ 2012 с Рутокен ЭЦП и OpenSSL 1.1.0 или новее, раздел Установка и настройка OpenSSL для работы с rtengine 0.7.x
Теперь перейдем к реализации законченных пользовательских сценариев.
Регистрация на портале
Сертификат выдается при регистрации в системе
- Получаем список подключенных к компьютеру устройств Рутокен ЭЦП 2.0ЭЦП
- Генерируем ключевую пару по ГОСТ Р 34.10-2012 на выбранном Рутокен ЭЦП 2.0ЭЦП
- Cоздаем запрос PKCS#10 на сертификат для сгенерированной ключевой пары
- Отправляем запрос на сервер
- На сервере создаем сертификат, привязываем к аккаунту (сам сертификат или его дескриптор). Следует отметить, что дескрипторы сертификатов, полученные при вызове функции enumerateCertificates, являются уникальными и неизменными
- Отправляем сертификат на клиент
- На клиенте визуализируем полученный сертификат
- Импортируем полученный сертификат в Рутокен ЭЦП 2.0ЭЦП
Последовательность вызовов в клиентском скрипте будет следующей:
...
Дальнейшая последовательность вызовов на клиенте:
Сертификат уже имеется на токене, выдан внешним УЦ
Ключевая пара при этом должна быть создана в формате, совместимом с библиотекой rtPKCS11ECP для Рутокен ЭЦП 2.0 .
- Получаем список подключенных к компьютеру устройств Рутокен ЭЦП 2.0ЭЦП
- Получаем список всех имеющихся пользовательских сертификатов на выбранном Рутокен ЭЦП 2.0ЭЦП
- Визуализируем каждый сертификат
- Пользователь выбирает нужный сертификат
- Сервер формирует начальную последовательность случайных данных (строку salt) и отправляет ее на клиент
- Вызываем на клиенте authenticate. При передаче salt в функцию плагина authenticate данная последовательность дополняется дополнительными случайными данными размером в 32 символа, и происходит подпись итоговой последовательности на выбранном пользователем сертификате в формате CMS attached
- Подпись отправляется на сервер
- На сервере происходит проверка CMS attached подписи с использованием корневого сертификата
- Из CMS attached сообщения извлекается итоговая случайная последовательность, “отсоединяется” salt и происходит сравнение
- Если сравнение успешно, то регистрируем пользователя по сертификату, который содержится в CMS attached сообщении
...
Code Block | ||||
---|---|---|---|---|
| ||||
openssl x509 -in cert.pem -noout -email |
Code Block | ||||
---|---|---|---|---|
| ||||
openssl x509 -in cert.pem -noout -startdate |
Code Block | ||||
---|---|---|---|---|
| ||||
openssl x509 -in cert.pem -noout -enddate |
Строгая аутентификация на портале
Общая схема аутентификации, используемая в Рутокен Плагин, выглядит следующим образом:
...
Реализация данной схемы ничем принципиально не отличается от «Регистрация, сертификат уже имеется, выдан внешним УЦ».
Электронная подпись данных и/или файлов в формате CMS
- формируется текстовое сообщение (строка), формирование сообщения может происходить как на сервере, так и на клиенте
- если требуется подписать документ произвольного формата (например, PDF), то требуется перекодировать его в формат base64
- строка, содержащая данные для подписи, передается в функцию sign
- если строка представляет собой закодированные в base64 данные, то параметр функции isBase64 должен быть установлен в true, при этом перед подписью произойдет декодирование данных из base64
- если требуется использовать аппаратное вычисление хэш-функции ГОСТ Р 34.11-94 (сертифицированная реализация, скорость 60-70 Кб/c), то в options нужно установить опцию useHardwareHash в true. Если данная опция установлена в false, то будет использована быстрая программная реализация хэш-функции ГОСТ Р 34.11-2012
- если требуется сформировать “отсоединенную” (detached) подпись CMS, то нужно установить опцию detached в true, иначе будет сформирована “присоединенная” (attached) подпись
- для того, чтобы включить/не включить пользовательский сертификат в подписанное CMS-сообщение существует опция addUserCertificate
- Установка опции addSignTime в true приведет к тому, что в подписанное CMS-сообщение будет добавлено системное время в качестве подписанного атрибута
Проверка подписи на сервере описана выше.
Шифрование/расшифрование данных и/или файлов в формате CMS
Шифрование данных на клиенте для сервера
Для того, чтобы обеспечить конфиденциальность обмена данными между клиентом и сервером в плагине предусмотрено шифрование/расшифрование данных. Данные шифруются в формате CMS. Для того, чтобы зашифровать данные в формате CMS, требуется сертификат открытого ключа «адресата». При этом расшифровать такое сообщение сможет только владелец закрытого ключа. При шифровании данных для сервера рекомендуется хранить сертификат сервера на Рутокен ЭЦП 2.0 . Этот сертификат может быть записан на устройство при регистрации пользователя на портале. Для этого следует использовать функцию importCertificate, при этом в качестве параметра category следует передать CERT_CATEGORY_OTHER. Для использования в функции cmsEncrypt нужно получить тело сертификата по его дескриптору с помощью функции getCertificate. При этом дескриптор является уникальным и неизменным и может быть сохранен в учетной записи пользователя на сервере при импорте сертификата сервера. Для того, чтобы использовалось аппаратное шифрование по ГОСТ 28147-89, требуется установить опцию useHardwareEncryption в true. В противном случае будет использована быстрая программная реализация ГОСТ 28147-89.
Последовательность вызовов приведена на картинке:
...
recipient.crt — сертификат того, для кого зашифровано сообщение, recipient.key — ключ того, для кого зашифровано сообщение.
Расшифрование данных, полученных с сервера, на клиенте
Для расшифрования данных, полученных с сервера, предназначена функция cmsDecrypt. Так как сервер шифрует для клиента, используя его сертификат, то в качестве keyId должен быть передан дескриптор закрытого ключа клиента, соответствующий открытому ключу в сертификате. Этот дескриптор является уникальным и неизменным и потому может быть сохранен в учетной записи пользователя на сервере. Кроме того, дескриптор ключа пользователя может быть получен явным образом, путем вызова функции getKeyByCertificate.
...
Code Block | ||||
---|---|---|---|---|
| ||||
// вызывается метод Promise. Переменные rutokenHandle и keyHandle должны быть во внешном блоке. .then(function () { var options = {}; return plugin.cmsDecrypt(rutokenHandle, keyHandle, cms, options); }) .then(function(msg) { alert(msg); }, handleError) |
Полезные ссылки
Данные ссылки могут быть полезны разработчикам инфосистем с поддержкой ЭП на базе Рутокен Плагин и opensslOpenSSL:
Пример интеграции Рутокен Плагин в ДБО
Веб-сервис генерации ключей, формирования запросов, управления сертификатами
Документация по использованию утилиты OpenSSL с российскими криптоалгоритмами