Добрый день!

Итак, мы разобрались, каким образом будет происходить аутентификация пользователей в системе, а так же создали свой локальный удостоверяющий центр с корневым приватным ключом и корневым сертификатом. Теперь пора перейти к работе непосредственно с Рутокеном. Напомню, что работать мы будем с Рутокеном из клиентской части приложения через Рутокен плагин.

Надеюсь, что драйвера для Рутокена и Рутокен плагин уже установлены на вашем компьютере. Если нет, то предлагаю еще раз ознакомиться с первой частью статьи.

Общая документация, как работать с Рутокен плагин находится по ссылкам:
Встраивание Рутокен ЭЦП 2.0 через Рутокен Плагин
Руководство Разработчика Версия 4.4.1

Если вы начнете читать документацию, то у вас может возникнуть вопрос (по крайней мере так было у меня). Что за объект такой plugin? Где его взять, чтобы потом применять к нему всякого рода функции из документации. Разбираясь с данной проблемой, я совершенно случайно попал на эту страницу

Там есть пример кода, как раз из которого у меня и удалось получить этот самый объект.
Тут приведу код, который тупо надо скопировать к себе, после чего вы сможете работать с документацией:

Открыть код
let rutoken = (function (my) {

    let loadCallbacks = [];
    let pluginMimeType = "application/x-rutoken-pki";
    let extension = window["C3B7563B-BF85-45B7-88FC-7CFF1BD3C2DB"];

    function isFunction(obj) {
        return !!(obj && obj.call && obj.apply);
    }

    function proxyMember(target, member) {
        if (isFunction(target[member])) {
            return function () {
                return target[member].apply(target, arguments);
            };
        } else {
            return target[member];
        }
    }

    function returnPromise(promise) {
        return function () {
            return promise;
        };
    }

    function initialize() {
        my.ready = Promise.resolve(true);
        my.isExtensionInstalled = returnPromise(Promise.resolve(false));
        my.isPluginInstalled = returnPromise(Promise.resolve(true));
        my.loadPlugin = loadPlugin;
        window.rutokenLoaded = onPluginLoaded;
    }

    function initializeExtension() {
        let readyPromise = extension.initialize().then(function () {
            return extension.isPluginInstalled();
        }).then(function (result) {
            my.isExtensionInstalled = returnPromise(Promise.resolve(true));
            my.isPluginInstalled = proxyMember(extension, "isPluginInstalled");

            if (result) {
                pluginMimeType = "application/x-rutoken-plugin";
                my.loadPlugin = loadChromePlugin;
            }

            return true;
        });

        my.ready = readyPromise;
    }

    function initializeWithoutPlugin() {
        my.ready = Promise.resolve(true);
        my.isExtensionInstalled = returnPromise(Promise.resolve(false));
        my.isPluginInstalled = returnPromise(Promise.resolve(false));
    }

    function loadPlugin() {
        let obj = document.createElement("object");
        obj.style.setProperty("visibility", "hidden", "important");
        obj.style.setProperty("width", "0px", "important");
        obj.style.setProperty("height", "0px", "important");
        obj.style.setProperty("margin", "0px", "important");
        obj.style.setProperty("padding", "0px", "important");
        obj.style.setProperty("border-style", "none", "important");
        obj.style.setProperty("border-width", "0px", "important");
        obj.style.setProperty("max-width", "0px", "important");
        obj.style.setProperty("max-height", "0px", "important");

        // onload callback must be set before type attribute in IE earlier than 11.
        obj.innerHTML = "<param name='onload' value='rutokenLoaded'/>";
        // Just after setting type attribute before function returns promise
        // FireBreath uses onload callback to execute it with a small delay.
        // So it must be valid, but it will be called a little bit later.
        // In other browsers plugin will be initialized only after appending
        // an element to the document.
        obj.setAttribute("type", pluginMimeType);

        document.body.appendChild(obj);

        let promise = new Promise(function (resolve, reject) {
            loadCallbacks.push(resolve);
        });

        return promise;
    }

    function loadChromePlugin() {
        return extension.loadPlugin().then(function (plugin) {
            return resolveObject(plugin);
        }).then(function (resolvedPlugin) {
            resolvedPlugin.wrapWithOldInterface = wrapNewPluginWithOldInterface;
            return resolvedPlugin;
        });
    }

    function onPluginLoaded(plugin, error) {
        wrapOldPluginWithNewInterface(plugin).then(function (wrappedPlugin) {
            if (loadCallbacks.length == 0) {
                throw "Internal error";
            }

            let callback = loadCallbacks.shift();
            callback(wrappedPlugin);
        });
    }

    function resolveObject(obj) {
        let resolvedObject = {};
        let promises = [];

        for (var m in obj) {
            (function (m) {
                if (isFunction(obj[m].then)) {
                    promises.push(obj[m].then(function (result) {
                        return resolveObject(result).then(function (resolvedProperty) {
                            if (isFunction(resolvedProperty)) {
                                resolvedObject[m] = proxyMember(obj, m);
                            } else {
                                resolvedObject[m] = resolvedProperty;
                            }
                        });
                    }));
                } else {
                    resolvedObject[m] = obj[m];
                }
            })(m);
        }

        if (promises.length == 0) {
            return new Promise(function (resolve) {
                resolve(obj);
            });
        } else {
            return Promise.all(promises).then(function () {
                return resolvedObject;
            });
        }
    }

    function wrapNewPluginWithOldInterface() {
        let wrappedPlugin = {};

        for (var m in this) {
            if (isFunction(this[m])) {
                wrappedPlugin[m] = (function (plugin, member) {
                    return function () {
                        var successCallback = arguments[arguments.length - 2];
                        var errorCallback = arguments[arguments.length - 1];
                        var args = Array.prototype.slice.call(arguments, 0, -2);
                        return member.apply(plugin, args).then(function (result) {
                            successCallback(result);
                        }, function (error) {
                            errorCallback(error.message);
                        });
                    };
                })(this, this[m]);
            } else {
                wrappedPlugin[m] = this[m];
            }
        }

        return new Promise(function (resolve) {
            resolve(wrappedPlugin);
        });
    }

    function wrapOldPluginWithOldInterface() {
        let unwrappedPlugin = {originalObject: this.originalObject};

        for (let m in this.originalObject) {
            unwrappedPlugin[m] = proxyMember(this.originalObject, m);
        }

        return new Promise(function (resolve) {
            resolve(unwrappedPlugin);
        });
    }

    function wrapOldPluginWithNewInterface(plugin) {
        let wrappedPlugin = {
            originalObject: plugin,
            wrapWithOldInterface: wrapOldPluginWithOldInterface
        };

        for (let m in plugin) {
            if (isFunction(plugin[m])) {
                wrappedPlugin[m] = (function (plugin, member) {
                    return function () {
                        let args = Array.prototype.slice.call(arguments);
                        return new Promise(function (resolve, reject) {
                            args.push(resolve, reject);
                            member.apply(plugin, args);
                        });
                    };
                })(plugin, plugin[m]);
            } else {
                wrappedPlugin[m] = plugin[m];
            }
        }

        return new Promise(function (resolve) {
            resolve(wrappedPlugin);
        });
    }

    if (extension) {
        initializeExtension();
    } else if (navigator.mimeTypes && navigator.mimeTypes[pluginMimeType]) {
        initialize();
    } else {
        try {
            let plugin = new ActiveXObject("Aktiv.CryptoPlugin");
            initialize();
        } catch (e) {
            initializeWithoutPlugin();
        }
    }

    return my;
}({}));
rutoken.ready
    // Проверка установки расширение 'Адаптера Рутокен Плагина' в Google Chrome
    .then(function () {
        if (window.chrome || typeof InstallTrigger !== 'undefined') {
            return rutoken.isExtensionInstalled();
        } else {
            console.log("расширение 'Адаптер Рутокен Плагина' не найдено. Установите адаптер рутокена в браузер");
            return Promise.resolve(true);
        }
    })
    // Проверка установки Рутокен Плагина
    .then(function (result) {
        if (result) {
            console.log("расширение 'Адаптер Рутокен Плагина' найдено");
            return rutoken.isPluginInstalled();
        } else {
            return Promise.reject("Не удаётся найти расширение 'Адаптер Рутокен Плагина'");
        }
    })
    // Загрузка плагина
    .then(function (result) {
        if (result) {
            console.log("Рутокен плагин найден");
            return rutoken.loadPlugin();
        } else {
            return Promise.reject("Не удаётся найти Плагин");
        }
    })
    //Можно начинать работать с плагином
    .then(function (plugin_) {
        plugin = plugin_;
        if (!plugin) {
            console.log("Не удаётся загрузить Рутокен Плагин");
            return Promise.reject("Не удаётся загрузить Плагин");
        } else {
            console.log("Рутокен плагин загружен успешно");
            return plugin.enumerateDevices()
        }
    })


