В Эстонии с 2000 года вступил в силу закон о цифровых подписях, которые стали юридически равноценны обычным рукописным. Вскоре была создана и техническая основа - компания SertifitseerimisKeskus
(буквально - «центр сертификации») принадлежащая банкам и
телекоммуникацонным операторам (а не государству, представляете себе!)
и схема обмена данными по X.509 стандарту. Эта статья расчитана в большей мере на программистов.
Цифровая подпись?
Подпись как оказывается очень важна, а признаваемая государством - тем более. Снижаются затраты на распечатку и/или доставку счетов по оплате, договоров между работником и работодателем. Я уже не говорю про обычное подтверждение что документ прислан точно нужным человеком, а не хакером. Спасает положение то что у каждого гражданина Эстоини есть сертификат подписи, но его недостаточно. Проблема в том что одной подписи-закарючки в IT-мире недостаточно. Подпись в расширенном виде на самом деле включает в себя набор данные, в том числе не статичные.
- Стороны подписывающие документ
- Собственно документ или его отпечаток (говорящий о неизменном состоянии со времени подписания)
- Свидетели (нотариус) и роль сторон
- Время, место
Контейнер всей этой информации решили сделать на XML и назвать .ddoc
расширением и связать с онлайн-сервисом создания и подтверждения
подписей — Digidoc.
За основу берутся основные свойства эстонской ID-карточки - авторизация, подпись и шифрование и в результате имеем:
- цифровая подпись файлов (DigiDoc клиент, портал или третья сторона через DigiDocService)
- шифрование и дешифрование файлов (DigiDoc клиент)
- подтверждение действительности (digidoccheck)
- подпись электронной почты
- подпись или авторизация с помощью мобильного телефона (Mobiil-ID)
Контейнер со времени создания претерпел некоторые изменения, сейчас есть версия 1.3 основана на стандарте XAdES-X-L расширенных электронных подписей.
Процесс создания подписи с DigiDocService
Теперь собственно о главном что может понадобится на любом сайте. Допустим вы продаёте рога и копыта и хотите всё юридически правильно оформить. По-старинке это было бы типичный checkbox мол «согласен с условиями». Теперь же можно получить юридически действительную подпись клиента под любым договором, распиской купли-продажи или договора предоставления услуги.
Что-бы это у себя сделать 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-карт
Если опустить очевидное, то процесс в общем выглядит так
- Создание сессии между приложением и digidoc (StartSession)
с передачей инфы о контейнере (который может включать несколько подписываемых файлов) — либо целиком файлы, либо их SHA1-хэш.
Запоминаем вернувшися SessionCode у себя
- Можно запросить с помощью GetSignatureModules сразу готовый html (со всякими апплетами, activex компонентами..) для того что-бы получить сертификаты клиента
- Клиент авторизуется передавая данные серверу, который вызывает PrepareSignature (signCertHex, signCertId), получает обратно бинарный хэш контейнера документов SignedInfoDigest который клиент должен подписать
- Клиент подписывает SignedInfoDigest введя PIN2 — генерируется подпись signValueHex и передаётся в FinalizeSignature(). На этом моменте digidoc проверяет действительность сертификата пользователя у OSCP
- В успешном случае можно уже скачать .ddoc файл. Если оригинальные файлы не отсылались, то их в base64-форме внедряют в вернувшийся xml. Сессия закрывается CloseSession.
Авторизацию и подпись можно поставить и с помощью мобильного телефона, где добавляются ещё и сторона оператора (MSSP), но я этот случай здесь не рассматриваю.
О качестве ddservice
Поставляемый php-пакет в качестве примера полон багов и говнокода — написанный под php4 надо постараться что-бы прикрутить его к PEAR под php5, надо увеличить таймаут curl во всех запросах с 4 секунд на что-либо существенное.
У меня в процессе вылетали ошибкииспользовал test-среду вместо live200: Failed to get signature confirmation/notary // используйте live среду вместо test
Validation constraint violation: data type mismatch xsd:string in element 'Sesscode' //приведите сессию в int-тип
Чтение сертификата с ID-карты
- signCertHex — сертификат подписи переведённый из цифрового DER формата в HEX
- signCertId — идентификатор приватного ключа
В качестве компонента 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 года в Opera, очевидно что это будет долго. Впрочем Firefox уже умеет использовать onepin-opensc-pkcs11.dll как с помощью SK'шного хака, так и с помощью плагина EstEID XPCOM v0.4 написанного в RIA и Smartlink. В результате имеем красивую картинку в 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-плагин теперь переехал и выпускается как open-source
Internet explorer - EIDCard / dsiglite2 + idutil
EIDCard.cab компонент судя по существующему коду прост - создаёшь объект, ссылаешься на этот файл, потом через vbscript спрашиваешь сертификат или запрашиваешь подпись digest'а - всё как в Firefox. Что-бы он заработал нормально надо соблюдать следующие заповеди
- Выключить автоматическое удаление сертификатов в ID-card tool если вы под Windows Vista с правами администратора
- Держать объект EIDCard за пределами формы, иначе он не будет доступен вовсе
- При создании подписи 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 и он есть на сайте, но правильней конечно запрашивать его динамически через 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;
}