Интерфейс плагина#

Общие сведения#

Программный интерфейс плагина предполагает вызов асинхронных операций, реализованных на основе объектов promise. Подробнее про использованную в реализации плагина спецификацию можно прочитать здесь. На promise необходимо устанавливать обработчики двух типов:

  • onFulfilled – срабатывают, когда promise в состоянии выполнен успешно;

  • onRejected – срабатывают, когда promise в состоянии завершен с ошибкой.

Универсальный метод для установки обработчиков имеет следующий вид:

promise.then(onFulfilled, onRejected)

Инициализация плагина#

Для работы с плагином необходимо вызвать функцию инициализации PKCS#11-компонента plugin.initPKCS11. Данная функция в качестве параметров принимает перечень названий модулей (в виде массива). Перечень предусмотренных модулей можно посмотреть здесь. Если модули не указаны, то плагин произведет инициирование всех модулей. При необходимости указать, какие именно криптопровайдеры следует использовать при работе модуля capi, следует использовать следующий формат записи:

capi:{prov1},{mode}:{prov2},{mode}

В этой записи:

  • prov1, prov2 – название криптопровайдера. В настоящее время поддерживаются следующие значения:

    • Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider;

    • Crypto-Pro GOST R 34.10-2012 Cryptographic Service Provider;

    • Crypto-Pro GOST R 34.10-2012 Strong Cryptographic Service Provider;

    • Signal-COM CPGOST Cryptographic Provider;

    • Signal-COM GOST R 34.10-2012 (256) Cryptographic Provider;

    • Signal-COM GOST R 34.10-2012 (512) Cryptographic Provider;

    • Infotecs Cryptographic Service Provider.

  • mode – режим отображения окна ввода пин-кода. Может принимать следующие значения:

    • 0 – режим по умолчанию, предусмотренный криптопровайдером;

    • 1 – отображение нативного окна криптопровайдера.

      Примечание

      При работе с Crypto-Pro в Linux отображение нативного окна криптопровайдера недоступно.

    • 2 – отображение окна в интерфейсе плагина.

      Примечание

      При работе с Signal-COM отображение окна в интерфейсе плагина недоступно.

Для получения ключей из системного хранилища Windows следует в качестве провайдера указать <CERT_STORES>, а в качестве режима - My.

Пример инициирования всех модулей:

plugin.initPKCS11(["ISBC ESMART", "Aladdin R.D. Unified JaCarta", "Rutoken", "SafeNet", "capi:Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider,0:Crypto-Pro GOST R 34.10-2012 Cryptographic Service Provider,0:Crypto-Pro GOST R 34.10-2012 Strong Cryptographic Service Provider,0:Signal-COM CPGOST Cryptographic Provider,0:Signal-COM GOST R 34.10-2012 (256) Cryptographic Provider,0:Signal-COM GOST R 34.10-2012 (512) Cryptographic Provider,0:Infotecs Cryptographic Service Provider,0:<CERT_STORES>,My"])

Пример инициирования модуля для получения ключей из системного хранилища Windows:

plugin.initPKCS11(["capi:<CERT_STORES>,My"])

При успешной инициализации функция возвращает объект (здесь и далее – с помощью механизма promise), имеющий функции modules и getCertsForSign.

Перечень модулей и их состояние#

Для просмотра перечня модулей и их состояния необходимо вызвать функцию modules. Пример запроса:

pkcs11.modules.then(onFulfilled, onRejected);

Пример ответа функции (разрывы даны для удобства чтения):

 [
   {
      "enable": true,
      "name": "Aladdin R.D. Unified JaCarta"
   },
   {
      "enable": true,
      "name": "Rutoken ECP"
   },
   {
      "enable": false,
      "error": "100:failed to load p11 module",
      "name": "ISBC ESMART"
   }
]

Перечень сертификатов#

Для просмотра перечня обнаруженных сертификатов необходимо вызвать функцию getCertsForSign. В качестве параметра вызова функции необходимо указать, следует ли использовать параллельный режим опроса инициализированных PKCS#11 модулей:

  • true – параллельное обращение к модулям (рекомендуемый режим);

  • false – последовательное обращение к модулям.

Перечень сертификатов представляет собой массив (JavaScript Array), элементами которого являются объекты сертификатов. На объекте сертификата можно выполнить функции full_info, cms_sign_on_it и start_signing.

Данные о сертификате#

Для просмотра данных о конкретном сертификате необходимо вызвать свойство full_info, возвращающее сведения о сертификате в виде json-объекта. Он включает в себя следующие параметры:

  • sn – серийный номер сертификата.

  • subject – данные о субъекте, которому выдан данный сертификат электронной подписи. Возвращается в виде json в формате «параметр: значение», где параметр – это название соответствующего объектного идентификатора (OID). Всем стандартным объектным идентификаторам даны общепринятые обозначения, например, CN (Common Name).

  • issuer – данные об издателе сертификата ключа электронной подписи. Возвращается в виде json в формате параметр: значение, где параметр – это название соответствующего объектного идентификатора (OID). Всем стандартным объектным идентификаторам даны общепринятые обозначения.

  • not_before – время начала действия сертификата (тип данных – строка в формате ASN1_TIME).

  • not_after – время окончания действия сертификата (тип данных – строка в формате ASN1_TIME).

  • key_usage – информация о назначении ключа, возвращается в виде массива.

Время начала / окончания действия сертификата в формате ASN1_TIME может быть переведено в стандартный формат с помощью функции new Date(ASN1_TIME).

