Перевірка підписаних даних, отриманих зі сторонньої системи: валідація КЕП та ідентифікація підписантів у файлах ASICS/CADES

1. Загальний опис

Під час інтеграції зі сторонніми системами на рівні бізнес-процесів потрібно обробляти підписані файли, які надходять від цих систем. Важливо забезпечити цілісність цих файлів, перевіряючи накладений на них цифровий підпис, отримати дані про підписанта для подальшої обробки або внесення в реєстри, а також мати можливість доступу до вмісту файлів-контейнерів.

Про особливості завантаження підписаних файлів до системи, див. на сторінці Завантаження файлів формату p7s та asics у Кабінетах.

1.1. Основні функціональні сценарії

Перевірка цілісності

Для забезпечення автентичності й цілісності даних, підписи перевіряються відповідно до типу контейнера.

Ідентифікація підписанта

Система дозволяє отримувати інформацію про всіх підписантів даних, що допомагає в трасуванні джерела й авторства.

Доступ до контенту

Можливість видобування контенту прямо з підписаного масиву даних.

1.2. Важливі аспекти

  • Обробка даних відбувається безпосередньо в скрипт-задачах бізнес-процесу.

  • Для передачі байтових даних між системами використовується кодування Base64.

  • За замовчуванням використовується формат CAdES-X-Long.

  • Дані та підпис завжди передаються разом в одному масиві.

  • Робота з підписами реалізована з допомогою ІІТ-бібліотеки.

1.3. Визначення й термінологія цифрового підпису

Контейнер — це результатний файл, який містить підписані дані. Існують різні типи контейнерів:

Таблиця 1. Типи контейнерів
Контейнер Опис Підтримується Платформою

CAdES (p7s)

Загальний формат для цифрових підписів

Так

ASiC (asic)

Сучасний контейнер, який рекомендується для використання. Особливість: архів для зберігання декількох файлів різних форматів

Так

XAdES (xml)

Формат, що базується на XML

Ні

PAdES (pdf)

Використовується для підпису PDF документів

Ні

Див. детальніше про підтримувані типи цифрового контенту на сторінці Завантаження масиву файлів в одному полі через компонент File.

Формат підпису — це конкретний алгоритм або набір правил, які використовуються при створенні цифрового підпису. Наприклад, CAdES-X-Long — це рекомендований формат. Саме такий формат використовує Платформа за замовчуванням.

Тип підпису може бути:

  • Відокремлений (detached) — підпис і дані зберігаються окремо.

  • Вбудований (enveloped) — підпис включений безпосередньо в документ або дані.

У цьому контексті терміни "файл" та "дані" є взаємозамінними й означають одне й те ж.

Як працює підписання цифрових документів за допомогою контейнерів різних форматів?

Розглянемо детальний опис типів контейнерів на прикладі офіційного ресурсу https://id.gov.ua/sign.

  1. Автентифікуйтеся за допомогою КЕП.

  2. Перейдіть на форму підписання та збереження документів.

    bp sign validate 01

  3. Оберіть формат підпису документа (тип контейнера).

    bp sign validate 02

    bp sign validate 03

  4. Завантажте файл, який необхідно підписати.

Надалі підписаний таким чином файл можна буде використовувати у бізнес-процесах реєстру.

2. Моделювання процесу валідації підписаного файлу з даними про домашніх тварин

Цей розділ містить референтний приклад моделювання процесу перевірки підписаних файлів, що містять інформацію про домашніх тварин.

Де можна знайти приклади референтних бізнес-процесів?

Адміністратор Платформи може розгорнути для вас демо-реєстр — еталонний реєстр, що містить референтні та інші приклади файлів для створення цифрового регламенту. Він містить різноманітні елементи для розробки моделі даних, бізнес-процесів, UI-форм, аналітичної звітності, витягів, сповіщень, зовнішніх інтеграцій та багато іншого.

Детальну інструкцію щодо розгортання демо-реєстру та отримання референтних прикладів моделювання ви знайдете на сторінці Розгортання демо-реєстру із референтними прикладами.

Приклад BPMN-схеми процесу буде доступний у регламенті демо-реєстру за пошуком по ключовим словам — checking-signed-data. Назви форм ви можете знайти всередині відповідних користувацьких задач бізнес-процесу у полі Form key.

2.1. Передумови

Користувач завантажує до зовнішньої системи фотографії тварин, які супроводжуються підписами форматів asics або CAdES.