Чтобы успешно использовать Рутокен плагин и вообще понимать, как работает этот код, нужно разобраться, что такое Promise. Есть отличная статья на эту тему, в которой имеется все что нужно для понимания Promise.

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

Для написания своих клиентский приложения я использую фреймворк Ext Js, но это абсолютно не важно. Уверен, что понять весь этот код можно и без знания Ext Js.

Перейдем к реализации. Для начала нужно получить соединение с Рутокеном. Для этого был создан класс, который будет за это отвечать:

Открыть реализацию Rutoken.rutoken.ConnectToDevice
Ext.define('Rutoken.rutoken.ConnectToDevice', {

    connectToDevice: function (rutoken) {
        rutoken = rutoken.getRutoken();
        return rutoken.ready
            // Проверка установки расширение 'Адаптера Рутокен Плагина' в Google Chrome
            .then(function () {
                if (window.chrome || typeof InstallTrigger !== 'undefined') {
                    return rutoken.isExtensionInstalled();
                } else {
                    console.log("расширение 'Адаптер Рутокен Плагина' не найдено. Установите адаптер рутокена в браузер");
                    return Promise.resolve(true);
                }
            })
            // Проверка установки Рутокен Плагина
            .then(function (result) {
                if (result) {
                    console.log("расширение 'Адаптер Рутокен Плагина' найдено");
                    return rutoken.isPluginInstalled();
                } else {
                    return Promise.reject("Не удаётся найти расширение 'Адаптер Рутокен Плагина'");
                }
            })
            // Загрузка плагина
            .then(function (result) {
                if (result) {
                    console.log("Рутокен плагин найден");
                    return rutoken.loadPlugin();
                } else {
                    return Promise.reject("Не удаётся найти Плагин");
                }
            })
            //Можно начинать работать с плагином
            .then(function (plugin_) {
                plugin = plugin_;
                if (!plugin) {
                    console.log("Не удаётся загрузить Рутокен Плагин");
                    return Promise.reject("Не удаётся загрузить Плагин");
                } else {
                    console.log("Рутокен плагин загружен успешно");
                    return plugin.enumerateDevices()
                }
            })
            //Попытка обнаружить рутокен в компьютере
            .then(function (devices) {
                if (devices.length > 0) {
                    console.log("Рутокен обнаружен");
                    return Promise.resolve(devices[0]);
                } else {
                    console.log("Рутокен НЕ обнаружен");
                    return Promise.reject("Рутокен не обнаружен");
                }
            })
            //Получение информации об рутокене, который подключен к компьютеру
            .then(function (device_) {
                device = device_;
                return plugin.getDeviceInfo(device, plugin.TOKEN_INFO_IS_LOGGED_IN);
            })
            // Логин на первый токен в списке устройств PIN-кодом по умолчанию
            .then(function (isLoggedIn) {
                if (isLoggedIn) {
                    return Promise.resolve();
                } else {
                    return plugin.login(device, "12345678");
                }
            })
            //Вернуть список, состоящий из плагина и номера первого рутокена,
            //который подключен к компьютеру
            .then(function () {
                return [plugin, device];
            })
    }
});


