Пређи на главни садржај
  1. Водич за програмере/

Архитектура потписивања

Ова страница описује интерну архитектуру LibreMiddleware engine-а за потписивање за контрибуторе који желе да разумеју како систем функционише, додају нове формате потписа или дебагују проблеме са потписивањем.

Желите да користите engine за потписивање из сопствене апликације? Јавни спој је LibreSCRS::Signing::SigningService — погледајте Водич за интеграцију потписивања. Остатак ове странице покрива оно што живи иза тог споја.

Јавни спој: LibreSCRS::Signing #

Спољни корисници никада не дотичу libresign::*. Линкују против LibreSCRS::Signing и позивају:

SigningService(std::shared_ptr<Trust::TrustStoreService>, TsaProvider);

SigningResult sign(const SigningRequest&,
                   Auth::CredentialProvider,
                   std::shared_ptr<Plugin::CardPlugin>,
                   std::shared_ptr<SmartCard::CardSession>);

LibreSCRS::Signing::SigningService је танка pimpl-подржана фасада која извршава валидацију захтева, резолвује снимак поверења из ињектованог TrustStoreService-а, покреће провајдер креденцијала и прослеђује интерном libresign::SigningService-у. libresign типови описани испод доступни су само кроз ову фасаду.

Животни циклус поверења (4.0): LibreSCRS::Trust::TrustStoreService #

Подсистем поверења има сопственог власника животног циклуса, одвојеног од потписивања. TrustStoreService::create(TrustConfig) је [[nodiscard]] noexcept и враћа std::expected<std::shared_ptr<TrustStoreService>, CreateError>. Фабрика синхроно гради локални скуп анкора (пакетски thirdparty/certs/ + опционо системски root store) и покреће сва конфигурисана Trusted-List преузимања на интерним радним нитима. Сервис за потписивање држи shared_ptr<TrustStoreService> за цео свој животни циклус, тако да eager TL преузимања попуњавају исти TrustStore који sign() посматра; лења преузимања током sign() позива се спајају у исти store, обезбеђујући да прегледач сертификата, регистар додатака и сервис за потписивање сви деле исти универзум поверења.

Корисници посматрају напредак преузимања кроз три ортогонална механизма:

  • status() враћа снимак AggregateStatus (Loading / Ready / Degraded).
  • addObserver(cb) региструје callback за прелазе стања — пут пријатељски за GUI.
  • waitForEagerFetches(deadline, token) блокира док се сваки eager извор не реши; намењено тестовима, CLI алатима и хеадлес корисницима.

Сервис је move-онемогућен и дели се преко референце, тако да исти универзум поверења природно конзумира више сервиса за потписивање или нон-сигнинг алата (прегледач сертификата, регистар додатака) у истом процесу.

Ток потписивања од почетка до краја #

Комплетан ток од корисничке акције до потписаног документа:

1. Корисник кликне „Потпиши" у LibreCelik-у
   └─ SignPage чаробњак прикупља: документ, формат, ниво, TSA, визуелне параметре

2. LibreCelik гради SigningRequest и позива SigningService::sign()
   └─ PIN прослеђен као span<const uint8_t> из SecureBuffer-а