Платформа надалі зможе автоматично інтегруватися з цією зовнішньою системою, забирає файли, відокремлює від них цифрові підписи, а потім проводити перевірку валідності кожного підпису.

2.2. Процес моделювання

2.2.1. Користувацька задача (User Task) для пошуку даних

Створіть користувацьку задачу для пошуку інформації про тварину за номером чіпа. Виконайте наступні налаштування:

  1. Оберіть Template > User form.

  2. У полі Name введіть назву задачі.

  3. У полі Form key вкажіть назву задачі. Ключ форми поєднує змодельовану користувацьку задачу зі службовою назвою UI-форми введення даних.

  4. У полі Assignee вкажіть значення ${initiator}, яке представляє користувача, що ініціював цей бізнес-процес.

bp sign validate 1

2.2.2. Скриптова задача (Script Task) для отримання підписаних документів від зовнішніх систем

Змоделюйте скриптову задачу для отримання підписаних даних від зовнішньої системи.

Розгляньте опис варіанту налаштування скриптів для отримання документів від зовнішніх систем: Завантаження цифрових документів за зовнішнім посиланням.
  1. Створіть скриптову задачу (Script Task).

  2. Відкрийте редактор скриптів — Open script editor.

    bp sign validate 2

  3. Використайте референтний приклад скрипту.

    У цьому випадку показано емуляцію запита до зовнішньої системи за допомогою сервісу Wiremock.

    Детальніше про емуляцію API див. Налаштування емуляторів зовнішніх інтеграцій.

    Референтний приклад скрипту для отримання підписаних даних із зовнішньої системи
    import okhttp3.OkHttpClient
    import okhttp3.Request
    
    def chipNumber = submission('UserTask_EnterAnimalChipNumber')
      .formData.prop('chipNumber')
      .value()
    
    def okHttpClient = new OkHttpClient().newBuilder().build()
    
    def requestToAnimal = new Request.Builder()
      .url('http://wiremock:9021/animals?chipNumber='.concat(chipNumber))
      .get()
      .build()
    
    def animalResponse = okHttpClient.newCall(requestToAnimal).execute()
    def animalResponseBodyString = animalResponse.body().string()
    def animalResponseBodySpinJson = S(animalResponseBodyString, 'application/json')
    
    set_variable('animalResponse', animalResponseBodySpinJson)

    Розгляньмо покроково поданий скрипт:

    1. Імпорт необхідних бібліотек для роботи із HTTP-запитами.

      import okhttp3.OkHttpClient
      import okhttp3.Request
    2. Отримання номера мікрочипа.

      def chipNumber = submission('UserTask_EnterAnimalChipNumber')
        .formData.prop('chipNumber')
        .value()

      Скрипт отримує номер мікрочипа, введений користувачем у задачі UserTask_EnterAnimalChipNumber, і зберігає його в змінній chipNumber.

    3. Створення клієнта для HTTP-запитів.

      def okHttpClient = new OkHttpClient().newBuilder().build()

      Тут ми створюємо новий об’єкт OkHttpClient, який буде відповідати за відправлення HTTP-запитів.

    4. Формування запита.

      def requestToAnimal = new Request.Builder()
        .url('http://wiremock:9021/animals?chipNumber='.concat(chipNumber))
        .get()
        .build()

      Ми формуємо HTTP GET-запит до URL 'http://wiremock:9021/animals' із параметром chipNumber. Тобто, ми запитуємо інформацію про тварину, яка має заданий номер мікрочипа.

    5. Відправлення запита та отримання відповіді.

      def animalResponse = okHttpClient.newCall(requestToAnimal).execute()

      За допомогою створеного раніше OkHttpClient, ми відправляємо наш запит і отримуємо відповідь у формі об’єкта animalResponse.

    6. Конвертація відповіді в рядок.

      def animalResponseBodyString = animalResponse.body().string()

      Відповідь перетворюється в рядок (String) для подальшої обробки.

    7. Перетворення рядка в JSON.

      def animalResponseBodySpinJson = S(animalResponseBodyString, 'application/json')

      Скрипт використовує функцію S() для перетворення рядка відповіді на об’єкт Spin JSON, який дозволить легко працювати з JSON-даними.

    8. Збереження результату:

      set_variable('animalResponse', animalResponseBodySpinJson)

      Отриманий об’єкт Spin JSON зберігається у змінну animalResponse для подальшого використання у бізнес-процесі.

    Отже, загалом скрипт виконує наступні дії:

    • Отримує номер мікрочипа тварини, введений користувачем.

    • Відправляє HTTP GET-запит до зовнішнього API, щоб отримати дані про тварину за її номером мікрочипа.

    • Зберігає отриману відповідь як JSON-об’єкт у змінну animalResponse для подальшого використання.

    Цей скрипт імітує надсилання відповіді з файлами фотографій тварини. Відповідь включає поле mainPhoto, де розміщена основна фотографія тварини в контейнері CAdES, та поле photos, що містить додаткові знімки, закодовані в Base64 у форматі asics (див. нижче).

