В этой статье я покажу как реализовать формирование юридически значимого электронного документа в веб интерфейсе без необходимости дорабатывать бекенд. Это может быть интересно тем, кто столкнулся, к примеру, со следующими задачами и ограничен в ресурсах и времени:
- необходимо добавить на сайт предприятия возможность подавать официальные заявки или обращения;
- необходимо добавить в веб интерфейс портала возможность подписывать договор присоединения;
- необходимо реализовать онлайн приемную.
В качестве приятного бонуса — возможность получать подписанные ЭЦП документы на электронную почту.
Речь будет идти о сертификатах ЭЦП, выпущенных НУЦ РК.
Задачу я сформулирую следующим образом: на основе заранее согласованного шаблона сформировать в веб интерфейсе электронный документ, предоставить возможность подписать его ЭЦП, передать его для обработки группе менеджеров и обеспечить юридическую значимость в соответствии с законодательством РК.
Я буду использовать:
- 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.
sshikov
Если бы меня спросить, является ли данная реализация э… надежной, я бы ответил — нет, по определению. Вы не можете доверять своему коду, так как он у вас где? Правильно, вот тут: cdn.jsdelivr.net/npm/vue@2. А раз вы не можете доверять даже своему коду — доверия вашим электронным подписям нет никакого.
В значительной степени такое сомнение касается вообще всей криптографии на базе JS.
vsenko Автор
Но в данной статье нет ни слова о криптографии на базе JS. Криптографией занимается нативное приложение NCALayer.
Согласен, было бы надежнее не использовать CDNы, благо сделать это не сложно. В данном случае я использую CDNы для простоты, так как это пример.
sshikov
>Криптографией занимается нативное приложение NCALayer.
А данные, которые js ему передает (а он ведь передает)? Вы им доверяете?
>В данном случае я использую CDNы для простоты, так как это пример.
Ну ок, как пример — нет вопросов. Я просто к тому, что по-хорошему, вот такое вот приложение — оно должно быть строго замкнуто, все ресурсы — только по https, со своих серверов, а если можем подписать каждый ресурс электронной подписью — то сделать это и проверить (и не факт, что это можно). Только тогда ему реально можно будет доверять.
vsenko Автор
Полностью солидарен, дополню статью рекомендацией не применять CDNы на боевых системах.
Я понимаю к чему Вы клоните, но ведь мы продолжаем пользоваться множеством систем, которым не имеем никаких оснований доверять. Тут нужен баланс между практичностью и безопасностью.
На мой взгляд хорошим решением, в данном случае, было бы доработать NCALayer так, чтобы он позволял просматривать подписываемые данные перед подписанием, вопрос этот поднимали: https://forum.pki.gov.kz/t/pochemu-v-ncalayer-netu-vizualizaczii-podpisyvaemyh-dannyh/590
sshikov
>ведь мы продолжаем пользоваться множеством систем, которым не имеем никаких оснований доверять.
Просто юридическая значимость — это даже не столько про доверие ваше к системе, сколько про доверие к вам условных специалистов по ИБ. Ну или юристов. Ну т.е. скажем от меня при внедрении похожей системы, которая выдает финансово значимые документы типа банковской выписки, потребовали зашифровать канал между приложением и базой данных.
Мы правда потом убедили коллег, что шифрование не обеспечивает целостность документа все равно, оно скорее от подслушивания спасет, или кражи данных, а нас не это волновало — но все равно, когда у вас юридические требования, или скажем участвует регулятор — то все происходит немножко иначе :)
vsenko Автор
Точно, в разных случаях разные средства.
Но, как минимум, хотя бы такие курьезы хотелось бы предотвратить: https://ct.kz/hall-of-fame-eds/elicensekz/