3. NativeSigningService::sign()
   ├─ Отвара Pkcs11Token (учитава PKCS#11 модул, пријављује се PIN-ом)
   ├─ Чита сертификат за потписивање + ланац са токена
   └─ Прослеђује format модулу на основу request.format

4. Format модул (нпр. PAdESModule::sign())
   ├─ Припрема контејнер потписа (PDF инкрементално чување, CMS, XML, JSON или ZIP)
   ├─ Рачуна digest документа (SHA-256)
   ├─ Позива Pkcs11Token::sign(hash) → сирови бајтови потписа са картице
   ├─ Уграђује потпис + ланац сертификата у контејнер
   └─ Ако је ниво >= B-T: позива TSAClient::timestamp(hash)
      └─ Шаље RFC 3161 захтев TSA серверу
      └─ Уграђује TimeStampToken у потпис

5. Ако је ниво >= B-LT:
   ├─ RevocationClient преузима CRL + OCSP одговоре за ланац сертификата
   └─ Format модул уграђује податке о опозиву у потпис

6. Ако је ниво == B-LTA:
   └─ Архивски временски печат додат преко целокупног потписа + података о опозиву

7. NativeSigningService враћа SigningResult { success, signedDocument }

8. LibreCelik чува потписан документ на диск

Структура модула #

Engine за потписивање налази се у lib/libresign/ унутар LibreMiddleware-а. Организован је у три слоја.

Јавна заглавља живе под include/libresign/ (типови највишег нивоа) и include/libresign/native/ (класе native backend-а). Имплементације и интерни помоћници backend-а живе под src/.

Слој основног сервиса #

ФајлНамена
include/libresign/signing_service.hSigningService апстрактни интерфејс — configure(), sign(), isAvailable()
include/libresign/signing_service_factory.hФабричка функција createSigningService(Backend)
include/libresign/types.hТипови података: SigningRequest, SigningResult, TrustConfig, TSAConfig, енумерације
include/libresign/trust_store_manager.hTrustStoreManager — агрегира системске, пакетске и TL-изведене сертификате за различите потрошаче

Format модули #

Сваки формат потписа имплементиран је као независан модул. Сви модули прате исти образац: примају документ + сертификат + callback за потписивање, производе потписане излазне бајтове.

МодулФајлСтандард
PAdESsrc/native/pades_module.cppPDF инкрементално чување са CMS потписом
CAdESsrc/native/cades_module.cppDetached CMS/PKCS#7 потпис
XAdESsrc/native/xades_module.cppXML Digital Signature са XAdES својствима
JAdESsrc/native/jades_module.cppJSON Web Signature са JAdES заглављем
ASiC-Esrc/native/asic_module.cppZIP контејнер са XAdES потписом (користи miniz)

Format модули примају Pkcs11Token& референцу за операције потписивања. Token интерно управља PKCS#11 сесијом, претрагом кључева и сировим потписивањем.

Инфраструктура #

КомпонентаФајловиНамена
Pkcs11Tokeninclude/libresign/native/pkcs11_token.h + src/native/pkcs11_token.cppУправљање PKCS#11 сесијом — учитавање модула, пријава, претрага кључева, сирово потписивање, екстракција сертификата
TSAClientinclude/libresign/native/tsa_client.h + src/native/tsa_client.cppRFC 3161 захтеви за временске печате преко HTTP-а (libcurl)
RevocationClientinclude/libresign/native/revocation_client.h + src/native/revocation_client.cppПреузимање CRL-ова и OCSP-а за B-LT/B-LTA нивое
SigningProviderinclude/libresign/native/signing_provider.h + src/native/signing_provider.cppАпстракција над Pkcs11Token за спољне кориснике
TrustedListParserinclude/libresign/native/trusted_list_parser.h + src/native/trusted_list_parser.cppXML парсер за EU Trusted Lists (LOTL и TL)
TlCache(интерни) src/native/tl_cache.h/.cppКеш на диску за преузете Trusted List XML фајлове
TlSignatureVerifier(интерни) src/native/tl_signature_verifier.h/.cppXML-DSIG верификација потписа Trusted List-а
PinnedTlCerts(интерни) src/native/pinned_tl_certs.h/.cppКомпајлирани LOTL сертификати за потпис који служе као корен поверења
PDF parser(интерни) src/native/pdf_parser.h/.cppМинималан PDF парсер за PAdES инкрементално чување — проналази xref, додаје речник потписа
OpenSSL RAII(интерни) src/native/openssl_raii.hRAII омотачи за OpenSSL типове (BIO, X509, EVP_PKEY, итд.)

Модел поверења #

Engine за потписивање користи модел поверења са три нивоа за валидацију сертификата:

Ниво 1: Системски сертификати #

Подразумевано складиште сертификата оперативног система. Користи се као корен поверења за TLS конекције (TSA, CRL/OCSP крајње тачке) и као резервна опција за изградњу ланца сертификата.

Ниво 2: Пакетски сертификати #

Сертификати испоручени са LibreMiddleware-ом у thirdparty/certs/. Укључују корене CA сертификате за српску државну PKI инфраструктуру који можда нису у системским складиштима. Користе се за верификацију ланца сертификата са картице.

Ниво 3: Сертификати изведени из листа поверења #

Сертификати екстрактовани из EU Trusted Lists (TL/LOTL). Engine преузима и парсира EU List of Trusted Lists, прати линкове ка националним листама поверења и екстрактује CA сертификате за потписивање. Користе се за B-LT и B-LTA валидацију — обезбеђују сидра поверења која повезују сертификат потписника са EU-признатим пружаоцем услуга поверења.

Ланац аутентификације: Сам LOTL је потписан. Engine верификује потпис LOTL-а користећи приквачене сертификате (pinned_tl_certs.cpp) који су компајлирани у библиотеку. Национални TL-ови се верификују користећи сертификате пронађене у LOTL-у. Ово ствара ланац: приквачени сертификат верификује потпис LOTL-а, LOTL обезбеђује сертификате који верификују потписе националних TL-ова, национални TL-ови обезбеђују сертификате пружалаца услуга поверења.

Приквачени LOTL сертификати за потписивање (компајлирани)
  └─ верификују → потпис EU LOTL XML-а
       └─ садржи → сертификате за потписивање националних TL-ова
             └─ верификују → потписе националних TL XML-ова
                   └─ садрже → сертификате пружалаца услуга поверења
                         └─ валидирају → ланац сертификата потписника

DSS oracle за валидацију #

Пројекат укључује DSS (Digital Signature Services) backend који се може користити као oracle за валидацију у тестовима. DSS је EU референтна имплементација за креирање и валидацију потписа, коју одржава Европска комисија.

Шта ради: DSS backend делегира потписивање покренутом DSS серверу преко REST API-ја. Ово је корисно за унакрсну валидацију да су потписи произведени нативним engine-ом прихваћени од стране EU референтне имплементације.

Коришћење у тестовима: Када је постављено SIGNING_BACKEND=both, тестови могу креирати потпис нативним backend-ом и валидирати га DSS-ом или обрнуто. Ово открива суптилне проблеме усклађености формата које сами unit тестови не би ухватили.

Напомена: DSS backend је застарео за продукциону употребу. Постоји искључиво као тест oracle. Native backend је продукциони engine за потписивање.


Ток података: LibreCelik ка LibreMiddleware #

LibreCelik (GUI) и LibreMiddleware (engine) су одвојени пројекти са чистом границом. Ево како подаци за потписивање прелазе ту границу:

┌─────────────────────────────────────────────────┐
│                 LibreCelik (GUI)                 │
│                                                  │
│  ┌────────────┐    ┌──────────────────────────┐ │
│  │  SignPage   │───▶│ SigningRequest            │ │
│  │  (чаробњак) │    │ ┌─ бајтови документа     │ │
│  │             │    │ ├─ формат (PAdES/CAdES/..)│ │
│  │ Прикупља:   │    │ ├─ ниво (B-B/B-T/..)     │ │
│  │ - путања    │    │ ├─ TSA URL               │ │
│  │ - формат    │    │ ├─ параметри виз. потписа │ │
│  │ - ниво      │    │ └─ PIN (SecureBuffer)     │ │
│  │ - PIN       │    └──────────┬───────────────┘ │
│  └────────────┘               │                  │
│                               ▼                  │
├───────────────────────────────┬──────────────────┤
│              LibreMiddleware  │                   │
│                               │                  │
│  ┌────────────────────────────▼───────────────┐  │
│  │         NativeSigningService               │  │
│  │                                            │  │
│  │  ┌──────────────┐  ┌───────────────────┐   │  │
│  │  │ Pkcs11Token  │  │  Format модул     │   │  │
│  │  │ (I/O картице)│  │  (PAdES/CAdES/..) │   │  │
│  │  └──────┬───────┘  └────────┬──────────┘   │  │
│  │         │ сирови потпис     │ потписан док. │  │
│  │         ▼                   ▼               │  │
│  │  ┌──────────────────────────────────────┐   │  │
│  │  │ SigningResult { success, bytes, err } │   │  │
│  │  └──────────────────────────────────────┘   │  │
│  └────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────┘

Кључне пројектне одлуке:

  • Без Qt зависности — целокупан engine за потписивање је чист C++23 без Qt типова. LibreCelik конвертује између Qt типова (QString, QByteArray) и стандардних типова (std::string, std::vector<uint8_t>) на граници.
  • Без познавања протокола картице — engine за потписивање не шаље APDU команде и не зна за типове картица. Сав приступ картици иде кроз PKCS#11, који је стандардни интерфејс који сваки компатибилан токен може да задовољи.
  • PIN се никада не чува — PIN путује као span<const uint8_t> који не поседује меморију, од SecureBuffer-а GUI-ја кроз C_Login позив PKCS#11. Ниједна међукопија не опстаје након повратка из позива.
  • Format модули су без стања — сваки sign() позив је самосадржан. Нема стања сесије између позива, што engine чини безбедним за конкурентно коришћење из више нити.

Додавање новог формата потписа #

За додавање новог format модула:

  1. Креирајте include/libresign/native/newformat_module.h и src/native/newformat_module.cpp
  2. Имплементирајте sign() функцију која прима документ, Pkcs11Token& и параметре специфичне за формат
  3. Региструјте формат у NativeSigningService::sign() додавањем случаја за нову SignatureFormat enum вредност
  4. Додајте нову enum вредност у SignatureFormat у types.h
  5. Додајте изворне фајлове у lib/libresign/CMakeLists.txt
  6. Напишите тестове — користите DSS oracle (SIGNING_BACKEND=both) за валидацију усклађености формата