2.2.3. Сервісна задача для перевірки підпису (Service Task)

Створіть сервісну задачу для перевірки валідності підпису у файлах. Використайте кастомний делегат Signature validation by DSO service.

Таблиця 2. Короткі відомості про делегат
Назва Пояснення

Бізнес-назва

Signature validation by DSO service

Службова назва

${digitalSignatureValidateDelegate}

Назва файлу у бібліотеці розширень

digitalSignatureValidateDelegate.json

  1. Відкрийте бізнес-процес та створіть Service Task.

  2. Натисніть кнопку Open Catalog.

  3. Зі списку делегатів оберіть Signature validation by DSO service та підтвердіть свій вибір, натиснувши Apply.

  4. У полі Name введіть зрозумілу назву задачі, яка відображатиме її суть.

  5. У полі Data вкажіть змінну, де зберігається підпис у форматі Base64 для подальшої обробки. Наприклад: ${animalResponse.prop('photos').value()}.

  6. Встановіть необхідний тип контейнера у полі Container. Можливі варіанти: ASIC, CADES, або ALL (якщо потрібно автоматично визначити формат вхідних даних).

    bp sign validate 3
    Зображення 1. Перевірка підпису. Автоматичне визначення формату вхідних даних
    bp sign validate 3 1
    Зображення 2. Перевірка підпису. Контейнер CADES

    У цьому бізнес-процесі використано дві сервісні задачі з типовим розширенням Signature validation by DSO service, в якому налаштовано різні типи контейнерів. В одній сервісній задачі перевіряється валідність підпису головного фото, яке було отримано в контейнері Asic, а в другій — додаткових фото, які були отримані в контейнері CAdES.

  7. У полі X-Access-Token вкажіть JWT-токен доступу користувача, від імені якого виконується операція. Наприклад, використаємо токен виконавця останньої задачі: ${completer('UserTask_EnterAnimalChipNumber').accessToken}.

  8. У полі Result variable вкажіть змінну, до якої необхідно зберегти результат. Наприклад, validationAsicResult або validationCadesResult.

2.3. Умови для перевірки підписів

Далі змоделюйте будь-які умови для перевірки підписів. У даному прикладі змодельована перевірка щодо валідності обидвох підписів — як для головного фото, так і для додаткових.

bp sign validate 4
Зображення 3. XOR-шлюз перевірки валідності підписів

2.4. Скриптова задача для отримання даних про підписантів та перевірки на збіг персональних даних

Змоделюйте Script Task та використайте скрипт, що збиратиме інформацію про підписантів отриманих файлів та виконуватиме перевірку на збіги персональних даних. Використайте у скрипті JUEL-функцію signature_details().

bp sign validate 5
Скрипт отримання деталей цифрового підпису
var asicSignInfo = signature_details(animalResponse.prop('photos').value(),
                        validationAsicResult.container).getSignInfo()
var cadesSignInfo = signature_details(animalResponse.prop('mainPhoto').value(),
                        validationCadesResult.container).getSignInfo()

var isEqualFullName = asicSignInfo.getSubjFullName()
                                .equalsIgnoreCase(cadesSignInfo.getSubjFullName())

def signerPayload = S([:], 'application/json')
signerPayload.prop('fullName', cadesSignInfo.getSubjFullName())
signerPayload.prop('drfo', cadesSignInfo.getSubjDRFOCode())
signerPayload.prop('edrpou', cadesSignInfo.getSubjEDRPOUCode())


set_transient_variable('isEqualFullName', isEqualFullName)
set_transient_variable('signerPayload', signerPayload)
set_variable('validationCadesRes', validationCadesResult)
set_variable('validationAsicRes', validationAsicResult)

Поданий скрипт спрямований на роботу з деталями цифрового підпису і робить наступне:

  • Отримує деталі цифрових підписів для фотографій тварини.

  • Порівнює повні імена осіб, які підписали обидва документи.

  • Формує JSON-об’єкт з деталями підпису.

  • Зберігає отримані дані в змінних для подальшого використання.