Данные о ключе электронной подписи#

Для просмотра данных о конкретном ключе электронной подписи сертификата необходимо вызвать метод token_info. Метод возвращает json-объект со следующими данными:

  • label – имя ключевого контейнера средства электронной подписи;

  • manufacturerID – идентификатор производителя средства электронной подписи;

  • model – модель средства электронной подписи;

  • serialNumber – серийный номер средства электронной подписи.

Для ключей, работающих через capi-модуль, возвращаемые данные имеют иной вид. Атрибут model всегда принимает значение capi, атрибут serialNumber отсутствует, manufacturerID соответствует названию криптопровайдера, а label – это название контейнера.

Подписание с помощью сертификата#

Простой режим подписания

Для подписания строки с помощью выбранного сертификата необходимо вызвать функцию cms_sign_on_it, принимающую следующие входные параметры:

  • строка для подписи;

  • количество попыток ввода пин-кода (например, значение 1 означает, что у пользователя имеется только одна попытка, после чего функция возвращает ошибку).

  • тип подписи — является ли подпись присоединенной (необходимо передавать значение true) или отсоединенной (false).

В качестве ответа функция возвращает строку с подписью в формате CAdES-BES / PKCS#7 attached/detached. Пример вызова функции, которая должна быть вызвана на объекте сертификата:

cms_sign_on_it("1234", 3, true).then(function(cms){console.log(cms)});

Расширенный режим подписания

Расширенный режим позволяет:

  • подписывать данные большого объема, например, файлы;

  • подписывать несколько файлов без повторного запроса пин-кода.

Для подписания данных с помощью выбранного сертификата необходимо предварительно инициализировать объект signer с помощью функции start_signing на объекте сертификата. Параметры функции:

  • тип подписи — является ли подпись присоединенной (необходимо передавать значение true) или отсоединенной (false);

  • количество попыток ввода пин-кода (например, значение 1 означает, что у пользователя имеется только одна попытка, после чего функция возвращает ошибку).

На объекте signer будут доступны методы:

  • add_data_in_hex(hexDataString) — принимает на вход данные в виде hex строки;

  • add_data_in_base64(base64DataString) — принимает на вход данные в виде base64 строки;

  • add_data_in_string(stringData) — принимает на вход данные в виде utf-8 строки;

  • free() — возвращает значение true/false, что позволяет проверить, что сертификат готов к подписанию. Требуется использовать для случая, когда несколько итераций подписи осуществляются на разных сертификатах. Иными словами, если осуществляется последовательное подписание на нескольких сертификатах, то перед подписанием необходимо вызвать этот метод и убедиться, что он вернул true;

  • finish() — финализирует подпись и возвращает ее в формате CAdES-BES / PKCS#7.

В качестве ответа функция возвращает строку с подписью в формате CAdES-BES / PKCS#7 attached/detached.

Подписание строки

Пример команды, позволяющей подписать строку:

signer.add_data_in_string("1234").then(function(res){ return signer.finish();}).then(function(cms){console.log(cms)});

В данной команде «1234» - это строка, которую необходимо подписать.

Установка нескольких подписей

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

Чтобы произвести подписание на другом сертификате, необходимо очистить объект signer. В большинстве браузеров этот объект очищается автоматически, когда он покидает область видимости (scope). Однако в Internet Explorer возможны ситуации, когда очищение signer не происходит, что приводит к ошибке. Для избегания ошибки рекомендуется явно очищать signer.free(). Данную операцию можно проводить во всех браузерах, чтобы унифицировать код. Пример подписи на сертификате с очисткой объекта signer:

function sign(cert, info) {

     function successCms(signature) {
      alert(signature);
     }

     cert.start_signing(false, 3)
      .then(
       function(signer) {
        signer.add_data_in_base64("MTIzNDU2")
         .then(function() {
            var data = signer.finish();
                    var free = signer.free();
             return data;
         }, e)
         .then(successCms, e);
       },
      e);
    }
Подписание файла большого объема

При подписании файлов большого объема рекомендуется осуществлять последовательное чтение и подписание фрагментов файла. В качестве примера можно воспользоваться следующей функцией:

function readFileByChunk(file, cbToRead, cbToFinish) {
    var fileSize   = file.size;
    var chunkSize  = 1024*1024; // bytes
    var offset     = 0;
    var chunkReaderBlock = null;
    var self       = this;

    var readEventHandler = function(evt) {
        if (evt.target.error == null) {
            cbToRead(evt.target.result, offset, fileSize);
            offset += evt.target.result.byteLength;
        } else {
            console.error("Read error: " + evt.target.error);
            showError("Ошибка чтения файла: " + evt.target.error);
            return;
        }
        if (offset >= fileSize) {
            cbToFinish()
            return;
        }

        // to the next chunk
        chunkReaderBlock(offset, chunkSize, file);
    }

    chunkReaderBlock = function(_offset, _chunkSize, _file) {
      var r = new FileReader();
      if (_file.slice) {
        var blob = _file.slice(_offset, _chunkSize + _offset);
      } else if (_file.webkitSlice) {
        var blob = _file.webkitSlice(_offset, _chunkSize + _offset);
      } else if (_file.mozSlice) {
        var blob = _file.mozSlice(_offset, _chunkSize + _offset);
      }
      r.onload = readEventHandler;
      r.readAsArrayBuffer(blob);
    }

    // start reading the first block
    chunkReaderBlock(offset, chunkSize, file);
}

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