---
title: Цифровая подпись документов в Эстонии с помощью DigiDocService
date: 2010-02-08T10:00
tags: [backend]
---

В Эстонии с 2000 года вступил в силу [закон о цифровых подписях](http://www.esis.ee/ist2004/101.html), которые стали юридически равноценны обычным рукописным. Вскоре была создана и техническая основа - компания SertifitseerimisKeskus (буквально - «центр сертификации») принадлежащая банкам и телекоммуникацонным операторам (а не государству, представляете себе!) и схема обмена данными по X.509 стандарту. Эта статья расчитана в большей мере на программистов.  

### Цифровая подпись?

Подпись как оказывается очень важна, а признаваемая государством - тем более. Снижаются затраты на распечатку и/или доставку счетов по оплате, договоров между работником и работодателем. Я уже не говорю про обычное подтверждение что документ прислан точно нужным человеком, а не хакером. Спасает положение то что у каждого гражданина Эстоини есть сертификат подписи, но его недостаточно. Проблема в том что одной подписи-закарючки в IT-мире недостаточно. Подпись в расширенном виде на самом деле включает в себя набор данные, в том числе не статичные.

![](img/Pasted%20image%2020241019194009.png)

1. Стороны подписывающие документ
2. Собственно документ или его отпечаток (говорящий о неизменном состоянии со времени подписания)
3. Свидетели (нотариус) и роль сторон  
    
4. Время, место


![](img/Pasted%20image%2020241019194024.png)


Контейнер всей этой информации решили сделать на XML и назвать .ddoc расширением и связать с онлайн-сервисом создания и подтверждения подписей — Digidoc. За основу берутся основные свойства эстонской ID-карточки - **авторизация**, **подпись** и шифрование и в результате имеем:  

- цифровая подпись файлов (DigiDoc клиент, [портал](https://digidoc.sk.ee/) или третья сторона через DigiDocService)
- шифрование и дешифрование файлов (DigiDoc клиент)
- **подтверждение действительности** ([digidoccheck](https://digidoccheck.sk.ee/))
- подпись электронной почты
- подпись или авторизация с помощью мобильного телефона (Mobiil-ID)  
    

Контейнер со времени создания претерпел некоторые изменения, сейчас есть версия 1.3 основана на стандарте **XAdES-X-L** [расширенных электронных подписей](http://www.w3.org/TR/XAdES/).

### Процесс создания подписи с DigiDocService

Теперь собственно о главном что может понадобится на любом сайте. Допустим вы продаёте рога и копыта и хотите всё юридически правильно оформить. По-старинке это было бы типичный checkbox мол «согласен с условиями». Теперь же можно получить юридически действительную подпись клиента под любым договором, распиской купли-продажи или договора предоставления услуги.

---

![](img/Pasted%20image%2020241019194046.png)

Что-бы это у себя сделать Sertifitseerimiskeskus предоставляет услугу DigiDocService по SOAP, и для этого опубликованы следующие списки WSDL-методов: `http://www.sk.ee/DigiDocService/DigiDocService_2_3.wsdl //почти live   https://digidocservice.sk.ee/?wsdl //live - работает с CURL только вместе с Juur-SK.crt   https://www.openxades.org:8443/?wsdl //test`  
В обмене данными участвуют следующие стороны  

- Клиент с нормальной ид-карточкой и софтом  
    
- Наше серверное приложение
- Digidoc-узел (см. wsdl выше)  
    
- OCSP сервер, публикующий устаревшие или отозванные сертификаты id-карт  


![](img/Pasted%20image%2020241019194054.png)

Если опустить очевидное, то процесс в общем выглядит так  

1. Создание сессии между приложением и digidoc (**StartSession**) с передачей инфы о контейнере (который может включать несколько подписываемых файлов) — либо целиком файлы, либо их SHA1-хэш. Запоминаем вернувшися SessionCode у себя  
    
2. Можно запросить с помощью **GetSignatureModules** сразу готовый html (со всякими апплетами, activex компонентами..) для того что-бы получить сертификаты клиента
3. Клиент авторизуется передавая данные серверу, который вызывает **PrepareSignature** (signCertHex, signCertId), получает обратно бинарный хэш контейнера документов **SignedInfoDigest** который клиент должен подписать
4. Клиент подписывает **SignedInfoDigest** введя PIN2 — генерируется подпись signValueHex и передаётся в **FinalizeSignature**(). На этом моменте digidoc проверяет действительность сертификата пользователя у OSCP
5. В успешном случае можно уже скачать .ddoc файл. Если оригинальные файлы не отсылались, то их в base64-форме внедряют в вернувшийся xml. Сессия закрывается CloseSession.

Авторизацию и подпись можно поставить и с помощью мобильного телефона, где добавляются ещё и сторона оператора (MSSP), но я этот случай здесь не рассматриваю.

### О качестве ddservice

Поставляемый [php-пакет](http://www.sk.ee/pages.php/020207010702,1039) в качестве примера полон багов и говнокода — написанный под php4 надо постараться что-бы прикрутить его к PEAR под php5, надо увеличить таймаут curl во всех запросах с 4 секунд на что-либо существенное.

У меня в процессе вылетали ошибкииспользовал test-среду вместо live `200: Failed to get signature confirmation/notary // используйте live среду вместо test   Validation constraint violation: data type mismatch xsd:string in element 'Sesscode' //приведите сессию в int-тип   `

### Чтение сертификата с ID-карты

![](img/Pasted%20image%2020241019194118.png)

Возвращаемся к двум основным пунктам подписи. До вызова **PrepareSignature** метода, на странице есть компонент (Applet, ActiveX либо зависимый от браузера plugin) который считывает два параметра  

- signCertHex — сертификат подписи переведённый из цифрового DER формата в HEX  
    
- signCertId — идентификатор приватного ключа  
    

В качестве [компонента](http://www.sk.ee/pages.php/020207010805) DigiDocService возвращает ActiveX для IE (EIDCard.cab файлик) и аплет для остальных ( SignApplet_sig.jar,iaikPkcs11Wrapper_sig.jar ). ActiveX в плане интеграции довольно прост - можно javascriptом вызвать оба метода, следовательно прикрутить их к любому дизайну, другое дело что у меня этот компонент не работает (уж и драйверы переустанавливал - не помогло). А вот аплет мало того что подгружает яву, так и в дизайн не вписывается со своими кнопками.

#### Firefox - EstEID XPCOM v0.4

В идеале каждый браузер мог бы завести свой plugin который бы поддерживал обработку созданного в RIA  

`<object type="application/x-esteid" />`

Но глядя как это [тянется c 2006 года](http://my.opera.com/community/forums/topic.dml?id=156020&abc=&page=1&skip=0&show=&perscreen=50) в Opera, очевидно что это будет долго. Впрочем Firefox уже умеет использовать [onepin-opensc-pkcs11.dll](http://support.sk.ee/index.php?_m=knowledgebase&_a=viewarticle&kbarticleid=69?app=FFInstaller&code=1&appver=0.8.6.4&l=) как с помощью SK'шного хака, так и с помощью [плагина](https://id.eesti.ee/idtrac/wiki/FirefoxModuleInstaller) EstEID XPCOM v0.4 написанного в RIA и Smartlink. В результате имеем [красивую картинку](https://id.smartlink.ee/plugin_tests/test.html) в Firefox и нет нужды читать страшные мануалы по копированию dll-файлов и ошибок «Teie arvutisse on vaja installeerida **PKCS**#11 **ohjurprogramm**!»

Этот компонент успешно может яваскриптом выкачивать данные об id-карте к примеру— document.getElementById('esteid').signCert.cert. Но этот сертификат подписи для DigiDoc надо первести в шестнадцатиричный формат из PEM. Примерно так:  
        `$sTempKey=str_replace(array(           "-----BEGIN CERTIFICATE-----",           "-----END CERTIFICATE-----",           "\n"           ),'',$sTempKey);              $sTempKey=base64_decode($sTempKey);           $sTempKey=bin2hex($sTempKey);`  
[EstID-плагин](http://code.google.com/p/esteid/) теперь переехал и выпускается как open-source  

#### Internet explorer - EIDCard / dsiglite2 + idutil

![](img/Pasted%20image%2020241019194135.png)

EIDCard.cab компонент судя по существующему коду прост - создаёшь объект, ссылаешься на этот файл, потом через vbscript спрашиваешь сертификат или запрашиваешь подпись digest'а - всё как в Firefox. Что-бы он заработал нормально надо соблюдать следующие заповеди

1. Выключить автоматическое удаление сертификатов в ID-card tool если вы под Windows Vista с правами администратора
2. Держать объект EIDCard за пределами формы, иначе он не будет доступен вовсе
3. При создании подписи VBscript по умолчанию пытается записать результат в поле signValueHex, вот только видимо кто-то использует такую же переменную в компоненте из-за чего подпись не передаётся серверу.  
    

dsiglite2.cab это альтернатива для создания подписи, правда его найти удалось только у swedbank'а на странице и он слишком низкоуровневый, пришлось отказаться. Третий компонент очень полезен - idutil.cab понимает события вытаскивания карточки, хоть и с использованием JScript'а (в итоге получается каша из трёх ECMAscript диалектов)  

`<!--Использование idutil для чтения личных данных в IE-->   <div id="mTag"></div>   <OBJECT  ID="myCard" CLASSID="CLSID:7F9F89F2-F12B-4B25-9C69-7358F38B898B" CodeBase="idutil.cab"></OBJECT>   <SCRIPT LANGUAGE="JScript" FOR="myCard" EVENT="CardInserted() ">       var mTag = document.getElementById("mTag");       mTag.innerHTML = "Reading data...";       timerId = window.setInterval("reading()",500);   </SCRIPT>   <SCRIPT LANGUAGE="JScript" FOR="myCard" EVENT="CardRemoved() ">       var mTag = document.getElementById("mTag");       window.clearInterval(timerId);       mTag.innerHTML = "Card removed";   </SCRIPT>   <SCRIPT LANGUAGE="JScript">   var timerId;      function reading(){       var mTag = document.getElementById("mTag");       window.clearInterval(timerId);          mTag.innerHTML = "";       try {           myCard.ReadCard();           mTag.innerHTML='Hello,'+myCard.familyName+' '+myCard.personalCode;           }       catch(e) {           mTag.innerHTML="Reading failed";           }       }   </SCRIPT>   `

Если вы «переписываете» ddservice, то имеет смысл сохранить корень (wsdl класс) и переписать обёртку. Обратите внимание что после чтения сертификата и PREPARE, нельзя перенаправлять на другую страницу - надо делать POST на ту же самую страницу, иначе аплет будет ругаться. Вторая проблема - правильно заменять маркеры вида {1} на реальные данные. Ну и третья проблема - расставить пути к jar-файлам если у вас используется ЧПУ, иначе плагины будут искаться в локальной несуществующей папке.

### Validity confirmation

Как я выше писал - существует digidoccheck с помощью которого можно проверить подпись. Листочек этот юридической силы не имеет в качестве распечатки цифровой подписи, но иметь на всякий случай не помешает. Разберёмся же какие тут данные есть и откуда они приходят

- Данные о файле и пользователе - приходят в ответе FinalizeSignature. Там очень просто - ходи по массиву и выдёргивай что тебе надо.
- Серийный номер сертификата - десятичная версия  находится в ['SignedDocInfo']->SignatureInfo->Signer->Certificate->IssuerSerial и легко переводится в hex  
    
- Сертифицирующая сторона (собственно SK) в явном виде отсутсвует, надо выдирать из ['SignedDocInfo']->SignatureInfo->Confirmation->ResponderCertificate->Issuer
- Хеш публичного ключа сертифицирующей стороны. На данный момент это ESTEID-SK 2007 и он [есть на сайте](http://www.sk.ee/pages.php/0203040502), но правильней конечно запрашивать его динамически через WSDL метод GetSignersCertificate(). В php к результату надо приделать "-----BEGIN CERTIFICATE-----" и концовку (что-бы получить PEM формат) и взять от него openssl_x509_parse(). Внутри и будет находится заветная 4806DEBE ... Вариант захардкодить и обновлять каждые X лет я не рассматриваю
- Хеш OCSP-сертификата о действительности карточки (HASH VALUE OF VALIDITY CONFIRMATION (OCSP RESPONSE)) берётся из WSDL метода getNotary() из которого OcspData попадает в sha1(base64_decode(...))  
    

Ваш покорный слуга на время написания статьи нашёл мега-баг в том листочке что генерирует Digidoc Client - там OCSP responce hash всегда одинаковый. Кроме того я бы добавил хеши самих документов для печати, иначе слишком много зависимости от памяти SK. Ну и в подарок - метод hex2bin  

        `function hex2bin($data) {               $len = strlen($data);               for($i=0;$i<$len;$i+=2) {               $newdata .= pack("C",hexdec(substr($data,$i,2)));               }               return $newdata;           }`

[img/177.pdf](img/177.pdf)


[img/172.pdf](img/172.pdf)

[img/173.pdf](img/173.pdf)

[img/174.pdf](img/174.pdf)

## Related

- [Alter sequence](/ru/blog/tech/backend/DB/postgres/alter-sequence/)
- [Amazon S3 direct upload](/ru/blog/tech/backend/amazon-s3-direct-upload/)
- [CUDA](/ru/blog/tech/backend/gpu/cuda/)
