Русский
Русский
English
Статистика
Реклама

Рутокен

Опыт применения технологии Рутокен для регистрации и авторизации пользователей в системе (часть 2)

15.06.2020 14:21:28 | Автор: admin
Добрый день! Продолжим разбираться с данной темой (с предыдущей частью можно ознакомиться по ссылке: habr.com/ru/post/506450).

Сегодня перейдем к практической части. Начнем с настройки своего удостоверяющего центра на основе полноценной криптографической библиотеки с открытым исходным кодом openSSL. Данный алгоритм был проверен с использованием windows 7.

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

Алгоритм действий следующий:

  1. Скачиваем установочный дистрибутив openssl-1.1.1g.
    У openSSL имеются различные версии. В документации к Рутокену было сказано, что необходима версия openSSL версии 1.1.0 или новее. Я использовал версию openssl-1.1.1g. Можно скачать openSSL с официального сайта, но для более простой установки необходимо найти установочный файл для windows в сети. Я это сделал за вас: slproweb.com/products/Win32OpenSSL.html
    Необходимо пролистать вниз страницы и скачать Win64 OpenSSL v1.1.1g EXE 63MB Installer.
  2. Устанавливаем openssl-1.1.1g на компьютер.
    Установку необходимо провести по стандартному пути, который указывается автоматически в папку C:\Program Files. Программа установится в папку OpenSSL-Win64.
  3. Для того, чтобы настроить openSSL так как вам нужно, существует файл openssl.cfg. Этот файл расположен в пути C:\Program Files\OpenSSL-Win64\bin если вы установили openSSL так, как было сказано в предыдущем пункте. Переходим в папку, где хранится openssl.cfg и открываем этот файл с использованием, например, Notepad++.
  4. Вы наверно догадались, что настройка удостоверяющего центра будет производиться как-то изменением содержимого файла openssl.cfg, и вы абсолютно правы. Для этого требуется настроить команду [ ca ]. В файле openssl.cfg начало текста, куда мы будем вносить изменения, можно найти как: [ ca ].
  5. Теперь я приведу пример настройки с его описанием:

    [ ca ]
    default_ca = CA_default

    [ CA_default ]
    dir = /Users/username/bin/openSSLca/demoCA
    certs = $dir/certs
    crl_dir = $dir/crl
    database = $dir/index.txt
    new_certs_dir = $dir/newcerts
    certificate = $dir/ca.crt
    serial = $dir/private/serial
    crlnumber = $dir/crlnumber

    crl = $dir/crl.pem
    private_key = $dir/private/ca.key
    x509_extensions = usr_cert

    Сейчас необходимо создать каталог demoCA и подкаталоги, как показано в примере выше. И расположить в этот каталог по пути, что указан в dir (у меня /Users/username/bin/openSSLca/demoCA).

    Очень важно правильно прописать dir это путь к директории, где будет находиться наш удостоверяющий центр. Эта директория должна обязательно находиться в /Users (то есть в учетной записи какого-нибудь пользователя). Если расположить эту директорию например в C:\Program Files, система не увидит файл с настройками openssl.cfg (по крайней мере у меня было так).

    $dir сюда подставляется путь, который указан в dir.

    Еще важный момент создать пустой файл index.txt, без этого файла команды openSSL ca ... не будут работать.

    Также необходимо иметь файл serial, корневой приватный ключ (ca.key), корневой сертификат (ca.crt). Процесс получения этих файлов будет описан далее.
  6. Подключаем алгоритмы шифрования предоставляемые Рутокеном.
    Это подключение происходит в файле openssl.cfg.
    • Прежде всего требуется скачать необходимые алгоритмы Рутокена. Это файлы rtengine.dll, rtpkcs11ecp.dll.
      Для этого скачиваем Рутокен SDK: www.rutoken.ru/developers/sdk.

      Рутокен SDK это все, что есть для разработчиков, которые хотят опробовать Рутокен. Там есть как отдельные примеры для работы с Рутокеном на разных языках программирования, так и представлены некоторые библиотеки. Наши библиотеки rtengine.dll и rtpkcs11ecp.dll находятся в Рутокен sdk соответственно по расположению:
      sdk/openssl/rtengine/bin/windows-x86_64/lib/rtengine.dll
      sdk/pkcs11/lib/windows-x86_64/rtpkcs11ecp.dll

      Очень важный момент. Библиотеки rtengine.dll, rtpkcs11ecp.dll не работают без установленного драйвера для Рутокена. Также Рутокен обязательно должен быть подключен к компьютеру. (про установку всего необходимого для Рутокен смотри в предыдущей части статьи habr.com/ru/post/506450)
    • Библиотеки rtengine.dll и rtpkcs11ecp.dll можно держать в любом месте учетной записи пользователя.
    • Прописываем пути к этим библиотекам в openssl.cfg. Для этого открываем файл openssl.cfg, в начало этого файла необходимо поместить строчку:

      openssl_conf = openssl_def

      В конец файла необходимо добавить:

      [ openssl_def ]
      engines = engine_section
      [ engine_section ]
      rtengine = gost_section
      [ gost_section ]
      dynamic_path = /Users/username/bin/sdk-rutoken/openssl/rtengine/bin/windows-x86_64/lib/rtengine.dll
      MODULE_PATH = /Users/username/bin/sdk-rutoken/pkcs11/lib/windows-x86_64/rtpkcs11ecp.dll
      RAND_TOKEN = pkcs11:manufacturer=Aktiv%20Co.;model=Rutoken%20ECP
      default_algorithms = CIPHERS, DIGEST, PKEY, RAND

      dynamic_path необходимо прописать свой путь к библиотеке rtengine.dll.
      MODULE_PATH необходимо прописать свой путь к библиотеке rtpkcs11ecp.dll.

  7. Добавляем переменные среды.
    Обязательно нужно добавить переменную среды, которая указывает путь к файлу конфигурации openssl.cfg. В моем случае была создана переменная OPENSSL_CONF с путем C:\Program Files\OpenSSL-Win64\bin\openssl.cfg.

    В переменную path необходимо указать путь до папки, в которой находится openssl.exe, в моем случае это: C:\Program Files\OpenSSL-Win64\bin.
  8. Теперь можно вернуться к пункту 5 и создать недостающие файлы для каталога demoCA.
    1. Первый важный файл без которого ничего не будет работать serial. Это файл без расширения, значение которого должно быть 01. Можно создать этот файл самостоятельно и прописать внутрь 01. Также можно скачать его из Рутокен SDK по пути sdk/openssl/rtengine/samples/tool/demoCA/.
      В каталоге demoCA лежит файл serial, который нам как раз и нужен.
    2. Создаем корневой приватный ключ.
      Для этого воспользуемся командой библиотеки openSSL, которую необходимо запустить прямо в командной строке:

      openssl genpkey -algorithm gost2012_256 -pkeyopt paramset:A -out ca.key
    3. Создаем корневой сертификат.
      Для этого воспользуемся следующей командой библиотеки openSSL:

      openssl req -utf8 -x509 -key ca.key -out ca.crt

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

    Все теперь имеются все недостающие файлы для полной конфигурации каталога demoCA. Поместите созданные файлы в те каталоги, которые указаны в пункте 5.


Будем считать, что после выполнения всех 8 пунктов наш удостоверяющий центр полностью настроен.

В следующей части я расскажу, каким образом мы будем работать с удостоверяющим центром, чтобы выполнить то, что было описано в предыдущей части статьи (http://personeltest.ru/aways/habr.com/ru/post/506450/).
Подробнее..

Опыт применения технологии Рутокен для регистрации и авторизации пользователей в системе (часть 4)

19.06.2020 16:07:35 | Автор: admin
Добрый день!

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

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

Общая документация, как работать с Рутокен плагин находится по ссылкам:
Встраивание Рутокен ЭЦП 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 с логическим значением, которое говорит о том, что прошла ли наша авторизация успешно или нет.

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

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

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru