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

Безопасность приложения

Зачем разработчику разбираться в вопросах безопасности?

07.05.2021 12:20:51 | Автор: admin

Одно дело почитать теорию об уязвимостях и совсем другое увидеть последствия и защититься на практике. Специалист по защите приложений в DINS Иван Юшкевич предлагает испробовать это во время конференции PHP Russia 2021, где он представит свой практический доклад Веб-безопасность для начинающих.

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

Расскажи о себе и своей работе, чем ты занимаешься?

Я специалист по защите приложений. Занимаюсь ApplicationSecurity в компании DINS. В этой области я уже лет десять. Начинал как специалист по защите информации, потом занимался тестированием на проникновением.

Что планируется на конференции и почему именно такая тема доклада?

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

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

На конференции я расскажу, почему prepared statements только усложняют код, а возможность внедрения JavaScript-кода на сайте не является багом. Кроме того, на практике мы с участниками попробуем накрутить лайков в социальных сетях, украдем криптовалюту и получим доступ к самым большим секретам пользователей. А потом я объясню, что нужно делать, чтобы не допустить подобных случаев в веб-приложении.

Как ты думаешь, кому будет полезен твой доклад?

  1. Узнает ли, например, сеньор что-то новое, если он пойдет на этот доклад? Нет.

  2. Узнает ли что-то миддл? Вполне. Возможности, ничего нового в инструментах он и не узнает, но может задуматься над некоторыми аспектами безопасности.

  3. Узнает ли что-то джун, который пойдет на доклад? Абсолютно точно.

Почему так? Потому что есть более интересные доклады для сеньоров. Что-то небанальное, о том, как построить самолет из желудей и спичечных коробочек. А для миддла написание кода одна из актуальных задач.

Программисту действительно нужна практика в вопросах безопасности? Если да, то зачем?

Да. Если говорить возвышенными словами это нужно, чтобы допускать меньше ошибок в коде. А если спуститься к материальному языку, то проблема безопасности обходится для компании в определенное количество денег. Поэтому любая ошибка, которую изначально не допустили или обнаружили на этапе создания, обходится компании дешевле, чем ее исправление потом.

В чем профит для разработчика? Ведь у компании куча денег, зачем ему их считать? Он может расширить свои компетенции и в некоторых случаях претендовать на звание Security Champion в компании.

Что нужно сделать, чтобы стать специалистом в твоей области? Пройти обучение и стать сертифицированным специалистом, или есть другой путь?

Мне кажется, в первую очередь должно быть желание: вам должно быть интересно этим заниматься. Большую часть информации о безопасности приложений сегодня можно почерпнуть из интернета. Есть куча ресурсов, где можно прокачать свои навыки. Заниматься вопросами безопасности можно и без трудоустройства в компании. Можно участвовать в bug bounty и получать за это хорошие деньги.

Главное иметь страсть, желание работать в области практической безопасности. Сертификаты, обучение не помогут, если вы по-настоящему не захотите этим заниматься. Среди безопасников очень многие люди не имеют вообще никаких сертификатов, но они являются первоклассными специалистами. Почему у них нет сертификатов? Потому что им просто это не нужно.

А есть люди, у которых список сертификатов на целую страницу А4, они их просто собирают, для галочки. Любая компания может сказать, что она выдает сертификат, другой вопрос, что действительно стоит за этим сертификатом. Если есть желание, от любых курсов и от любого сертификата можно получить намного больше преимуществ, чем если вы просто ждете, что в какой-то момент станете профессионалом.

Как можно убедить бизнес уделить время вопросам безопасности? Какие ты слова подбираешь для того, чтобы этого добиться?

Слова очень простые. Причем, не мои, эту мысль озвучили уже до меня: исправление уязвимости на этапе разработки стоит около ста долларов, а исправление уязвимости в выпущенном продукте на порядки дороже. Вот и все.

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

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

Можно провести такую параллель: радость от сэкономленных денег намного короче, чем слезы от плохой татуировки. Сэкономили денег, сделали татуировку, радуемся два дня, а потом думаем: там же какой-то черт нарисован, а я тигра хотел! И от этого уже никуда не деться.

Достаточно ли сделать один раз аудит, или нужно выстраивать процесс внутри компании?

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

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

Считаешь ли ты свой доклад нетривиальным?

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

PHP Russia 2021 пройдет 28 июня вМосква, Radisson Slavyanskaya. Но уже сегодня можно ознакомиться с расписанием и присмотреть доклады, которые вы точно не захотите пропустить.

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

Подробнее..

Опыт применения технологии Рутокен для регистрации и авторизации пользователей в системе (часть 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