Для работы с этим классом просто нужно создать его объект, а потом вызвать функцию connectToDevice. В результате эта функция возвращает список, состоящий из плагина и номера первого Рутокена. Функция connectToDevice принимает параметр rutoken. Этот объект получается копированием части содержимого кода, приведенного в начале статьи:

Открыть реализацию Rutoken.rutoken.RutokenInit
Ext.define("Rutoken.rutoken.RutokenInit", {

    config: {
        rutoken: undefined,
    },

    constructor: function(){
        let rutoken = (function (my) {

            let loadCallbacks = [];
            let pluginMimeType = "application/x-rutoken-pki";
            let extension = window["C3B7563B-BF85-45B7-88FC-7CFF1BD3C2DB"];

            function isFunction(obj) {
                return !!(obj && obj.call && obj.apply);
            }

            function proxyMember(target, member) {
                if (isFunction(target[member])) {
                    return function () {
                        return target[member].apply(target, arguments);
                    };
                } else {
                    return target[member];
                }
            }

            function returnPromise(promise) {
                return function () {
                    return promise;
                };
            }

            function initialize() {
                my.ready = Promise.resolve(true);
                my.isExtensionInstalled = returnPromise(Promise.resolve(false));
                my.isPluginInstalled = returnPromise(Promise.resolve(true));
                my.loadPlugin = loadPlugin;
                window.rutokenLoaded = onPluginLoaded;
            }

            function initializeExtension() {
                let readyPromise = extension.initialize().then(function () {
                    return extension.isPluginInstalled();
                }).then(function (result) {
                    my.isExtensionInstalled = returnPromise(Promise.resolve(true));
                    my.isPluginInstalled = proxyMember(extension, "isPluginInstalled");

                    if (result) {
                        pluginMimeType = "application/x-rutoken-plugin";
                        my.loadPlugin = loadChromePlugin;
                    }

                    return true;
                });

                my.ready = readyPromise;
            }

            function initializeWithoutPlugin() {
                my.ready = Promise.resolve(true);
                my.isExtensionInstalled = returnPromise(Promise.resolve(false));
                my.isPluginInstalled = returnPromise(Promise.resolve(false));
            }

            function loadPlugin() {
                let obj = document.createElement("object");
                obj.style.setProperty("visibility", "hidden", "important");
                obj.style.setProperty("width", "0px", "important");
                obj.style.setProperty("height", "0px", "important");
                obj.style.setProperty("margin", "0px", "important");
                obj.style.setProperty("padding", "0px", "important");
                obj.style.setProperty("border-style", "none", "important");
                obj.style.setProperty("border-width", "0px", "important");
                obj.style.setProperty("max-width", "0px", "important");
                obj.style.setProperty("max-height", "0px", "important");

                // onload callback must be set before type attribute in IE earlier than 11.
                obj.innerHTML = "<param name='onload' value='rutokenLoaded'/>";
                // Just after setting type attribute before function returns promise
                // FireBreath uses onload callback to execute it with a small delay.
                // So it must be valid, but it will be called a little bit later.
                // In other browsers plugin will be initialized only after appending
                // an element to the document.
                obj.setAttribute("type", pluginMimeType);

                document.body.appendChild(obj);

                let promise = new Promise(function (resolve, reject) {
                    loadCallbacks.push(resolve);
                });

                return promise;
            }

            function loadChromePlugin() {
                return extension.loadPlugin().then(function (plugin) {
                    return resolveObject(plugin);
                }).then(function (resolvedPlugin) {
                    resolvedPlugin.wrapWithOldInterface = wrapNewPluginWithOldInterface;
                    return resolvedPlugin;
                });
            }

            function onPluginLoaded(plugin, error) {
                wrapOldPluginWithNewInterface(plugin).then(function (wrappedPlugin) {
                    if (loadCallbacks.length == 0) {
                        throw "Internal error";
                    }

                    let callback = loadCallbacks.shift();
                    callback(wrappedPlugin);
                });
            }

            function resolveObject(obj) {
                let resolvedObject = {};
                let promises = [];

                for (var m in obj) {
                    (function (m) {
                        if (isFunction(obj[m].then)) {
                            promises.push(obj[m].then(function (result) {
                                return resolveObject(result).then(function (resolvedProperty) {
                                    if (isFunction(resolvedProperty)) {
                                        resolvedObject[m] = proxyMember(obj, m);
                                    } else {
                                        resolvedObject[m] = resolvedProperty;
                                    }
                                });
                            }));
                        } else {
                            resolvedObject[m] = obj[m];
                        }
                    })(m);
                }

                if (promises.length == 0) {
                    return new Promise(function (resolve) {
                        resolve(obj);
                    });
                } else {
                    return Promise.all(promises).then(function () {
                        return resolvedObject;
                    });
                }
            }

            function wrapNewPluginWithOldInterface() {
                let wrappedPlugin = {};

                for (var m in this) {
                    if (isFunction(this[m])) {
                        wrappedPlugin[m] = (function (plugin, member) {
                            return function () {
                                var successCallback = arguments[arguments.length - 2];
                                var errorCallback = arguments[arguments.length - 1];
                                var args = Array.prototype.slice.call(arguments, 0, -2);
                                return member.apply(plugin, args).then(function (result) {
                                    successCallback(result);
                                }, function (error) {
                                    errorCallback(error.message);
                                });
                            };
                        })(this, this[m]);
                    } else {
                        wrappedPlugin[m] = this[m];
                    }
                }

                return new Promise(function (resolve) {
                    resolve(wrappedPlugin);
                });
            }

            function wrapOldPluginWithOldInterface() {
                let unwrappedPlugin = {originalObject: this.originalObject};

                for (let m in this.originalObject) {
                    unwrappedPlugin[m] = proxyMember(this.originalObject, m);
                }

                return new Promise(function (resolve) {
                    resolve(unwrappedPlugin);
                });
            }

            function wrapOldPluginWithNewInterface(plugin) {
                let wrappedPlugin = {
                    originalObject: plugin,
                    wrapWithOldInterface: wrapOldPluginWithOldInterface
                };

                for (let m in plugin) {
                    if (isFunction(plugin[m])) {
                        wrappedPlugin[m] = (function (plugin, member) {
                            return function () {
                                let args = Array.prototype.slice.call(arguments);
                                return new Promise(function (resolve, reject) {
                                    args.push(resolve, reject);
                                    member.apply(plugin, args);
                                });
                            };
                        })(plugin, plugin[m]);
                    } else {
                        wrappedPlugin[m] = plugin[m];
                    }
                }

                return new Promise(function (resolve) {
                    resolve(wrappedPlugin);
                });
            }

            if (extension) {
                initializeExtension();
            } else if (navigator.mimeTypes && navigator.mimeTypes[pluginMimeType]) {
                initialize();
            } else {
                try {
                    let plugin = new ActiveXObject("Aktiv.CryptoPlugin");
                    initialize();
                } catch (e) {
                    initializeWithoutPlugin();
                }
            }

            return my;
        }({}));
        this.setRutoken(rutoken);
    }

});