Функція signature_details(…​) приймає два аргументи: контент, що підписується, та контейнер підпису. Вона повертає деталі про підпис (як про особу, яка підписала, так і технічні деталі підпису).

Розглянемо скрипт докладно:

  1. Використання функції signature_details(…​):

    • asicSignInfo:

      var asicSignInfo = signature_details(animalResponse.prop('photos').value(),
                              validationAsicResult.container).getSignInfo()

      Функція signature_details(…​) приймає два аргументи: контент для підпису (у цьому випадку — це фотографії тварини) та контейнер підпису. Ця функція повертає деталі про цифровий підпис ASIC. Після цього за допомогою методу .getSignInfo() ми отримуємо інформацію про підпис.

    • cadesSignInfo:

      var cadesSignInfo = signature_details(animalResponse.prop('mainPhoto').value(),
                              validationCadesResult.container).getSignInfo()

      Аналогічно попередньому пункту, але тут ми працюємо з основною фотографією тварини та контейнером підпису CADES.

    Функція signature_details(…​) приймає лише контейнери ASIC та CAdES.
  2. Порівняння імен у підписах:

    var isEqualFullName = asicSignInfo.getSubjFullName()
                        .equalsIgnoreCase(cadesSignInfo.getSubjFullName())

    Скрипт порівнює повні імена суб’єктів (людей, які підписали документи) в обох підписах (ASIC та CADES) і перевіряє, чи вони збігаються. Результат порівняння зберігається у змінній isEqualFullName.

  3. Формування JSON-об’єкта з деталями підпису:

    def signerPayload = S([:], 'application/json')
    signerPayload.prop('fullName', cadesSignInfo.getSubjFullName())
    signerPayload.prop('drfo', cadesSignInfo.getSubjDRFOCode())
    signerPayload.prop('edrpou', cadesSignInfo.getSubjEDRPOUCode())

    Тут ми створюємо порожній JSON-об’єкт (signerPayload) та наповнюємо його даними з підпису CADES: повне ім’я, код DRFO та код EDRPOU.

  4. Збереження змінних:

    set_transient_variable('isEqualFullName', isEqualFullName)
    set_transient_variable('signerPayload', signerPayload)
    set_variable('validationCadesRes', validationCadesResult)
    set_variable('validationAsicRes', validationAsicResult)

    Результати обробки зберігаються у змінних, які будуть доступні для подальшого використання в бізнес-процесі.

2.5. Перегляд даних про підписанта

Змоделюйте користувацьку задачу для перегляду даних про підписанта на формі.

bp sign validate 6

2.6. Скриптова задача для отримання переліку підписаних файлів

Змоделюйте скриптову задачу та використайте у скрипті JUEL-функцію signature_content(), яка отримує перелік файлів, що були підписані.

bp sign validate 7
Скрипт для обробки контенту цифрового підпису та його збереження
var asicContent = signature_content(animalResponse.prop('photos').value(),
                        validationAsicRes.container).getAllContent()
var cadesContent = signature_content(animalResponse.prop('mainPhoto').value(),
                        validationCadesRes.container).getContent()

def photos = []
asicContent.each {
        def decodedAsicContent = Base64.getDecoder().decode(it.getData())
        def asicFileMetadata = save_digital_document(decodedAsicContent, it.getName())
        def photo = [:]
        photo.id = asicFileMetadata.getId()
        photo.checksum = asicFileMetadata.getChecksum()
        photos << photo
        }

def mainPhotos = []
def decodedCadesContent = Base64.getDecoder().decode(cadesContent.getData())
def cadesFileMetadata = save_digital_document(decodedCadesContent, cadesContent.getName().concat('.png'))
def mainPhoto = [:]
mainPhoto.id = cadesFileMetadata.getId()
mainPhoto.checksum = cadesFileMetadata.getChecksum()
mainPhotos << mainPhoto

def contentsPayload = S([:], 'application/json')

contentsPayload.prop('name', animalResponse.prop('name').value())
contentsPayload.prop('chipNumber', animalResponse.prop('chipNumber').value())
contentsPayload.prop('photos', S(photos, 'application/json'))
contentsPayload.prop('mainPhoto', S(mainPhotos, 'application/json'))

set_transient_variable('contentsPayload', contentsPayload)

