Это продолжение темы, поднятой в предыдущем посте о Едином Портале Госуслуг (ЕПГУ) и аппаратных ключах ЭЦП eToken-ГОСТ.
Сначала традиционное описание граблей.
После того, как я получил токен, мне захотелось вытащить из него сертификат ключа, чтобы в случае чего отдавать заинтересованным лицам для проверки моей ЭЦП.
Грабля первая — ни SDK 4.55, ни SDK 5.1 не хотели признавать eToken ГОСТ инициализированным и с наличествующим ключом.
Грабля вторая — КриптоПро 3.6 — тоже. Что странно — в бланке сертификата указывалось, что ключ сгенерирован КриптоПро CSP 3.6.
В поискахистины хоть чего-то, что могло бы помочь доступиться до содержимого токена, я набрел на аладдиновский же плагин JC-Web.
Плагин опознавал токен, выдавал SN, список сертификатов числом 1 с ID=3 и названием «Certificate». Но не более. Попытка скормить PIN, или передать данные на подпись вызывали исключение.
Оставалась последняя надежда — расковырять плагин, используемый ЕПГУ для целей авторизации. По сути тот же JC-Web, только сильно проще.
И я полез на сайт Госуслуг.
Квест номер два оказался сильно проще.
Расковыряв главную страницу портала госуслуг, я выяснил, что у плагина есть 2 основополагающих JS-метода: etgSignData, и etgGetCertificate. Есть еще свойства etgErrorCode, valid и version, но их я рассматривать не буду, ввиду тривиальности последних.
Эти методы в реализации ЕПГУ обернуты функциями, упрощающими доступ к плагину в контексте веб-страницы.
Я честно их скопировал, лишь чуть-чуть подправив.
В итоге получилась вот такая простенькая веб-страничка, позволяющая а) выдернуть сертификат держателя токена (выдается в Base64), и подписать данные. Подпись тоже формируется в виде PKCS#7, завернутого в Base64 кодировку.
Сначала традиционное описание граблей.
Квест номер 1
После того, как я получил токен, мне захотелось вытащить из него сертификат ключа, чтобы в случае чего отдавать заинтересованным лицам для проверки моей ЭЦП.
Грабля первая — ни SDK 4.55, ни SDK 5.1 не хотели признавать eToken ГОСТ инициализированным и с наличествующим ключом.
Грабля вторая — КриптоПро 3.6 — тоже. Что странно — в бланке сертификата указывалось, что ключ сгенерирован КриптоПро CSP 3.6.
В поисках
Плагин опознавал токен, выдавал SN, список сертификатов числом 1 с ID=3 и названием «Certificate». Но не более. Попытка скормить PIN, или передать данные на подпись вызывали исключение.
Оставалась последняя надежда — расковырять плагин, используемый ЕПГУ для целей авторизации. По сути тот же JC-Web, только сильно проще.
И я полез на сайт Госуслуг.
Квест номер 2
Квест номер два оказался сильно проще.
Расковыряв главную страницу портала госуслуг, я выяснил, что у плагина есть 2 основополагающих JS-метода: etgSignData, и etgGetCertificate. Есть еще свойства etgErrorCode, valid и version, но их я рассматривать не буду, ввиду тривиальности последних.
Эти методы в реализации ЕПГУ обернуты функциями, упрощающими доступ к плагину в контексте веб-страницы.
Я честно их скопировал, лишь чуть-чуть подправив.
В итоге получилась вот такая простенькая веб-страничка, позволяющая а) выдернуть сертификат держателя токена (выдается в Base64), и подписать данные. Подпись тоже формируется в виде PKCS#7, завернутого в Base64 кодировку.
Код страницы-примера для работы с eToken ГОСТ через плагин ЕПГУ
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> <!-- Для ознакомительного и информационного использования --> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Пример использования eToken ГОСТ через плагин ЕПГУ</title> </head> <body style="font-size: 11px; font-family: Verdana;"> <!-- объект плагина ЕПГУ --> <object id="etoken" type="application/x-csuser" width="0" height="0" style="overflow: hidden; float: left;"> <!--<param name="onload" value="pluginLoaded" />--> </object> <script type="text/javascript" language="javascript"> /** * Operations with eToken GOST using CSuser plugin * **/ // функция подписывания данных токеном. первый параметр - строка данных, второй - ПИН-код токена // в поле 1 возвращается ЭЦП (PKCS#7, закодированное в Base64), в поле 5 - код ошибки window.SignDataByEToken = function(mess, pin) { var plugin = eTokenPlugin(); if (plugin.valid) { //возвращаемые данные var return_array = new Object(); return_array[1] = ""; //cms(base64) return_array[5] = ""; //errorCode if (mess == "") { alert("Нет данных для подписывания"); return return_array; } try { // Вывод CMS return_array[1] = eTokenPlugin().etgSignData(1, 1, pin, mess, 0); if (eTokenPlugin().etgErrorCode == 28) return_array[1] = eTokenPlugin().etgSignData(1, 99, pin, mess, 0); } catch (e) { alert("Невозможно подписать данные\r\n" + e.description); return return_array; } try { // Проверка ошибки подписи return_array[5] = eTokenPlugin().etgErrorCode; } catch (e) { alert(e.description); return return_array; } return return_array; } } // функция доступа к сертификату владельца. первый параметр - ПИН-код токена. Возвращает сертификат в Base64 window.GetCertificateByEToken = function(pin) { var cert = null; try { if (eTokenPlugin().valid) { cert = eTokenPlugin().etgGetCertificate(1, 1, pin); if (eTokenPlugin().etgErrorCode == 28) { cert = eTokenPlugin().etgGetCertificate(1, 99, pin); } } } catch (e) { alert(e.description); } return cert; } // глобальный аксессор к плагину window.eTokenPlugin = function() { return document.getElementById("etoken"); }; // валидация версии плагина. в данном примере не используется window.checkPluginVersion = function(version) { if (!(eTokenPlugin() && eTokenPlugin().valid)) return false; var plugin_version = eTokenPlugin().version.split('.'); var portal_version = version.split('.'); if (isNaN(parseInt(plugin_version[0]))) return false; if (isNaN(parseInt(plugin_version[1]))) return false; if (isNaN(parseInt(plugin_version[2]))) return false; if (isNaN(parseInt(portal_version[0]))) return false; if (isNaN(parseInt(portal_version[1]))) return false; if (isNaN(parseInt(portal_version[2]))) return false; if (parseInt(plugin_version[0]) > parseInt(portal_version[0])) return true; if (parseInt(plugin_version[0]) < parseInt(portal_version[0])) return false; if (parseInt(plugin_version[1]) > parseInt(portal_version[1])) return true; if (parseInt(plugin_version[1]) < parseInt(portal_version[1])) return false; if (parseInt(plugin_version[2]) == 11 && parseInt(portal_version[2]) == 9) return false; //9>11 O_o if (parseInt(plugin_version[2]) > parseInt(portal_version[2])) return true; if (parseInt(plugin_version[2]) < parseInt(portal_version[2])) return false; return true; } // собственно, начинка этого документа function doLogin() { var PIN = document.getElementById("pin").value; var rd = document.getElementById("cleartext").value; var cert = GetCertificateByEToken(PIN); var ds = SignDataByEToken(rd, PIN); var dstext = ""; document.getElementById("cert").value = cert; for (name in ds) { dstext = dstext + name + " : " + ds[name] + "\r\n"; } document.getElementById("dsig").value = dstext; } </script> <!-- UI --> <div> <a id="btnLogin" onclick="doLogin();" style="border : solid 1px black; width : 140px; height : 40 px;" href="#">Выполнить</a><br/><br/> <b>PIN-код</b><br/> <input type="password" id="pin" style="width : 250px; border : solid 1px black;"/><br/><br/> <b>Данные</b><br/> <input type="text" id="cleartext" style="width : 250px; border : solid 1px black;"/><br/><br/> <b>Сертификат</b><br/> <input type="text" id="cert" style="width : 250px; border : solid 1px black;"/><br/><br/> <b>ЭЦП</b><br/> <textarea id="dsig" style="width : 600px; height : 300px; border : solid 1px black;"></textarea><br/><br/> </div> </body> </html>
