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


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

В качестве приятного бонуса — возможность получать подписанные ЭЦП документы на электронную почту.


Речь будет идти о сертификатах ЭЦП, выпущенных НУЦ РК.


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


Я буду использовать:


  • Vue.js для удобства разработки (постараюсь не писать ничего специфического, так, чтобы можно было легко переключиться на любой другой);
  • Bootstrap для базового оформления (его практически не будет заметно);
  • axios для упрощения сетевого взаимодействия;
  • pdfmake для создания PDF из JS;
  • ncalayer-js-client для взаимодействия с NCALayer;
  • SIGEX для проверки подписей и отправки документов по электронной почте.

Для реализации мне потребуются:


  • шаблон электронного документа, состоящий из фиксированной части и полей, которые должен заполнять пользователь;
  • действующий сертификат ЭЦП НУЦ РК;
  • установленный NCALayer.

В самой статье я приведу только ключевые части кода, а с полной реализацией можно ознакомиться тут: https://github.com/sigex-kz/example-sign-web-form


Веб форма


В первую очередь размещу на странице веб форму, в которую пользователи будут вводить данные:


<form v-on:submit.prevent="compilePDF">
  <h2>{{ formHeader }}</h2>
  <p>{{ formIntro }}</p>
  <div v-for="formItem in formItems" class="form-group">
    <label>{{ formItem.label }}</label>
    <input v-model="formItem.value" type="text" class="form-control">
  </div>
  <button type="submit" class="btn btn-primary">Сформировать документ</button>
</form>

Фиксированные части формы буду заполнять в объекте данных Vue.js для того, чтобы их можно было использовать и в HTML и в PDF:


new Vue({
...
  data: {
    formHeader: 'Документ №1',
    formIntro: 'Этот документ сформирован в браузере из данных веб формы, является примером, он никого ни к чему не обязывает и ничего не гарантирует, несмотря на то, что он может быть подписан ЭЦП.',
    formItems: [
      { label: 'Первый вопрос', value: '', },
      { label: 'Второй вопрос', value: '', },
      { label: 'Третий вопрос', value: '', },
    ],
...
  },
...

Генерация PDF в JS


Для формирования PDF воспользуюсь библиотекой pdfmake, она создает PDF файл из определения — объекта JS.


Сначала сформирую объект определения и наполню его данными, после этого передам его в pdfmake и получу байты PDF файла в base64 кодировке:


async compilePDF() {
  const pdfDefinition = {
    content: [
      { text: this.formHeader, fontSize: 20, bold: true, alignment: 'center', margin: [ 0, 0, 0, 20 ] },
      { text: this.formIntro, fontSize: 15, margin: [ 0, 0, 0, 20 ] },
    ],
  };

  for (const formItem of this.formItems) {
    pdfDefinition.content.push(`${formItem.label}: ${formItem.value}`);
  }

  this.dataB64 = await new Promise((resolve) => {
    const pdfDocGenerator = pdfMake.createPdf(pdfDefinition);
    pdfDocGenerator.getBase64(resolve);
  });
},

Подписание PDF цифровой подписью


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


<p>Вы подписываете документ <a v-bind:href="`data:application/octet-stream;base64,${dataB64}`" target="_blank" v-bind:download="title">{{ title }}</a>.</p>

НУЦ РК предоставляет сертифицированное средство ЭЦП — приложение NCALayer, его и буду использовать для формирования подписи. NCALayer — это WebSocket сервер, чтобы не писать обработку отправки и получения команд вручную, воспользуюсь библиотекой ncalayer-js-client.


NCALayer поддерживает, как файловые хранилища ключей ЭЦП, так и защищенные аппаратные хранилища (токены и карты). Чтобы не перегружать пользователя излишними вопросами в первую очередь спрошу обнаружил ли NCALayer аппаратные хранилища. В том случае, если аппаратных хранилищ не обнаружено, переключусь на файловое:


const storageTypes = await this.ncaLayer.getActiveTokens();

if (storageTypes.length == 0) {
  this.storageType = 'PKCS12';
} else {
  this.storageType = storageTypes[0];
}

Теперь можно подписать PDF. В зависимости от типа запрошенного хранилища, NCALayer отобразит соответствующий пользовательский интерфейс и завершит процедуру:


const signature = await this.ncaLayer.createCMSSignatureFromBase64(this.storageType, this.dataB64);

В том случае, если все прошло хорошо, на данный момент у меня имеются:


  • электронный документ в виде PDF файла;
  • цифровая подпись под электронным документом.

Проверка подписи и пересылка электронного документа по электронной почте


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


Я воспользуюсь API сервиса SIGEX для выполнения проверки, а сервис обеспечит возможность долгосрочного хранения электронного документа путем получения метки времени и информации о статусе сертификата. Так же я запрошу отправку уведомления по электронной почте.


let response = await axios.post(
  `${this.sigexURL}/api`,
  {
    title: this.title,
    description: this.description,
    signature,
    emailNotifications: { to: ['some-manager@example.kz', this.email] },
  },
);

this.sigexId = response.data.documentId;

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


Вторым этапом передам сервису на проверку подписанный документ:


const dataToSend = Uint8Array.from(atob(this.dataB64), c => c.charCodeAt(0)).buffer;
response = await axios.post(
  `${this.sigexURL}/api/${this.sigexId}/data`,
  dataToSend,
  {
    headers: { 'Content-Type': 'application/octet-stream' },
  },
);

Сервис не хранит самих подписанных документов, только электронные подписи под ними. Электронные документы будут отправлены по электронной почте на указанные адреса.


Итог


Проверим соответствие поставленной задаче:


  • на основе заранее согласованного шаблона сформировать в веб интерфейсе электронный документ — реализовано в виде статической веб страницы и небольшого количества JS кода;
  • предоставить возможность подписать его ЭЦП — реализовано с использованием рекомендуемого сертифицированного ПО;
  • передать его для обработки группе менеджеров — реализовано путем отправки подписанных документов на электронный адрес ответственных менеджеров;
  • обеспечить юридическую значимость в соответствии с законодательством РК — реализовано с помощью API сервиса SIGEX.