Цей скрипт зосереджений на обробці контенту цифрового підпису та його збереженні. Він виконує наступні дії:

  • Отримує контент цифрових підписів для фотографій тварини.

  • Декодує, зберігає та обробляє ці фотографії.

  • Формує JSON-об’єкт з деталями контенту.

  • Зберігає JSON-об’єкт в змінній для подальшого використання.

Функція signature_content(…​) приймає два аргументи: контент, що підписується, та контейнер підпису. Вона повертає деталі контенту підпису.

Розглянемо скрипт докладніше:

  1. Використання функції signature_content(…​):

    • asicContent:

      var asicContent = signature_content(animalResponse.prop('photos').value(),
                              validationAsicRes.container).getAllContent()

      Функція signature_content(…​) приймає два аргументи: контент для підпису (у цьому випадку це фотографії тварини) та контейнер підпису ASIC. Метод .getAllContent() повертає усі частини цього контенту.

    • cadesContent:

      var cadesContent = signature_content(animalResponse.prop('mainPhoto').value(),
                              validationCadesRes.container).getContent()

      Аналогічно попередньому, але для основної фотографії тварини та підпису CADES.

    Функція signature_content(…​) працює виключно з контейнерами цифрових підписів двох типів: ASIC і CAdES. Щоб вона функціонувала коректно, потрібно спочатку встановити або визначити тип контейнера, з яким вона буде працювати, безпосередньо в налаштуваннях цієї функції.
  2. Обробка та збереження фотографій з контенту ASIC:

    def photos = []
    asicContent.each {
        def decodedAsicContent = Base64.getDecoder().decode(it.getData())
        def asicFileMetadata = save_digital_document(decodedAsicContent, it.getName())
        def photo = [:]
        photo.id = asicFileMetadata.getId()
        photo.checksum = asicFileMetadata.getChecksum()
        photos << photo
    }

    Цей блок коду декодує кожну частину контенту ASIC з Base64, зберігає її за допомогою функції save_digital_document(…​), а потім зберігає метадані цих файлів у список photos.

  3. Обробка та збереження основної фотографії з контенту CADES:

    def mainPhotos = []
    def decodedCadesContent = Base64.getDecoder().decode(cadesContent.getData())
    def cadesFileMetadata = save_digital_document(decodedCadesContent, cadesContent.getName().concat('.png'))
    def mainPhoto = [:]
    mainPhoto.id = cadesFileMetadata.getId()
    mainPhoto.checksum = cadesFileMetadata.getChecksum()
    mainPhotos << mainPhoto

    Аналогічно попередньому блоку коду, але обробляється лише одна фотографія (основна).

  4. Формування JSON-об’єкта з деталями контенту:

    def contentsPayload = S([:], 'application/json')
    contentsPayload.prop('name', animalResponse.prop('name').value())
    contentsPayload.prop('chipNumber', animalResponse.prop('chipNumber').value())
    contentsPayload.prop('photos', S(photos, 'application/json'))
    contentsPayload.prop('mainPhoto', S(mainPhotos, 'application/json'))

    Тут ми створюємо порожній JSON-об’єкт (contentsPayload) та наповнюємо його даними: ім’ям тварини, номером чипа, списком фотографій та основною фотографією.

  5. Збереження JSON-об’єкта:

    set_transient_variable('contentsPayload', contentsPayload)

    Сформований JSON-об’єкт contentsPayload зберігається як тимчасова змінна для подальшого використання в бізнес-процесі.

2.7. Підписання даних та збереження їх до БД

Змоделюйте кроки з підписання даних та їх збереження до БД реєстру.

bp sign validate 8

3. Використання у Кабінеті користувача

Розглянемо приклад, як виглядатимуть користувацькі UI-форми з підписаними цифровими документами, отриманими із зовнішньої системи. Також оглянемо отриману інформацію про підписанта для подальшої обробки або внесення в реєстри, а також вміст файлів-контейнерів.

  1. Увійдіть до Кабінету користувача.

  2. Запустіть змодельований бізнес-процес.

    bp sign validate 9
  3. Введіть номер чипа тварини.

    bp sign validate 10
  4. Перегляньте дані про підписанта на формі.

    bp sign validate 11
  5. Далі перевірте ім’я та номер чипа тварини, а також основне та додаткові фото, які можна вивантажити на свій пристрій та переглянути.

    bp sign validate 12
  6. Підпишіть дані на формі за допомогою КЕП та завершіть бізнес-процес.