Основываясь на полученном опыте интеграции продукта в реальные проекты мне хочется отметить, что нередко разработчики для реализации серверной части предпочитают использовать поддерживающий российские криптоалгоритмы openssl.
В данной статье будет расписана описана типичная схема подобной интеграции, основанная на следующих сценариях использования плагинаПлагина:
- Регистрация на портале (с выдачей сертификата или по имеющемуся сертификату)
- Строгая аутентификация на портале
- Электронная подпись данных и/или файлов в формате CMS
- Шифрование данных и/или файлов в формате CMS
Table of Contents
Данные сценарии предполагают клиент-серверное взаимодействие, написание клиентских скриптов на JavaScript и Данные сценарии предполагают клиент-серверное взаимодействие, написание клиентских скриптов на JavaScript и соответствующих им серверных вызовов opensslOpenSSL.
Общие операции
Операции с устройством
Поиск подключенных устройств
Info | ||
---|---|---|
| ||
Если вы открываете локальную страницу, убедитесь, что в настройках расширения Адаптер Рутокен Плагин включена опция "Разрешить открывать локальные файлы по ссылкам" |
Общие операции
Так как все функции Плагина выполняются в отдельных потоках, не заставляя браузер ждать выполнения операций, его интерфейс постороен на промисах. Промисы - это способ организации асинхронного кода. Универсальный метод добавления обработчиков:Любой клиентский сценарий начинается с поиска подключенных к компьютеру USB-устройств Рутокен.
Code Block | ||
---|---|---|
| ||
var devices = Array();
try
{
devices = plugin.enumerateDevices();
}
catch (error)
{
console.log(error);
} |
...
Получение информации об устройстве
Для определения типа устройства следует использовать функцию getRutokenModelName() репозитория https://github.com/AktivCo/blade-runner.github.io
Параметр функции getDeviceInfo TOKEN_INFO_DEVICE_TYPE – устарел и не поддерживается.
С помощью функции getDeviceInfo можно получить:
- модель устройства
- метку устройства
- серийный номер устройства
- информацию о том, залогинен ли пользователь на устройство
Смена PIN-кода
Code Block | ||||
---|---|---|---|---|
| ||||
var options = {};
try
{
plugin.changePin(deviceId, "12345678", "12345671", options);
}
catch (error)
{
console.log(error);
} |
...
Работа с сертификатами
1. На токене могут храниться 3 категории сертификатов:
- пользовательские (константа в плагине CERT_CATEGORY_USER)
Это сертификаты, связанные с закрытым ключом пользователя. Применяются, например, для подписи CMS/PKCS#7, аутентификации пользователя в протоколе TLS.
Если сертификат импортируется как пользовательский, то при импорте будет произведен поиск в устройстве соответствующего ему закрытого ключа. Если такой ключ будет найден, то сертификат будет «привязан» к этому ключу. Если ключ найден не будет, то вернется ошибка. - корневые (константа в плагине CERT_CATEGORY_CA)
Это сертификаты издателей сертификатов, применяющиеся для проверки подписи под сертификатами. Подобная проверка подписи (построение цепочки доверия) позволяет определить, доверяет ли пользователь подписи другого пользователя. Например, в функции плагина verify есть режим проверки сертификата, на котором было подписано сообщение. При этом используется хранилище корневых сертификатов на токене, созданное импортом корневых сертификатов на токен. - другие (константа в плагине CERT_CATEGORY_OTHER)
Это сертификаты, которые не связаны с закрытым ключом и не являются корневыми.
...
Code Block | ||||
---|---|---|---|---|
| ||||
var certs = Array();
try
{
certs = plugin.enumerateCertificates(deviceId, plugin.CERT_CATEGORY_USER);
}
catch (error)
{
console.log(error);
} |
...
Code Block | ||
---|---|---|
| ||
var certpem;
try
{
certpem = plugin.getCertificate(deviceId, certId);
}
catch (error)
{
console.log(error);
} |
Получится примерно такая строка:
Code Block |
---|
-----BEGIN CERTIFICATE-----
MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G
A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i
MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy
NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD
AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px
HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH
AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw
CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW
detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+
-----END CERTIFICATE----- |
...
| |||
promise.then(onFulfilled, onRejected)
// onFulfilled – функция, которая будет вызвана с результатом при успешном выполнении асинхронной функции.
// onRejected – функция, которая будет вызвана с ошибкой при ошибке выполнения асинхроннной функции. |
Операции с устройством
Поиск подключенных устройств
Любой клиентский сценарий начинается с поиска подключенных к компьютеру USB-устройств Рутокен.
Code Block | ||
---|---|---|
| ||
function handleError(reason) {
if (isNaN(reason.message)) {
alert(reason);
} else {
var errorCodes = plugin.errorCodes;
switch (parseInt(reason.message)) {
case errorCodes.PIN_INCORRECT:
alert("Неверный PIN");
break;
default:
alert("Неизвестная ошибка #" + reason.message);
}
}
}
// ...
plugin.enumerateDevices().then(function(devices) {
if (devices.length > 0) {
// ...
} else {
return Promise.reject("Рутокен не обнаружен");
}
}, handleError) |
При этом вызове возвращается список идентификаторов подключенных устройств. Идентификатор представляет собой число, связанное с номером слота, к которому подключено устройство. При повторном перечислении это число может отличаться для одного и того же устройства.
Стоит заметить, что вызов этот произойдет асинхронно. Первый аргумент метода then, вызывается в случае успешного выполнения Primve enumerateDevices, второй --в случае ошибки. В данном примере был написан обобщенный обработчик ошибок. Его можно расширить и использовать в дальнейшем в своих проектах.
Рутокен Плагин определяет все подключенные к компьютеру USВ-устройства Рутокен ЭЦП. Поэтому следующим шагом следует определить тип устройства.
Получение информации об устройстве
Для определения типа устройства следует использовать функцию getRutokenModelName() репозитория https://github.com/AktivCo/rutoken-model-by-hw-features-js
Параметр функции getDeviceInfo TOKEN_INFO_DEVICE_TYPE – устарел и не поддерживается.
С помощью функции getDeviceInfo можно получить:
- модель устройства
- метку устройства
- серийный номер устройства
- информацию о том, залогинен ли пользователь на устройство
Смена PIN-кода
Code Block | ||||
---|---|---|---|---|
| ||||
// вызывается метод Promise, который должен вернуть Id устройства
.then(function(rutokenHandle){
options={}
return plugin.changePin(rutokenHandle, "12345678", "12345678", options)
})
.then(function() {
alert("PIN изменен успешно");
}, handleError) |
Здесь первым параметром выступает старый PIN-код, а вторым новый PIN-код.
Работа с сертификатами
1. На токене могут храниться 4 категории сертификатов:
- пользовательские (константа в плагине CERT_CATEGORY_USER)
Это сертификаты, связанные с закрытым ключом пользователя. Применяются, например, для подписи CMS/PKCS#7, аутентификации пользователя в протоколе TLS.
Если сертификат импортируется как пользовательский, то при импорте будет произведен поиск в устройстве соответствующего ему закрытого ключа. Если такой ключ будет найден, то сертификат будет «привязан» к этому ключу. Если ключ найден не будет, то вернется ошибка. - корневые (константа в плагине CERT_CATEGORY_CA)
Это сертификаты издателей сертификатов, применяющиеся для проверки подписи под сертификатами. Подобная проверка подписи (построение цепочки доверия) позволяет определить, доверяет ли пользователь подписи другого пользователя. Например, в функции плагина verify есть режим проверки сертификата, на котором было подписано сообщение. При этом используется хранилище корневых сертификатов на токене, созданное импортом корневых сертификатов на токен. - Неуточненная категория сертификатов (CERT_CATEGORY_UNSPEC)
- другие (константа в плагине CERT_CATEGORY_OTHER)
Это сертификаты, которые не связаны с закрытым ключом и не являются корневыми.
2. Для чтения сертификатов, хранящихся на устройстве, не требуется авторизация на устройство.
Code Block | ||||
---|---|---|---|---|
| ||||
// вызывается метод Promise, который должен вернуть Id устройства
.then(function(rutokenHandle) {
return plugin.enumerateCertificates(rutokenHandle, plugin.CERT_CATEGORY_USER);
})
// Вывод первого найденного сертификата на Рутокене
.then(function(certs) {
if (certs.length > 0) {
// ...
} else {
return Promise.reject("Сертификат на Рутокен не обнаружен");
}
}, handleError) |
3. Сертификат можно экспортировать в PEM-формате:
Code Block | ||
---|---|---|
| ||
// вызывается метод Promise, который должен вернуть Id устройства и Id сертификата
.then(function(rutokenHandle, certHandle) {
return plugin.getCertificate(rutokenHandle, certHandle);
})
// Отображение подписанных данных в формате PEM
.then(function(pem) {
alert(pem);
}, handleError) |
Получится примерно такая строка:
Code Block |
---|
-----BEGIN CERTIFICATE-----
MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G
A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i
MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy
NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD
AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px
HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH
AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw
CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW
detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+
-----END CERTIFICATE----- |
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-кода. Следует понимать, что само значение закрытого ключ получено быть не может, так как ключ является неизвлекаемым.
Code Block | ||
---|---|---|
| ||
// вызывается метод Promise, который должен вернуть Id устройства
.then(function(rutokenHandle) {
return plugin.enumerateKeys(rutokenHandle, "");
})
.then(function(keys) {
if (keys.length > 0) {
// ...
} else {
return Promise.reject("Ключи на Рутокене не обнаружены");
}
}, handleError) |
2. Для генерации ключевой пары требуется ввод PIN-кода. При генерации ключа можно задать параметры ключевой пары. Если опция не задана, будут выбраны параметры А для любого из алгоритмов ГОСТ.
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 = {};
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_FORMAT_PLAIN. Используется для подписи незакодированных данных.
- DATA_FORMAT_BASE64 Используется для подписи данных в base64 формате.
- DATA_FORMAT_HASH. Используется для подписи готового хеша. Для корректной работы необходимо выставить опцию detached в true.
2. Для проверки подписи используется метод verify:
Code Block | ||||
---|---|---|---|---|
| ||||
// Проверка подписи
.then(function(certPem, CA, cms) {
var options = {certificates: [certPem], CA: CA};
return plugin.verify(rutokenHandle, cms, options);
}) |
Последние аргумент – опции функции. Их подробный список можно найти в документации, некоторые из них:
- 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) - проверить сертификат пользователя;
Code Block | ||||
---|---|---|---|---|
| ||||
var certpem = "-----BEGIN CERTIFICATE-----
MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G
A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i
MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy
NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD
AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px
HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH
AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw
CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW
detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+
-----END CERTIFICATE-----";
try
{
plugin.importCertificate(deviceId, certpem, plugin.CERT_CATEGORY_USER);
}
catch (error)
{
console.log(error);
} |
6. Вызовом функции deleteCertificate можно удалить сертификат с токена.
Работа с ключевыми парами ГОСТ
...
Code Block | ||
---|---|---|
| ||
var keys = Array();
try
{
plugin.login(deviceId, "12345678");
keys = plugin.enumerateKeys(deviceId, null);
}
catch (error)
{
console.log(error);
} |
...
- ГОСТ Р 34.10-2012 длина закрытого ключа 256 бит:
- "A"
- "B"
- "C"
- "XA"
- "XB"
- ГОСТ Р 34.10-2012 длина закрытого ключа 512 бит
- "A"
- "B"
Code Block | ||||
---|---|---|---|---|
| ||||
var options = {};
var keyId;
try
{
keyId = plugin.generateKeyPair(deviceId, undefined, marker, options);
}
catch (error)
{
console.log(error);
} |
3. С помощью функции deleteKeyPair ключевая пара может быть удалена с токена.
Конфигурирование openssl
См. инструкцию Интеграция ГОСТ 2012 с Рутокен ЭЦП и OpenSSL 1.1.0 или новее, раздел Установка и настройка OpenSSL для работы с rtengine 0.7.x
Теперь перейдем к реализации законченных пользовательских сценариев.
Регистрация на портале
Сертификат выдается при регистрации в системе
- Получаем список подключенных к компьютеру устройств Рутокен ЭЦП 2.0
- Генерируем ключевую пару по ГОСТ Р 34.10-2012 на выбранном Рутокен ЭЦП 2.0
- Cоздаем запрос PKCS#10 на сертификат для сгенерированной ключевой пары
- Отправляем запрос на сервер
- На сервере создаем сертификат, привязываем к аккаунту (сам сертификат или его дескриптор). Следует отметить, что дескрипторы сертификатов, полученные при вызове функции enumerateCertificates, являются уникальными и неизменными
- Отправляем сертификат на клиент
- На клиенте визуализируем полученный сертификат
- Импортируем полученный сертификат в Рутокен ЭЦП 2.0
Последовательность вызовов в клиентском скрипте будет следующей:
Далее запрос отправляется на сервер, где на его основе выдается сертификат.
Для этого на сервере должен быть установлен и правильно сконфигурирован openssl версии от 1.0 и развернут функционал УЦ.
1. Генерация улюча УЦ:
Code Block | ||
---|---|---|
| ||
openssl genpkey -algorithm gost2012_256 -pkeyopt paramset:A -out ca.key |
После этого в файле ca.key будет создан закрытый ключ
2. Создание самоподписанного сертификата УЦ:
Code Block | ||
---|---|---|
| ||
openssl req -utf8 -x509 -key ca.key -out ca.crt |
После ввода необходимой информации об издателе в файле ca.crt будет создан сертификат УЦ.
Полученный от клиента запрос сохраняем в файл user.csr и выдаем на его основе сертификат (без модификации данных из запроса):
Code Block | ||
---|---|---|
| ||
openssl ca -keyfile ca.key -cert ca.crt -in user.csr -out user.crt -batch |
После этого в файле user.crt создается сертификат пользователя в PEM формате. Его следует отправить на клиент.
Дальнейшая последовательность вызовов на клиенте:
Сертификат уже имеется на токене, выдан внешним УЦ
Ключевая пара при этом должна быть создана в формате, совместимом с библиотекой rtPKCS11ECP для Рутокен ЭЦП 2.0 .
- Получаем список подключенных к компьютеру устройств Рутокен ЭЦП 2.0
- Получаем список всех имеющихся пользовательских сертификатов на выбранном Рутокен ЭЦП 2.0
- Визуализируем каждый сертификат
- Пользователь выбирает нужный сертификат
- Сервер формирует начальную последовательность случайных данных (строку salt) и отправляет ее на клиент
- Вызываем на клиенте authenticate. При передаче salt в функцию плагина authenticate данная последовательность дополняется дополнительными случайными данными размером в 32 символа, и происходит подпись итоговой последовательности на выбранном пользователем сертификате в формате CMS attached
- Подпись отправляется на сервер
- На сервере происходит проверка CMS attached подписи с использованием корневого сертификата
- Из CMS attached сообщения извлекается итоговая случайная последовательность, “отсоединяется” salt и происходит сравнение
- Если сравнение успешно, то регистрируем пользователя по сертификату, который содержится в CMS attached сообщении
Последовательность вызовов на клиенте:
Подпись получается в base64-формате. При проверке ее на сервере с помощью openssl подпись следует обрамить заголовками, чтобы сделать из нее PEM. Выглядеть подобная подпись будет примерно так:
Code Block |
---|
-----BEGIN CMS----- MIIDUQYJKoZIhvcNAQcCoIIDQjCCAz4CAQExDDAKBgYqhQMCAgkFADCBygYJKoZI hvcNAQcBoIG8BIG5PCFQSU5QQURGSUxFIFVURjg+PFY+0JLRi9C/0L7Qu9C90LjR gtGMINCw0YPRgtC10L3RgtC40YTQuNC60LDRhtC40Y4/PCE+c2VydmVyLXJhbmRv bS1kYXRhZTI6ZGE6MmM6MDU6MGI6MzY6MjU6MzQ6YzM6NDk6Nzk6Mzk6YmI6MmY6 YzU6Mzc6ZGI6MzA6MTQ6NDQ6ODM6NjY6Njk6NmI6OWY6YTU6MDk6MzQ6YmY6YzQ6 NzY6YzmgggGeMIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQG EwJSVTEPMA0GA1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJr LVRlbGVjb20iMRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1 MTIyMjE2NTEyNVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUD AgIjAQYHKoUDAgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHk IwIdf7zPe2PxHyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4w HAYIKwYBBQUHAwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1Ud EwEB/wQCMAAwCgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5 dn9hrKkNrZsWdetWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+MYG7MIG4AgEB MFkwVDELMAkGA1UEBhMCUlUxDzANBgNVBAcTBk1vc2NvdzEiMCAGA1UEChQZT09P ICJHYXJhbnQtUGFyay1UZWxlY29tIjEQMA4GA1UEAxMHVGVzdCBDQQIBATAKBgYq hQMCAgkFADAKBgYqhQMCAhMFAARAco5PumEfUYVcLMb1cnzETNOuWC8Goda8pdUL W5ASK+tztCwM7wpXgAy+Y6/sLtClO9sh8dKnAaEY2Yavg3altQ== -----END CMS----- |
...
Code Block | ||||
---|---|---|---|---|
| ||||
openssl cms -verify -in sign.cms -inform PEM -CAfile ca.crt -out data.file -certsout user.cr |
Здесь sign.cms — файл, в котором находится подпись, ca.crt — файл с корневыми сертификатами, до одного из которых должна выстроиться цепочка, data.file — файл, в который будет сохранены подписанные данные, user.crt — файл, в который будет сохранен пользовательский сертификат. Именно из data.file нужно извлечь данные отсоединить последние 32 символа и сравнить salt.
Если на сервере нужно получить информацию из сертификата, то парсить его можно так:
Code Block | ||||
---|---|---|---|---|
| ||||
openssl x509 -in cert.pem -noout -text |
...
Code Block | ||||
---|---|---|---|---|
| ||||
openssl x509 -in cert.pem -noout -subject |
Code Block | ||||
---|---|---|---|---|
| ||||
openssl x509 -in cert.pem -noout -issuer |
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 |
Строгая аутентификация на портале
Общая схема аутентификации, используемая в Рутокен Плагин, выглядит следующим образом:
- сервер формирует начальную последовательность случайных данных (строку salt) и отправляет ее на клиент
- при передаче salt в функцию плагина authenticate данная последовательность дополняется случайными данными размером в 32 символа, и происходит подпись итоговой последовательности на выбранном пользователем сертификате в формате CMS attached
- подпись отправляется на сервер
- на сервере происходит проверка подписи
- из CMS attached сообщения извлекается итоговая случайная последовательность, “отсоединяется” salt и происходит сравнение
- в случае успешной проверки пользователь аутентифицируется на основе сертификата, извлеченного из сообщения CMS
Реализация данной схемы ничем принципиально не отличается от «Регистрация, сертификат уже имеется, выдан внешним УЦ».
Электронная подпись данных и/или файлов в формате 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.
Последовательность вызовов приведена на картинке:
и неизменным и может быть сохранен в учетной записи пользователя на сервере при импорте сертификата сервера. Для того, чтобы использовалось аппаратное шифрование по ГОСТ 28147-89, требуется установить опцию useHardwareEncryption в true. В противном случае будет использована быстрая программная реализация ГОСТ 28147-89.
Последовательность вызовов приведена на картинке:
Code Block | ||||
---|---|---|---|---|
| ||||
var data = "some data"
var recipientsCertsInPem = [
String.raw`-----BEGIN CERTIFICATE-----
MIICEjCCAb+gAwIBAgIJAL+E0An4CJgVMAoGCCqFAwcBAQMCMHkxCzAJBgNVBAYT
AlJVMQ8wDQYDVQQIDAZSdXNzaWExDzANBgNVBAcMBk1vc2NvdzEXMBUGA1UECgwO
WkFPIEFrdGl2LVNvZnQxEDAOBgNVBAsMB1J1dG9rZW4xHTAbBgNVBAMMFFJ1dG9r
ZW4gVEVTVCBDQSBHT1NUMB4XDTIwMTAxOTE3MjIyN1oXDTIxMTAxOTE3MjIyN1ow
MjEMMAoGA1UEAwwDbWVtMQswCQYDVQQGEwJSVTEVMBMGA1UECAwM0JzQvtGB0LrQ
stCwMGYwHwYIKoUDBwEBAQEwEwYHKoUDAgIjAQYIKoUDBwEBAgIDQwAEQIyKtSP4
WYjewCNr52fNMYDVwFPIwWgWdOi+DpJ47pBn4g6rILdZfvkAfemMs7a74is+mPyg
401mjbZPhDXCRt2jajBoMAsGA1UdDwQEAwIGwDATBgNVHSUEDDAKBggrBgEFBQcD
BDATBgNVHSAEDDAKMAgGBiqFA2RxATAvBgUqhQNkbwQmDCTQodCa0JfQmCAi0KDR
g9GC0L7QutC10L0g0K3QptCfIDIuMCIwCgYIKoUDBwEBAwIDQQCJvbdPKR9QO4zP
Xs4SK/dhzNdNzYmyCOoi7qUBYc4laKwUnhV88ENR5+VDee05w+JMIIJFuelg/QN3
W/W6SC2v
-----END CERTIFICATE-----`
]
// вызывается метод Promise, который должен вернуть Id устройства
.then(function (rutokenHandle) {
| ||||
Code Block | ||||
| ||||
try { var recipientCert = plugin.getCertificate(deviceId, certRecId); } catch (error) { console.log(error); } var options = {}; options.useHardwareEncryption = true; var cms; try { cms = return plugin.cmsEncrypt(deviceIdrutokenHandle, certSenderId"", recipientCertrecipientsCertsInPem, data, options); } catch (error) .then(function(msg) { console.log(error alert(msg); }, handleError) |
Перед расшифрованием сообщение нужно обрамить PEM-заголовками "-----BEGIN PKCS7-----" и "-----END PKCS7-----":
Code Block | ||||
---|---|---|---|---|
| ||||
openssl cms -decrypt -binary -in message.cms -inform PEM -recip respondent.cer -inkey recipient.key -out drecipient.crt |
recipient.crt — сертификат того, для кого зашифровано сообщение, recipient.key — ключ того, для кого зашифровано сообщение.
Расшифрование данных, полученных с сервера, на клиенте
Для расшифрования данных, полученных с сервера, предназначена функция cmsDecrypt. Так как сервер шифрует для клиента, используя его сертификат, то в качестве keyId должен быть передан дескриптор закрытого ключа клиента, соответствующий открытому ключу в сертификате. Этот дескриптор является уникальным и неизменным и потому может быть сохранен в учетной записи пользователя на сервере. Кроме того, дескриптор ключа пользователя может быть получен явным образом, путем вызова функции getKeyByCertificate.
Code Block | ||||
---|---|---|---|---|
| ||||
openssl cms -encrypt -binary -gost28147-paramset_a-cfb -in data.file -out message.enc -outform PEM user.crt |
...
Code Block | ||||
---|---|---|---|---|
| ||||
// вызывается метод Promise. Переменные rutokenHandle и keyHandle должны быть во внешном блоке. .then(function () { var data; var options = {}; try { data =return plugin.cmsDecrypt(deviceIdrutokenHandle, keyIdkeyHandle, cms, options); } catch (error) .then(function(msg) { console.logalert(errormsg); }, handleError) |
Полезные ссылки
Данные ссылки могут быть полезны разработчикам инфосистем с поддержкой ЭП на базе Рутокен Плагин и opensslOpenSSL:
Демосистема
Пример интеграции Рутокен Плагин в ДБО
WEBВеб-сервис генерации ключей, формирования запросов, управления сертификатами, формирования шаблонов запросов на сертификаты
Документация по использованию утилиты openssl OpenSSL с российскими крипталгоритмамикриптоалгоритмами