Таким образом, нужно создать объект данного класса и подставить его в функцию connectToDevice класса Rutoken.rutoken.ConnectToDevice, код которого был приведен ранее. Это действие я делаю в следующем классе, который отвечает за регистрацию пользователя:

Открыть реализацию Rutoken.rutoken.RutokenRegistration
Ext.define('Rutoken.rutoken.RutokenRegistration', {

    registration: function (rutoken, device, scope) {
        return device.connectToDevice(rutoken)
            //Получаем исходные данные для проведения регистрации
            .then(function (pluginWithDeviceIndex) {
                values = scope.lookupReference('regForm').getValues();
                plugin = pluginWithDeviceIndex[0];
                deviceIndex = pluginWithDeviceIndex[1];
                host = 'http://localhost:8080/';
                createCrtDataPrefix = 'createCrtData';
                registrationPrefix = 'registration';
            })
            //Получаем список сертификатов с рутокена
            .then(function () {
                return plugin.enumerateCertificates(deviceIndex, plugin.CERT_CATEGORY_USER);
            })
            //Проверяем существование ключевой пары на рутокене
            .then(function (crts) {
                if (crts.length > 0) {
                    throw "Certificate already exist on rutoken";
                }
            })
            //Создаем ключевую пару на рутокене по GOST3410_2012_256
            .then(function () {
                let option = {
                    "publicKeyAlgorithm": plugin.PUBLIC_KEY_ALGORITHM_GOST3410_2012_256
                };
                return plugin.generateKeyPair(deviceIndex, undefined, "", option);
            })
            //Формируем запрос createPkcs10 на выдачу сертификата
            .then(function (keyPair) {
                let subject = [
                    {
                        rdn: "countryName",
                        value: "RU"
                    }
                    , {
                        rdn: "stateOrProvinceName",
                        value: "Russia"
                    }
                    , {
                        rdn: "localityName",
                        value: "Saint-Petersburg"
                    }
                    , {
                        rdn: "streetAddress",
                        value: "street"
                    }
                    , {
                        rdn: "organizationName",
                        value: "Eurica"
                    }
                    , {
                        rdn: "organizationalUnitName",
                        value: "Rutoken"
                    }
                    , {
                        rdn: "title",
                        value: "инженер"
                    }
                    , {
                        rdn: "commonName",
                        value: `${values.name} ${values.surname}`
                    }
                    , {
                        rdn: "postalAddress",
                        value: "postal address"
                    }
                    , {
                        rdn: "pseudonym",
                        value: "инженер123"
                    }
                    , {
                        rdn: "surname",
                        value: `${values.surname}`
                    }
                    , {
                        rdn: "givenName",
                        value: "given name"
                    }
                    , {
                        rdn: "emailAddress",
                        value: `${values.email}`
                    }
                ];
                let keyUsageVal = [
                    "digitalSignature"
                    , "nonRepudiation"
                    , "keyEncipherment"
                    , "dataEncipherment"
                    , "keyAgreement"
                    , "keyCertSign"
                    , "cRLSign"
                    , "encipherOnly"
                    , "decipherOnly"
                ];
                let extKeyUsageVal = [
                    "emailProtection"
                    , "clientAuth"
                    , "serverAuth"
                    , "codeSigning"
                    , "timeStamping"
                    , "msCodeInd"
                    , "msCodeCom"
                    , "msCTLSign"
                    , "1.3.6.1.5.5.7.3.9" // OSCP
                    , "1.2.643.2.2.34.6" // CryptoPro RA user

                ];
                let certificatePolicies = [
                    "1.2.643.100.113.1", // КС1
                    "1.2.643.100.113.2", // КС2
                    "1.2.643.100.113.3", // КС3
                    "1.2.643.100.113.4", // КВ1
                    "1.2.643.100.113.5", // КВ2
                    "1.2.643.100.113.6"  // КА1
                ];
                let extensions = {
                    "keyUsage": keyUsageVal,
                    "extKeyUsage": extKeyUsageVal,
                    "certificatePolicies": certificatePolicies
                };
                let options = {
                    "subjectSignTool": 'СКЗИ "РУТОКЕН ЭЦП"',
                    "hashAlgorithm": plugin.HASH_TYPE_GOST3411_12_256,
                    "customExtensions": [
                        {
                            oid: "1.3.6.1.4.1.311.21.7",
                            value: "MA0GCCqFAwICLgAIAgEB",
                            criticality: false
                        }
                    ],
                };
                return plugin.createPkcs10(deviceIndex, keyPair, subject, extensions, options);
            })
            //создаем объект pkcs10Message для отправки на сервер
            .then(function (pkcs10Request) {
                let pkcs10Message = Ext.create('Rutoken.model.RuTokenPkcs10Message');
                pkcs10Message.requestPkcs10Message = pkcs10Request;
                return pkcs10Message;
            })
            //отправляем pkcs10Message на сервер для формирования сертификата
            .then(function (pkcs10Message) {
                return new Promise(function (resolve, reject) {
                    Ext.Ajax.request({
                        method: 'POST',
                        jsonData: Ext.encode(pkcs10Message),
                        url: `${host}${createCrtDataPrefix}`,
                        scope: this,
                        headers: {
                            'accept': 'application/json',
                        },
                        success: function (response) {
                            let crt = Ext.decode(response.responseText);
                            plugin.importCertificate(deviceIndex, crt.responseCertificateMessage, plugin.CERT_CATEGORY_USER);
                            console.log('Certificate was saved successful');
                            resolve(crt);
                        },
                        failures: function (response) {
                            console.log('Failure Pkcs10 request');
                            reject(response.status);
                        }
                    });
                });
            });
    }
});


В качестве параметров функция registration получает объекты классов Rutoken.rutoken.RutokenInit (rutoken), Rutoken.rutoken.ConnectToDevice (device), третьим параметром передается scope на ViewController, чтобы у меня была возможность работать с данными с формы (рассмотрение данной части выходит за рамки статьи).

Считаю, что код имеет достаточное количество комментариев, чтобы можно было понять, как он работает. Обращу внимание только на некоторые моменты.

Во-первых, обратите внимание на часть кода помеченную комментарием «Формируем запрос createPkcs10 на выдачу сертификата». Рассмотрим переменную subject. Она представляет собой ассоциативный массив, который содержит в себе данные пользователя. Эти данные потом будут отражаться в самом сертификате. Дело в том, что по умолчанию можно менять только значения этого массива, но нельзя добавлять новые или удалять старые элементы этого массива. Обратите на это внимание, иначе ваша функция для создания запроса работать не будет.

Во-вторых, хочу обратить внимание на то, что алгоритм, с помощью которого формируется ключевая пара (в моем случае PUBLIC_KEY_ALGORITHM_GOST3410_2012_256), должен соответствовать алгоритму хеширования в переменной options (в моем случае это «hashAlgorithm»: plugin.HASH_TYPE_GOST3411_12_256). Если будете использовать другой алгоритм создания ключевой пары, то алгоритм хеширования должен быть соответствующий. В противном случае ваша функция для создания запроса также работать не будет.

После формирования запроса на создание сертификата, происходит отправка этого запроса на сервер, и в ответ приходит сам сертификат, который успешно импортируется на Рутокен.

Отлично! Мы произвели регистрацию пользователя в системе. Теперь мы имеем на своем Рутокене сертификат с неэкспортируемой ключевой парой, а также удостоверяющий центр знает о сертификате, который записан на Рутокене, так как он сам его выдал с помощью первой команды, описанной в данной статье.

Перейдем к авторизации пользователя. Алгоритм процесса авторизации описан тут.

Для программного описания процесса авторизации мной был создан класс Rutoken.rutoken.RutokenAuthorization:

Открыть реализацию Rutoken.rutoken.RutokenAuthorization

Ext.define('Rutoken.rutoken.RutokenAuthorization', {

    reference: "ruTokenAuthorization",

    authorization: function (rutoken, device, scope) {
        return device.connectToDevice(rutoken)
            //Получаем исходные данные для проведения аутентификации
            .then(function (pluginWithDeviceIndex) {
                plugin = pluginWithDeviceIndex[0];
                deviceIndex = pluginWithDeviceIndex[1];
                values = scope.lookupReference('authForm').getValues();
                host = 'http://localhost:8080/';
                prefixSalt = 'get-authentication-salt';
                prefixAuthentication = 'authentication';
                prefixLogin = 'login';
            })
            //Делаем запрос на аутентификацию на сервер и получаем случайную строку salt
            .then(function () {
                return new Promise(function (resolve, reject) {
                    Ext.Ajax.request({
                        method: 'POST',
                        jsonData: "",
                        url: `${host}${prefixSalt}`,
                        scope: this,
                        headers: {
                            'accept': 'application/json',
                        },
                        success: function (response) {
                            console.log(`Success ${host}${prefixSalt} request`);
                            resolve(Ext.decode(response.responseText));
                        },
                        failures: function (response) {
                            console.log(`Failure ${host}${prefixSalt} request`);
                            reject(response.status);
                        }
                    });
                });
            })
            .then(function (responseData) {
                salt = responseData;
            })
            //Получаем список сертификатов с рутокена
            .then(function () {
                return plugin.enumerateCertificates(deviceIndex, plugin.CERT_CATEGORY_USER);
            })
            //Формируем запрос на аутентификацию. Для этого используем только первый сертификат.
            //На устройстве должен быть только один сертификат привязанный к ключевой паре.
            .then(function (crts) {
                return plugin.authenticate(deviceIndex, crts[0], salt.salt);
            })
            //Корректируем запрос на аутентификацию для правильного понимания запроса сервером
            .then(function (auth) {
                auth = "-----BEGIN CMS-----\n" + auth + "-----END CMS-----";
                return auth;
            })
            //Формирум объект authenticateMessage для отправки его на сервер
            .then(function (auth) {
                let authenticateMessage = Ext.create("Rutoken.model.RuTokenAuthenticateMessage");
                authenticateMessage.authenticateMessage = auth;
                return authenticateMessage;
            })
            //Отправляем запрос authenticateMessage на сервер
            .then(function (authenticateMessage) {
                return new Promise(function (resolve, reject) {
                    Ext.Ajax.request({
                        method: 'POST',
                        jsonData: Ext.encode(authenticateMessage),
                        url: `${host}${prefixAuthentication}`,
                        scope: this,
                        headers: {
                            'accept': 'application/json',
                        },
                        success: function (response) {
                            console.log(`Success ${host}${prefixAuthentication} request`);
                            resolve(Ext.decode(response.responseText).authorizationSuccess);
                        },
                        failures: function (response) {
                            console.log(`Failure ${host}${prefixAuthentication} request`);
                            reject(response.status);
                        }
                    });
                });
            });
    },
});


В результате мы получим объект authorizationSuccess с логическим значением, которое говорит о том, что прошла ли наша авторизация успешно или нет.

В заключение приведу полезную ссылку, которая поможет вам протестировать работоспособность функций Рутокен плагина.

Надеюсь, этот цикл статей был полезен для вас, желаю удачи и спасибо за внимание!