«Почему всем можно, а мне нельзя?» или реверсим API и получаем данные с eToken



Однажды у нас на предприятии встала задача о повышении уровня безопасности при передаче очень важных файлов. В общем, слово за слово, и пришли мы к выводу, что передавать надо с помощью scp, а закрытый ключ сертификата для авторизации хранить на брелке типа eToken, благо их у нас накопилось определенное количество.

Идея показалась неплохой, но как это реализовать? Тут я вспомнил, как однажды в бухгалтерии не работал банк-клиент, ругаясь на отсутствие библиотеки с говорящим именем etsdk.dll, меня охватило любопытство и я полез ее ковырять.

Вообще, компания-разработчик на своем сайте распространяет SDK, но для этого надо пройти регистрацию как компания-разработчик ПО, а это явно не я. На просторах интернета документацию найти не удалось, но любопытство одержало верх и я решил разобраться во всём сам. Библиотека – вот она, время есть, кто меня остановит?

Первым делом я запустил DLL Export Viewer от NirSoft, который показал мне приличный список функций, экспортируемых библиотекой. Список выглядит неплохо, прослеживается логика и последовательность действий при работе с токенами. Однако одного списка мало, нужно понять какие параметры, в каком порядке передавать и как получать результаты.



Тут-то и пришла пора вспомнить молодость и запустить OllyDbg версии 2.01, загрузить в него библиотеку ccom.dll криптосистемы Крипто-Ком, используемой банк-клиентом и использующей ту самую библиотеку etsdk.dll, и начать разбираться как именно они это делают.

Поскольку исполняемого файла нет, библиотека загрузится с помощью loaddll.exe из комплекта Olly, поэтому о полноценной отладке мы можем и не мечтать. По сути мы будем использовать отладчик как дизассемблер (да, есть IDA, но с ней я никогда не работал и вообще она платная).

Вызываем контекстное меню и выбираем Search for > All intermodular calls, упорядочиваем результат по имени и ищем функции, начинающиеся на ET*, и не находим. Это значит, что библиотека подключается динамически, поэтому в том же списке мы ищем вызовы GetProcAddress, просматриваем их и с определенной попытки натыкаемся на попытку узнать адрес функции ETReadersEnumOpen, а присмотревшись чуть дальше видим загрузку в память адресов всех функций из библиотеки etsdk.dll.



Неплохо. Полученные адреса функций сохраняются в память командами типа MOV DWORD PTR DS:[10062870],EAX, выделяем каждую такую команду, вызываем контекстное меню и выбираем Find references to > Address constant. В открывшемся окне будут показаны текущая команда и все места вызова функции. Пройдемся по ним и проставим комментарий с именем вызываемой функции – этим мы облегчим себе дальнейшую жизнь.

Пришло время выяснить, как правильно вызывать эти функции. Начнем с начала и изучим получение информации о считывателях. Переходим к месту вызова функции ETReadersEnumOpen и, благодаря оставленным комментариям, видим, что ETReadersEnumOpen, оба ETReadersEnumNext и ETReadersEnumClose сосредоточились в одной функции – очевидно, она, среди прочего, занимается получением списка считывателей.

Все функции используют соглашение о вызове cdecl. Это значит, что результат будет возвращаться в регистре EAX, а параметры передаваться через стек справа-налево. Кроме того, это значит, что все параметры имеют размерность двойного слова, а если не имеют – расширяются до него, что упростит нам жизнь.

Посмотрим окрестности вызова ETReadersEnumOpen:



Передается один параметр, представляющий собой указатель на некую локальную переменную, а после вызова, если результат не равен 0, управление передается на некий явно отладочный код, а если равен – идем дальше (команда JGE передает управление если флаги ZF и OF равны, а флаг OF команда TEST всегда сбрасывает в 0). Таким образом, я заключаю следующий порядок: в функцию передается переменная по ссылке, в которую вернется некий идентификатор перечисления, а как результат функция возвращает код ошибки или 0 если ошибки нет.

Переходим к ETReadersEnumNext:



В нее передаются два параметра: значение переменной, полученное с помощью ETReadersEnumOpen (идентификатор перечисления) и указатель на локальную переменную, куда, очевидно, возвращается очередное значение. Причем поскольку параметры передаются в порядке справа-налево, именно первый параметр – идентификатор, а второй – указатель результата. Код ошибки все так же возвращается через EAX, причем, судя по конструкции цикла, он используется не только для сообщения об ошибке, но и для сообщения о том, что больше перечислять нечего.

С ETReadersEnumClose все еще проще: в нее передается идентификатор перечисления, ну а результат никого не волнует.



Пришло время проверить наше представление об этих функциях. Тут я вынужден сделать небольшое лирическое отступление: дело в том, что по профессии я – сисадмин, и поэтому серьезные компилируемые языки программирования – это не совсем мое. По работе мне больше нужен Bash и Python под Linux, ну а если мне надо быстро что-нибудь сваять под Windows, я использую полюбившийся мне AutoIt.

Плюсами для меня являются:

  • мобильность (интерпретатор и редактор скриптов полностью portable),
  • простая работа с GUI,
  • возможность, если недостаточно функционала, подключать внешние библиотеки (знаю, что тривиально для языка программирования, но не так уж тривиально для скриптового языка),
  • возможность скомпоновать скрипты в исполняемые файлы.

Минусы:

  • Неявное преобразование типов и недостаточное количество представленных типов.
  • Отсутвие записей (а также ассоциативных массивов) и ООП (вообще оно есть, но только для COM-объектов, так что как бы и нету).

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

Приступим: откровенно говоря, мы понятия не имеем, что и какого размера возвращают функции, поэтому будем отдавать большой буфер для начала, и посмотрим, что будет. Код для начала:

Dim $ETSdkDll=DllOpen('etsdk.dll')
Dim $buf=DllStructCreate('BYTE[32]')

Func PrintBuf($buf)
	For $i=1 To DllStructGetSize($buf)
		ConsoleWrite(Hex(DllStructGetData($buf,'buf',$i),2)&' ')
	Next
	ConsoleWrite(@CRLF)
EndFunc

ConsoleWrite('Buffer before: ')
PrintBuf($buf)
$result=DllCall($ETSdkDll,'DWORD','ETReadersEnumOpen', _
	'PTR',DllStructGetPtr($buf) _
)
ConsoleWrite('Buffer after:  ')
PrintBuf($buf)
ConsoleWrite('Return value: '&$result[0]&@CRLF)


Выполнив его, получаем вывод типа такого:

Buffer before: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
Buffer after:  44 6F C8 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
Return value: 0

Прогоняем несколько раз и видим, что меняются только первые 4 байта, значит, в качестве идентификатора используется 4-байтовое целое, а значит мы можем немного причесать код вызова этой функции до такого состояния:

Func ETReaderEnumOpen()
	Local $id=DllStructCreate('DWORD')
	Local $result=DllCall($ETSdkDll,'DWORD','ETReadersEnumOpen', _
		'PTR',DllStructGetPtr($id) _
	)
	Return $result[0]?0:DllStructGetData($id,1)
EndFunc

Подобные эксперименты с функцией ETReadersEnumNext показали следующее: первые 260 байт буфера содержат имя считывателя и нули. Последовательный вызов этой функции перечислил мне все считыватели в системе (например, под ruToken их создано заранее три штуки). Считыватели под eToken создаются динамически, в зависимости от числа подключенных токенов и, самое интересное, у них установлен в еденицу 261-й байт буфера, который, судя по всему, указывает на совместимость считывателя с нашей библиотекой. Если вглядеться в дизассемблированный код, то видно, что записи, у которых 261-й байт равен 0, не обрабатываются. Все остальные байты до конца килобайтного буфера у всех считывателей равны 0 и не различаются.

Итак, со считывателями разобрались, теперь надо понять что дальше. Осмотрев список функций, я пришел к выводу, что последовательность вызова должна быть следующей: сначала делаем bind нужного считывателя, на этом этапе можем узнать общую информацию о вставленном токене, потом делаем логин, и уже после этого получаем доступ к файловой системе. Таким образом, следующие на очереди функции ETTokenBind и ETTokenUnbind.



ETTokenBind выглядит сложно и непонятно, но, поковырявшись некоторое время, я пришел к выводу, что функции передается два параметра, первый из который – указатель на буфер величиной 328 байт (0x0148), а второй – указатель на строку с именем считывателя. Путем экспериментов было установлено, что в первые четыре байта буфера возвращается идентификатор (далее: идентификатор привязки). Для чего выделяется весь остальной буфер – пока загадка. С какими токенами я бы не экспериментировал, остальные 324 байта буфера оставались заполнены нулями. Указанный идентификатор, что логично, успешно используется как аргумент функций ETTokenUnbind и ETTokenRebind.



Следующая функция на очереди – ETRootDirOpen. Принимает три параметра: указатель на результат, идентификатор привязки и константу. У функции есть несколько особенностей.

Первое: возвращаемый результат этой функции проверяется не только на равенство нулю (успех), но и на равенство младших двух байт числу 0x6982, и в случае, если результат равен этому числу, управление передается функции, которая впоследствии вызывает ETTokenLogin, а потом еще раз пытается вызвать ETRootDirOpen. Отсюда можно заключить, что 0x6982 – код ошибки, означающий «Требуется авторизация». Забегая вперед скажу, что все остальные функции, работающие с файлами и папками, устроены так же.

Второе: в качестве одного из параметров эта функция принимает константу 0xF007. Вызовов с другими константами в коде нет. Возможно, эта константа как-то характеризует информацию, записанную на токен (множество корневых папок?). Я попробовал пройти брутфорсом по всем значениям двухбайтовой константы и токен откликнулся только на значения 0x0001, 0xF001-0xF00B (авторизацию, кстати, ни разу не попросил). Позже я выяснил, что на свежеинициализированном токене доступны те же папки. Подумав над этим некоторое время, я пришел к выводу, что по замыслу разработчика, разные корневые папки используются для разных целей, и где-то прописано, что 0xF007 – для ключей.

Третье: значение, возвращаемое функцией, на скриншоте не видно, но возвращается в середину того 328-байтного буфера, который выделялся ранее, из чего можно сделать вывод, что тот буфер – структура, хранящая самые разные идентификаторы и данные, касающиеся рассматриваемого токена.

Раз уж пошла попытка авторизации, время разобраться с ней. Функция ETTokenLogin получает два параметра: идентификатор привязки и указатель на буфер. Сначала я думал, что буфер используется для вывода какого-то результата, однако экспериметы показали, что используется следующий алгоритм: если указатель нулевой или указывает на пустую строку, библиотека рисует интерфейсное окно с запросом пароля, если же он указывает на непустую строку – эта строка используется как пароль. ETTokenLogout воспринимает всего один параметр: идентификатор привязки.

Следующая группа функций: ETDirEnumOpen, ETDirEnumNext и ETDirEnumClose. Их можно попробовать распутать, не заглядывая в код. В общем и целом они должны работать так же, как ETReadersEnum*, с той лишь разницей, что в ETDirEnumOpen будет передаваться в качестве параметра еще и идентификатор текущей папки. Проверяем – работает.

Группа функций ETFilesEnumOpen, ETFilesEnumNext и ETFilesEnumClose просто обязаны работать так же, однако проверить это с уверенностью мы пока не можем, т.к. в корневой папке исследуемого токена, судя по всему, файлов нет, а это значит, что пора уходить вглубь дерева папок, функцией ETDirOpen.



В данном API, похоже, нарисовалась традиция, согласно которой, первый параметр используется для возврата результата, поэтому предположим, что это верно и в этот раз. Второй параметр, прежде чем быть переданным функции, проходит видоизменения с помощью команды MOVZX EDI,DI, т.е. слово расширяется до двойного слова. Очевидно, это нужно для того, чтобы двухбайтовое имя папки передать в четырехбайтовом параметре. Ну а третий параметр по логике вещей должен быть идентификатором открытой папки. Пробуем – получилось. ETDirClose угадывается без сюрпризов: 1 параметр – идентификатор папки.

Итак, мы узнали достаточно, чтобы перечислить все файлы и папки на токене. Следующий простенький код именно это и сделает (описание вызова DllCall я тут не делаю – оно будет для всех функций в тексте модуля в конце статьи):

Func PrintDir($Id,$Prefix)
	Local $EnumId=ETDirEnumOpen($Id)
	While 1
		Local $dir=ETDirEnumNext($EnumId)
		If @error Then ExitLoop
		ConsoleWrite($Prefix&'(dir)'&Hex($dir,4)&@CRLF)
		Local $DirId=ETDirOpen($dir,$Id)
		PrintDir($DirId,$Prefix&@TAB)
		ETDirClose($DirId)
	WEnd
	ETDirEnumClose($EnumId)
	$EnumId=ETFilesEnumOpen($Id)
	While 1
		Local $file=ETFilesEnumNext($EnumId)
		If @error Then ExitLoop
		ConsoleWrite($Prefix&'(file)'&Hex($file,4)&@CRLF)
	WEnd
	ETFilesEnumClose($EnumId)
EndFunc

Local $EnumId=ETReaderEnumOpen()
If $EnumId Then
	While 1
		Local $reader=ETReaderEnumNext($EnumId)
		If @error Then ExitLoop
		If Not $reader[1] Then ContinueLoop
		Local $BindId=ETTokenBind($reader[0])
		ConsoleWrite($reader[0]&':'&@CRLF)
		ETTokenLogin($BindId,'123456')
		Local $DirId=ETRootDirOpen($BindId)
		PrintDir($DirId,@TAB)
		ETDirClose($DirId)
	WEnd
EndIf
ETReaderEnumClose($EnumId)

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

Aladdin Token JC 0:
	(dir)1921
		(dir)DDDD
			(file)0002
			(file)0003
			(file)0004
			(file)0001
		(file)A001
		(file)B001
		(file)C001
		(file)AAAA
		(file)D001

Отлично!

Чтож, мы научились открывать и просматривать папки, пора научиться открывать и читать файлы. ETFileOpen принимает 3 параметра, поэтому для начала пробуем сделать так же, как и для ETDirOpen: результат, имя файла, идентификатор папки и обламываемся: разработчики поменяли местами последние два параметра. Ну хоть ETFileClose работает без сюрпризов.

ETFileRead. Самая страшная функция из всех, т.к. воспринимает аж 5 параметров. Куда столько? Попробуем перечислить что нам нужно: откуда читать (файл), куда читать (буфер), сколько читать и начиная откуда читать. Попробуем разобраться что да как:



Как видно, третий параметр, передаваемый в функцию ETFileRead всегда равен 0xFFFF, поэтому я склонен считать, что это – длина считываемого куска данных. Остальные 4 параметра приходят в функцию, названную мной FileReadHere извне в том же порядке. Ниже на рисунке окрестности вызова этой функции. Значение первого параметра берется из памяти по адресу ESI+8. Указатель на этот адрес используется в функции FileOpenHere (названа по тому же принципу) и туда, очевидно, записан идентификатор открытого файла. Второй параметр равен нулю, поэтому его назначаем ответственным за точку начала чтения файла. Третий параметр (четвертый для ETFileRead) какой-то мутный, поэтому его назначим указателем на буфер-результат. Пятый параметр необычен совсем. В него помещается слово из адреса ESI+12, расширяясь до двойного слова – это необычно, т.к. пока что все смещения, которые я видел, были кратны 4 (12 не кратно 4, потому что это 0x12, т.е. 18 в десятичной). Адрес ESI+10 нигде в окрестностях не упоминается, а вот ESI+0C передается в FileGetInfoHere, поэтому придется сначала разобраться с функцией ETFileGetInfo. Она простая, первый параметр – идентификатор файла, второй – указатель на буфер результата. После вызова в буфере меняются 1, 2, 3, 7 и 8 байты. Забегая вперед, скажу, что выяснится, что последние два байта – размер файла. Именно это значение передается в функцию ETFileRead и в функцию, инициализирующую выходной буфер для нее. Первые два байта результата ETFileGetInfo оказались именем файла. Значение третьего я не понял, но он был установлен в 1 только у одного файла на токене. Таким образом, вырисовывается следующий порядок параметров: идентификатор файла, точка начала чтения, максимальное количество считывемых байт, указатель на буфер, размер буфера.

Раз уж мы затронули ETFileGetInfo, надо бы сразу и реализовать ETDirGetInfo: порядок параметров тот же, только участвует идентификатор папки, а не файла. Возвращаемый результат: имя папки по идентификатору.

На этом мы закончили читать с токена, пришло время писать на токен. Начнем с того, чтобы создать папку. Параметры функции ETDirCreate: указатель для результата (очевидно, после создания папка откроется и сюда вернется идентификатор), имя папки, идентификатор родительской папки и 0. Четвертый параметр жестко прописан в коде и я так и не понял, на что он влияет. Папки успешно создаются при любом его значении. ETDirDelete принимает всего 1 параметр, поэтому это, очевидно, идентификатор открытой папки. ETFileCreate воспринимает пять параметров: указатель на результат, аналогично ETDirCreate, идентификатор папки, имя файла, размер файла и пятый параметр. Если пятый параметр установить в ненулевое значение, то при последующем вызове ETFileGetInfo для этого файла, третий байт результата (тот самый, непонятный) будет установлен в 1. Подумав, я провел эксперимент и убедился, что когда атрибут установлен, для доступа к файлу необходимо ввести пароль, если нет, то это не обязательно. Забавно, что на токене, с которым я экспериментировал, такой файл оказался всего один. Надеюсь, что все остальные файлы зашифрованы на ключе из этого. ETFileDelete работает без сюрпризов, аналогично ETDirDelete.

Последняя функция, обращение к которой реализовано в этой библиотеке – ETFileWrite. Принимает 4 аргумента: идентификатор файла, ноль (эксперимент показывает, что это смешение относительно начала файла), указатель на буфер с данными и размер данных. При этом важно помнить, что файл не расширяется. Если сумма смещения и длины файла превышает размер файла, запись не происходит, поэтому если размер файла требуется изменить, файл придется удалять и создавать заново с новым размером.

Далее: если вспомнить таблицу экспорта библиотеки, то в ней есть еще 5 функций, однако их вызов не реализован в данной библиотеке, работающей с СКЗИ Крипто-Ком. На наше счастье, тот же банк распространяет также и библиотеку для работы с СКЗИ Message-Pro – mespro2.dll, которая также может работать с токенами и в ней есть немного больше, а именно – вызов ETTokenLabelGet.



На скриншоте видно, что есть два вызова функции, различающиеся тем, что в первом случае второй параметр равен нулю, а во втором – какому-то числу. Третий параметр всегда указатель, поэтому предположим, что это результат, а первый – было бы логично предположить, что идентификатор связки с токеном. Пробуем запустить с нулем в качестве второго параметра – первые 4 байта в буфере изменились на значение 0x0000000A, т.е. 10, а это как раз длина имени «TestToken» с нулевым байтом в конце. Но если по указателю в третий параметр возврачается двойное слово, получается, указатель на буфер нужного размера надо передавать во второй параметр. Посему заключаем такой порядок: первый раз запускаем функцию так, что второй параметр – нулевой указатель, а третий – указатель на двойное слово. Потом инициализируем буфер нужного размера и запускаем функцию второй раз, при этом второй параметр – указатель на буфер.

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

ETTokenIDGet: 3
ETTokenMaxPinGet: 2
ETTokenMinPinGet: 2
ETTokenPinChange: 2

ETTokenIDGet принимает слишком много параметров для возврата какого-то простого значения, поэтому запустим ее так же, как и ETTokenGetLabel – получается с первой попытки и возвращает строку с номером, написанным на боку токена.

ETTokenMaxPinGet и ETTokenMinPinGet, как раз наоборот, имеют количество параметров, идеальное для возврата однго числового значения. Пробуем первый параметр – идентификатор связки, второй – указатель на число. В результате получаем максимальную и минимально возможные длины пароля, заданные в настройках токена.

ETTokenPinChange, исходя из названия, служит для смены пароля на токен, соответственно, должен бы принимать только идентификатор связки и указатель на строку с новым паролем. Пробуем первый раз, получаем код ошибки 0x6982, который, как мы знаем, означает необходимость выполнить логин на токен. Логично. Повторяем с логином и коротким паролем – получаем ошибку 0x6416. Делаем вывод о том, что длина пароля не соответствует политике. Повторяем с длинным паролем – отрабатывает.

Теперь сводим все функции в один модуль и сохраняем его – будем инклудить в другие проекты. Текст модуля получился такой:

etsdk.au3

;Func ETReadersEnumOpen()
;Func ETReadersEnumNext($EnumId)
;Func ETReadersEnumClose($EnumId)
;Func ETTokenBind($ReaderName)
;Func ETTokenRebind($BindId)
;Func ETTokenUnbind($BindId)
;Func ETTokenLogin($BindId,$Pin='')
;Func ETTokenPinChange($BindId,$Pin)
;Func ETTokenLogout($BindId)
;Func ETRootDirOpen($BindId,$Dir=0xF007)
;Func ETDirOpen($Dir,$DirId)
;Func ETDirCreate($Dir,$DirId)
;Func ETDirGetInfo($DirId)
;Func ETDirClose($DirId)
;Func ETDirDelete($DirId)
;Func ETDirEnumOpen($DirId)
;Func ETDirEnumNext($EnumId)
;Func ETDirEnumClose($EnumId)
;Func ETFileOpen($File,$DirId)
;Func ETFileCreate($File,$DirId,$Size,$Private=0)
;Func ETFileGetInfo($FileId)
;Func ETFileRead($FileId)
;Func ETFileWrite($FileId,$Data,$Pos=0)
;Func ETFileClose($FileId)
;Func ETFileDelete($FileId)
;Func ETFilesEnumOpen($DirId)
;Func ETFilesEnumNext($EnumId)
;Func ETFilesEnumClose($EnumId)
;Func ETTokenLabelGet($BindId)
;Func ETTokenIDGet($BindId)
;Func ETTokenMaxPinGet($BindId)
;Func ETTokenMinPinGet($BindId)

Const $ET_READER_NAME=0
Const $ET_READER_ETOKEN=1
Const $ET_FILEINFO_NAME=0
Const $ET_FILEINFO_PRIVATE=1
Const $ET_FILEINFO_SIZE=2

Dim $ETSdkDll=DllOpen('etsdk.dll')

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('BYTE[8]')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirGetInfo', _
		'DWORD',$DirId, _
		'PTR',DllStructGetPtr($Out) _
	)
	Return $CallRes[0] _
		?SetError($CallRes[0],0,False) _
		:DllStructGetData($Out,1)
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[$ET_FILEINFO_SIZE]&']')
	Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileRead', _
		'DWORD',$FileId, _
		'DWORD',0, _
		'DWORD',0xFFFF, _
		'PTR',DllStructGetPtr($Out), _
		'DWORD',$FileInfo[$ET_FILEINFO_SIZE] _
	)
	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


Итак, мы можем делать все, что захотим с файловой системой токена. Чтобы продемонстрировать это, я написал простенький скрипт, который будет копировать содержимое с одного токена на другой. Скрипт уровня «Proof-of-concept», т.е. тут не будет уймы проверок, которые должны были бы быть в «правильном» приложении, однако позволит нам получить второй действующий токен.

eTokenCopy.au3
#include <etsdk.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#NoTrayIcon

Opt('MustDeclareVars',1)
Opt('GUIOnEventMode',1)
Opt('GUIDataSeparatorChar',@LF)
Const $Title='eToken Copy'
Const $GUISize[2]=[250,100]

Dim $SrcCtrl,$DstCtrl,$ListTimer

Func TokenCopyDir($SrcId,$DstId)
	Local $Name,$SrcSubId,$DstSubId,$SrcInfo,$SrcData
	; Проход по папкам с рекурсией
	Local $EnumId=ETDirEnumOpen($SrcId)
	While 1
		$Name=ETDirEnumNext($EnumId)
		If @error Then ExitLoop
		$SrcSubId=ETDirOpen($Name,$SrcId)
		$DstSubId=ETDirOpen($Name,$DstId)
		If @error Then
			$DstSubId=ETDirCreate($Name,$DstId)
		EndIf
		TokenCopyDir($SrcSubId,$DstSubId)
		ETDirClose($SrcSubId)
		ETDirClose($DstSubId)
	WEnd
	ETDirEnumClose($EnumId)
	; Проход по файлам
	$EnumId=ETFilesEnumOpen($SrcId)
	While 1
		$Name=ETFilesEnumNext($EnumId)
		If @error Then ExitLoop
		$SrcSubId=ETFileOpen($Name,$SrcId)
		$SrcInfo=ETFileGetInfo($SrcSubId)
		$DstSubId=ETFileOpen($Name,$DstId)
		If Not @error Then
			ETFileDelete($DstSubId)
		EndIf
		$DstSubId=ETFileCreate($Name,$DstId,$SrcInfo[$ET_FILEINFO_SIZE],$SrcInfo[$ET_FILEINFO_PRIVATE])
		ETFileWrite($DstSubId,ETFileRead($SrcSubId))
		ETFileClose($SrcSubId)
		ETFileClose($DstSubId)
	WEnd
	ETFilesEnumClose($EnumId)
EndFunc

Func TokenCopy()
	Local $Src=GUICtrlRead($SrcCtrl)
	Local $Dst=GUICtrlRead($DstCtrl)
	If $Src=='' Or $Dst=='' Then
		MsgBox(0x10,$Title,'Не все поля заполнены')
		Return False
	EndIf
	; Из выбранного поля получаем номер токена
	$Src=StringMid($Src,StringLen($Src)-8,8)
	$Dst=StringMid($Dst,StringLen($Dst)-8,8)
	If $Src==$Dst Then
		MsgBox(0x10,$Title,'Нельзя выбрать один и тот же токен')
		Return False
	EndIf
	; Подключаемся к токенам
	Local $SrcBindId=False,$DstBindId=False
	Local $EnumId=ETReadersEnumOpen()
	While 1
		Local $Reader=ETReadersEnumNext($EnumId)
		If @error Then ExitLoop
		If Not $Reader[$ET_READER_ETOKEN] Then ContinueLoop
		Local $BindId=ETTokenBind($Reader[$ET_READER_NAME])
		If ETTokenIDGet($BindId)==$Src Then
			$SrcBindId=$BindId
		ElseIf ETTokenIDGet($BindId)==$Dst Then
			$DstBindId=$BindId
		Else
			ETTokenUnbind($BindId)
		EndIf
	WEnd
	ETReadersEnumClose($EnumId)
	If Not ETTokenLogin($SrcBindId) Then
		MsgBox(0x10,$Title,'Ошибка авторизации на токене-источнике')
		Return False
	EndIf
	If Not ETTokenLogin($DstBindId) Then
		MsgBox(0x10,$Title,'Ошибка авторизации на токене-назначении')
		Return False
	EndIf
	; Запуск копирования
	TokenCopyDir(ETRootDirOpen($SrcBindId),ETRootDirOpen($DstBindId))
	ETTokenUnbind($SrcBindId)
	ETTokenUnbind($DstBindId)
	MsgBox(0x40,$Title,'Копирование завершено')
EndFunc

Func GetTokenList()
	Local $Reader, $BindId, $Result=''
	Local $EnumId=ETReadersEnumOpen()
	While 1
		$Reader=ETReadersEnumNext($EnumId)
		If @error Then ExitLoop
		If Not $Reader[$ET_READER_ETOKEN] Then ContinueLoop
		$BindId=ETTokenBind($Reader[$ET_READER_NAME])
		$Result&=@LF&ETTokenLabelGet($BindId)&' ('&ETTokenIDGet($BindId)&')'
		ETTokenUnbind($BindId)
	WEnd
	ETReadersEnumClose($EnumId)
	Return $Result
EndFunc

Func UpdateTokenList()
	Local $Tokens=GetTokenList()
	GUICtrlSetData($SrcCtrl,$Tokens,GUICtrlRead($SrcCtrl))
	GUICtrlSetData($DstCtrl,$Tokens,GUICtrlRead($DstCtrl))
EndFunc

Func onClose()
   Exit
EndFunc

Func GUIInit()
	GUICreate($Title,$GUISize[0],$GUISize[1],(@DesktopWidth-$GUISize[0])/2,(@DesktopHeight-$GUISize[1])/2)
	GUISetOnEvent($GUI_EVENT_CLOSE,'onClose')
	GUICtrlCreateLabel('Источник:',8,8,64,-1,$SS_RIGHT)
	GUICtrlCreateLabel('Назначение:',8,32,64,-1,$SS_RIGHT)
	$SrcCtrl=GUICtrlCreateCombo('',76,6,$GUISize[0]-84,-1)
	$DstCtrl=GUICtrlCreateCombo('',76,30,$GUISize[0]-84,-1)
	GUICtrlCreateButton('Копировать',8,54,$GUISize[0]-16,$GUISize[1]-62)
	GUICtrlSetOnEvent(-1,'TokenCopy')
	GUISetState(@SW_SHOW)
EndFunc

GUIInit()
UpdateTokenList()
$ListTimer=TimerInit()
While 1
	; Обновление списка токенов раз в 3 секунды
	If TimerDiff($ListTimer)>3000 Then
		UpdateTokenList()
		$ListTimer=TimerInit()
	EndIf
	Sleep(100)
WEnd




Я попробовал все СКЗИ, до которых смог дотянуться: Крипто-Ком, Крипто-Про, Message-Pro, Сигнатура и даже Верба. Все эти ключи успешно прошли копирование и работали.

Но как же так? Разве не должны ключи быть неизвлекаемыми с токена? Ответ кроется в спецификациях eToken: дело в том, что неизвлекаемый ключ действительно есть, но служит он только для криптопреобразований с помощью алгоритма RSA. Ни одно из рассмотренных СКЗИ… нет, вот так: ни одно из СКЗИ, одобренных ФСБ для использования на территории РФ (вроде бы) не использует RSA, а все они используют криптопреобразования на основе ГОСТ-*, поэтому eToken – не более чем флэшка с паролем и замысловатым интерфейсом.
Поделиться публикацией

Комментарии 76

    +9
    > поэтому eToken – не более чем флэшка с паролем и замысловатым интерфейсом.
    Меня это тоже удивило в свое время.
      +3
      При выборе токена задавайте производителю самый первый вопрос — извлекаемое или неизвлекаемое хранение?
      И у Аладдина и у Рутокена сейчас есть в прайсе и первого и второго вида, как для RSA так и для ГОСТ-а, в т.ч. нового (2012 года).
      Просто необходимо выбирать для нужных целей нужные модели.
        0
        С одной стороны да, такие решения есть, с другой — бремя выбора токена лежит на производителе ПО, а я пока что не видел ни одного ПО, в том числе и продвигаемого ЦБ, которое могло бы использовать токены JaCarta.
          0
          Райфайзеновский корпоративный КБ сейчас работет с JaCarta нормально
            0
            Федеральная служба по регулированию алкогольного рынка РФ — egais.ru работает только с джакартой
              0
              Но… Ключи для производителей на eToken и RuToken, подключится к ЕГАИС производителя (или как его называет ФСРАР — «Классический ЕГАИС») с помощью JaCarta нельзя.
                0
                Я про egais.ru, а вы про service.fsrar.ru
          0
          пришли мы к выводу, что передавать надо с помощью scp

          Интересно, почему выбрали scp? Рассматривали ли решения по управляемому файлообмену (MFT)?

          Все эти ключи успешно прошли копирование и работали.

          А как ключи на этих носителях были сгенерированы? использовали какой-то csp?
            0
            Интересно, почему выбрали scp? Рассматривали ли решения по управляемому файлообмену (MFT)?
            Ну, тогда стоят вопрос о том, чтобы сделать быстро и из того, что есть и знакомо.
            Однако сейчас, после того, как я узнал об отсутствии сложностей на пути чтения с токена для злоумышленника, мы снова в поиске.
            Сейчас по запросу «управляемый файлообмен MFT» гуглится в основном OpenTrust MFT, который лично мне показался оверкилом для цели передачи из А в Б.

            А как ключи на этих носителях были сгенерированы? использовали какой-то csp?
            Крипто-Ком: ключи сгенерированы банком, в дальнейшем при перегенерация идет в ActiveX библиотеке (которую я и рассматривал) и новый ключ кладется на токен (в моем примере — в папку DDDD). Также пробовал программу Admin-PKI.
            Message-Pro: аналогично, банком.
            Крипто-Про: ключи генерировались на сайте УЦ «Крипто-Про», насколько я понимаю, тоже посредством ActiveX или Java
            Сигнатура и Верба: соответствующим ПО.
            0
            >ни одно из СКЗИ, одобренных ФСБ для использования на территории РФ (вроде бы) не использует RSA
            Если подсунуть libeTPkcs11.so или его виндовый аналог фаерфоксу или java keytool'у, то вроде можно сгенерировать RSA ключ. Он в таком случае будет неизвлекаемым или токен сгенерирует ГОСТ-овский ключ?
            И есть ли эта замечательная библиотека под Linux (у меня стоит Safenet client, но такой библиотеки нет).

            Также хотелось бы узнать, можно ли загружать свои java card апплеты на токены с поддержкой java. Интересная технология, хочется попробовать.
              +1
              Для работы с неизвлекаемыми ключами, а также апплетами у производителя, видимо, есть другие API, в то время как рассматриваемое в статье используется только для работы с файловой системой.
              Если вы сгенерируете RSA ключ и поместите его в специальную область на токене, то токен сможет этим ключом генерировать подпись для ваших данных, но о ГОСТовских алгоритмах в случае eToken Pro речи не идет.
                0
                В своё время брал eToken Java и нет, загружать туда апплеты нельзя. Техподдержка ответила, что ключи для загрузки они меняют на случайные при инициализации токена и нигде не хранят.
                0
                Есть наработки по реверсу аппаратного ГОСТ-токена из комплекта CryptoPro Rutoken CSP. В кратце сделано практически всё, кроме того, что надо понять как преобразуется пароль от ключа в тот вид, который передаётся на токен. Если есть кто готов присоединиться, то Welcome. Конечная цель поддержка всякой аппаратной ГОСТ-криптотни под линуксом.
                  0
                  dev.rutoken.ru/pages/viewpage.action?pageId=18055184. Работает под Linux
                    –1
                    Во-первых, не хочется пользоваться закрытыми библиотеками. Во-вторых, с помощью openssl не возможно создать сертификат и/или запрос на него, который удовлетворял бы требованиям приказа ФСБ. Проблема в том, что приказ требует наличия доп.полей (таких как СНИЛС, ИНН и т.д.) хитрых типов, а openssl использует дефолтные. В-третьих, Рутокен ЭЦП это совсем не то же самое, что носитель из комплекта CryptoPro Rutoken CSP, второй имеет дополнительную функциональность и защиту каждого отдельного ключа отдельным паролем, а не одним ПИН-кодом на весь брелок. В-третьих, в перспективе планирую поддерждать ещё и УЭК.
                      +2
                      Во-вторых, с помощью openssl не возможно создать сертификат и/или запрос на него, который удовлетворял бы требованиям приказа ФСБ. Проблема в том, что приказ требует наличия доп.полей (таких как СНИЛС, ИНН и т.д.) хитрых типов, а openssl использует дефолтные.

                      Возможно. Через API openssl. Через тулзу openssl возможно, начиная с версии openssl 1.1.0.
                      Мы сейчас готовим совместимую с этой версией сборку engine pkcs11_gost. Заодно в ней будет и поддержка новых криптографических ГОСТ-ов.
                      В-третьих, Рутокен ЭЦП это совсем не то же самое, что носитель из комплекта CryptoPro Rutoken CSP, второй имеет дополнительную функциональность и защиту каждого отдельного ключа отдельным паролем, а не одним ПИН-кодом на весь брелок.

                      Вообще говоря, в Рутокен ЭЦП есть поддержка Secure Messaging-протокола, обеспечивающего защиту канала между Рутокен ЭЦП и библиотекой pkcs11. Если интересны нюансы, то в личку.

                        0
                        > Через API openssl.

                        Это да.

                        > Secure Messaging-протокола, обеспечивающего защиту канала между Рутокен ЭЦП и библиотекой pkcs11. Если интересны нюансы, то в личку.

                        А разве API PKCS #11 позволяет указывать что-то кроме ПИН-кода токена (административного и пользовательского)? Просто не очень понятно, как это заюзать. Либо тут уже библиотека должна через какой-то другой интерфейс запрашивать у пользователя пароль ключа, когда к нему идет обращение.
                        +1
                        А что плохого в закрытых библиотеках, если ключ в любом случае не извлекается? Какой смысл в подмене своей библиотекой одной из частей программно-аппаратного комплекса? Конечно, дело благородное, но с чего вы внезапно взяли, что написанная вами открытая библиотека будет корректно работать с токеном с точки зрения безопасности? Желательно, не с точки зрения интуиции.
                        В частности, упомянутый ранее CryptoPro Rutoken CSP является полноценным ПАК, где токен и программные библиотеки — равнозначно важные сущности.
                        Про УЭК, кстати, интересно. Что значит, «поддержать»? Он ведь проводит ряд защищенных процедур на неопубликованных протоколах (это не значит, что они плохие/кривые/проприетарные), а затем включает шифрованный канал.
                        Совсем забыл небольшой вопрос: если вы «отцепляете» токен от СКЗИ, то зачем вообще будут использоваться ГОСТ-алгоритмы? Купите обычные западные токены и работайте спокойно с виндовыми провайдерами.
                          0
                          > Он ведь проводит ряд защищенных процедур на неопубликованных протоколах

                          Совершенно верно. Идея в том, чтобы это реверснуть. Это не так сложно, как может показаться на первый взгляд.

                          > Совсем забыл небольшой вопрос: если вы «отцепляете» токен от СКЗИ, то зачем вообще будут использоваться ГОСТ-алгоритмы?

                          Послушайте, требование использовать сертифицированные ФСБ СКЗИ — это больше требования для институциональных учреждений (банков, ведомств, мед.учреждений и т.д.) Для меня, как для конечного пользователя, доверие к себе многократно выше, чем доверие к конторе, которая сертифицировала что-то кому-то. Поэтому я вполне могу использовать любую реализацию ГОСТа, и это никак не обнаружит ни один мой контрагент.

                          > Купите обычные западные токены и работайте спокойно с виндовыми провайдерами.
                          Западные токены, к сожалению, не дают мне возможности использовать криптографию для нужд юридически значимых внутри России.
                            +1
                            > Совершенно верно. Идея в том, чтобы это реверснуть. Это не так сложно, как может показаться на первый взгляд.

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

                            > Для меня, как для конечного пользователя, доверие к себе многократно выше, чем доверие к конторе, которая сертифицировала что-то кому-то.

                            Конечно, выше. И это типичная проблема подавляющего большинства тех, кто пытается самостоятельно лезть в разработку криптографии. Не забывайте, пожалуйста, про закон Шнайера.

                            > Западные токены, к сожалению, не дают мне возможности использовать криптографию для нужд юридически значимых внутри России.

                            Осуществить юридически значимые нужды с ГОСТ-криптографией, но несертифицированной, да еще и на коленке сделанной, тоже весьма затруднительно. Приведу пример: для понимания базовых принципов криптоалгоритмов и прокачки навыка разрабоки я иногда даю своим студентам задания в духе «реализуй ГОСТ хххх-хх» или «встрой туда-то реализацию OpenSSL».
                            Хотите сказать, что их поделки, имеющие в коде функции типа gost_encrypt_megasecure, тоже применимы в юридически значимом документообороте?
                              +2
                              Хотите сказать, что их поделки, имеющие в коде функции типа gost_encrypt_megasecure, тоже применимы в юридически значимом документообороте?


                              Вы так говорите, будто подобные поделки не имеют сертификатов ФСТЭК и ФСБ.
                                0
                                Я, конечно, не уверен, но не думаю, что мои студенты сразу после сдачи лаб отправляют их в органы :)

                                А если речь о промышленных разработках, то хотелось бы услышать более адекватные доводы, чем «всем хорошо известно».
                                  +3
                                  > что мои студенты сразу после сдачи лаб отправляют их в органы :)

                                  Разумеется, потому что у них нет ни лицензии на разработку СКЗИ, ни денег на их сертификацию ) А вот мой друг, будучи студентом, сидел и пилил openssl на поддержку ГОСТ, только не увас на лабах, а в лицензированной конторе в Питере. И сертифицировали же потом )
                            0
                            > А что плохого в закрытых библиотеках, если ключ в любом случае не извлекается? Какой смысл в подмене своей библиотекой одной из частей программно-аппаратного комплекса?

                            Ещё один нюанс забыл. Смотрите, если вы пользовались хоть раз какими-то ГосУслугами или системой электронного ДО, где имеется авторизация или подпись документа по ключем, то должны были обратить внимание, что то, что плагин браузера посылает на подпись в токен, совершенно вам не видно. То есть вы вслепую подписываете что-то, надеясь на то, что это то же, что и на странице браузера. Это очень неправильно. Я хочу это ликвидировать, добавив возможность просмотреть подписываемое сторонним приложением, встроившись между плагином и библиотекой токена.
                              0
                              > совершенно вам не видно
                              Для этого есть средства визуализации подписи (см. Rutoken PinPad или SafeTouch). Не нужно городить огороды. Да, в конце концов, если уж так хочется, можно поставить отладчик APDU-инструкций, ловить хэш, отправляющийся на подпись в токен, и его печатать пользователю.
                                0
                                > Для этого есть средства визуализации подписи (см. Rutoken PinPad или SafeTouch).

                                Они поддержаны в Linux? Что делать тем, у кого УЭК или такой носитель как у меня? Повесится от несовершенства мира?

                                > Да, в конце концов, если уж так хочется, можно поставить отладчик APDU-инструкций, ловить хэш, отправляющийся на подпись в токен, и его печатать пользователю.

                                А что собственно даст пользователю значения хэша? Да и какое-то решение из области заката солнца вручную. Неудобство использования инструментов защиты информации играет против их корректного применения. Описанная схема слишком сложна, чтобы средний пользователь мог её безопасно использовать.
                                  0
                                  > Они поддержаны в Linux? Что делать тем, у кого УЭК или такой носитель как у меня? Повесится от несовершенства мира?

                                  Вполне поддержаны. Насчет поддержки УЭК рутокен — точно нет, а для SafeTouch не вижу проблемы. Какой носитель у вас, я не знаю.

                                  > А что собственно даст пользователю значения хэша?

                                  Ну, если хотите, получайте это значение, считайте хэш от тех данных, что подписываете и сравнивайте: примерно так вся эта визуализация и работает.

                                  > средний пользователь мог её безопасно использовать

                                  С чего вы взяли, что к полученной системе будет применимо слово «безопасно»?
                                    0
                                    > Ну, если хотите, получайте это значение, считайте хэш от тех данных, что подписываете и сравнивайте: примерно так вся эта визуализация и работает.

                                    Очень удобно, не правда ли? )

                                    > С чего вы взяли, что к полученной системе будет применимо слово «безопасно»?

                                    Я буду стремиться к тому, что к ней будет применимо слово «безопасней» по сравнению подписью чего-то вслепую. Кроме того, заставлять кого-либо пользоваться этим я не собираюсь.

                                    Только не пытайтесь меня убедить, что сертификаторы ФСБ неимоверно крутые спецы, которые глазами анализируют исходники и ищут в них уязвимости и их сертификция — это клеймо безупречного качества. Я знаю как это делается. По крайней мере делалось некоторое время назад. Это очень формальный и практически бесполезный процесс, который стоит весьма хороших денег.

                                    Ну и наконец, что конкретно вы предлагаете в условиях, когда производителю в основном пофиг на Linux. Тупо сидеть и ничего не делать? Прекрасный план!
                                0
                                > вы вслепую подписываете что-то, надеясь на то, что это то же, что и на странице браузера

                                Так это еще не самое смешное. Самое, как мне кажется, состоит в том, что владелец ЭЦП в том виде, в каком ее нынче выдает большинство (если не все) УЦ, совершенно не причастен к генерации секретного ключа, поскольку получает от УЦ уже готовый криптоноситель.

                                То есть, по сути, это не «подпись», а некий аналог печати. И нет никакого способа проверить, сколько было изготовлено экземпляров этой печати, и где они находятся — все упирается в абсолютное доверие к УЦ и его сотрудникам.
                                  0

                                  Не упирается ничего в доверие. Все упирается в тупость этих людей, которые так делают. Позвоните в ллюбой УЦ, только не тупыми продажникам, а сразу в саппорт, и спросите как сделать сертификат через запрос. Процентов 75 УЦ уже так умеют делать. Просто встаньте на их место. Вот приходит к вам председатель ТСЖ 65 лет отряду (ТСЖ обязаны иметь ЭП) и говорит: сынок, мне бы подпись эту диавольскую заиметь. А ты такой: придерживайтесь отец, смотрите вот Алиса, вот Боб, она пишет ему письмо, а на почте их читает Ева… )))

                                    0
                                    Ну вот я не далее, как вчера, покупал ЭЦП в новосибирском ООО «РУЦ», который аккредитован аж с 2013-го, в отличие от большинства новоявленных. И спросил их специалиста, который непосредственно занимался изготовлением сертификата, каким образом обеспечивается защита секретных ключей, если их генерирует сам УЦ, и почему они не делают, как это принято у серьезных организаций, сертификатов по запросу. Получил ответ, что это прямое требование какого-то там ФЗ, а иначе, мол, невозможно обеспечить защиту и бла-бла-бла. В дискуссию я вдаваться не стал. :)

                                    Из других аккредитованных «не вчера» хотел сперва ткнуться в Департамент информационного развития и отделение Пенсионного фонда, но они выпускают ЭЦП исключительно для госконтор и организаций самоуправления, вроде тех ТСЖ.

                                    Вообще, грустно наблюдать, как под эгидой «информатизации» и «безопасности» тупо пилят бабло, пользуясь дремучестью большинства юзеров.
                                      0
                                      Я знаю только один кейс, когда выработка ключа в УЦ обязательна. Для таможни и чего-то там ещё требуют сертификат, в котором УЦ должен вставить OID, что закрытый ключ к этому сертификату выработан на аппаратном носителе (типа Рутокен ЭЦП 2.0). В этом случае УЦ вырабатывает его сам на этом носителе и генерирует сертификат, но в этом случае как бы есть «гарантия», что закрытый ключ не покинет носитель даже в УЦ.

                                      А не могу коментировать ваш пересказ чьих-то слов, не зная полного диалога. Если вы в Новосибирске, то прибегните к услугам Такском. Они совершенно точно умеют через запрос.
                                        0
                                        Спасибо, в следующий раз попробую. Я искал по списку УЦ, аккредитованных Минсвязи, и по Новосибирску наиболее серьезным показался РУЦ, а посмотреть местные отделения федеральных УЦ я не догадался.

                                        Сейчас уже нет смысла обращаться куда-то еще — сертификат изготовлен, использовать я его буду для отправки отчетности по ИП в ФНС, так что явной опасности компрометации ключей нет.

                                        Кстати, Вы не в курсе, что за странная ситуация с драйверами eToken? Gemalto их свободно не раздает, а УЦ, выдающие сертификаты для подписи кода (DigiCert, GlobalSign и т.п.) почему-то раздают собственные лицензированные версии SafeNet Authentication Client'ов. Чем объясняется такая политика? Или эта область настолько хлебная, что даже драйверы в свободный доступ выложить жалко?
                                          0
                                          И на сколько вас раздел этот самый РУЦ за подпись для ИП, если не секрет?

                                          Касательно гемальты — никогда не пользовался их продуктами и не собираюсь. Там судя по всему полные штаны security by obscurity, от того и поведение такое. Как они сделали электронные паспорта с уязвимостями для, кажется, эстонцев и испанцев, хватает, чтобы всю мощь этой конторы осознать.
                                            0
                                            Не секрет — 2500. :) Полный комплект из подписи, токена и бессрочной лицензии КриптоПро они продают за 4300, вполне по-божески.

                                            Я бы тоже не пользовался, но DigiCert и GlobalSign дают EV-сертификаты для подписи кода только на eToken. Российскую ЭЦП тоже решил записать на eToken, наивно полагая, что драйверы с Authentication Client от GlobalSign уже стоят, останется лишь поставить КриптоПро или VipNet. Однако ж, Authentication Client вообще не видит на этом токене ничего, полагая его чистым, только по паролю авторизуется. Контейнер становится виден только в КриптоПро CSP.

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

                                            Оно все такое, или есть вменяемые решения? Как-то интуитивно казалось, что любой аппаратный ключ должен иметь некую универсальную ФС, универсальный же менеджер для нее, а уже поверх этого должны работать надстройки.
                                              0
                                              Эм… тут вопрос очень многоуровневый. Вся эта хрень, в том числе с плагинами к браузеру для подписи, и вообще кастомных браузеров для SSL по ГОСТу (CryptoFox, Яндекс.Браузер) — это не от хорошей жизни. Чуваки из libnss и разрабы Хрома стоят горой перед любыми попытками включить алгоритмы ГОСТ и соответствующие ciphersuite'ы в библиотеку. Сам многократно вступал с ними в дискуссию. Причем у меня было полное ощущение, что я попал на Russia Today, точнее на USA Today.

                                              Если бы всё это было поддержано популярными библиотеками, то нужды бы в этом не было. Но мы попали в изоляцию. Причем ещё задолго до известных политических событий. Хотя я не сторонник нашего режима, но блин — получатся, что мы окружены )

                                              Далее, касательно содержания токена. Если мы говорим о тупом токене, который хранит в извлекаемом виде ключи, то действительно там есть более-менее стандартная «файловая» система. Но вот содержание файлов не стандартизировано. Каждое СКЗИ может туда писать в своём формате. Очевидно, что тогда другое СКЗИ не будет видеть эту инфу.

                                              А вот если токен претендует на умность — например, аппаратная ЭП или блочное шифрование, то тут уже мыши впляс — каждый вендор во что горазд. Тот же еТокен (если это не узконишевая версия), если я правильно помню умеет все эти RSA/DSA аппаратно делать неизвлекаемым ключем. А ключи ГОСТ он умеет хранить только в тупом виде.
                                                0
                                                А как могла сложиться ситуация, в которой каждое СКЗИ использует свой формат? Насколько я понимаю, на токене хранятся только сертификаты и ключи, форматы которых стандартизированы и устаканены десятки лет назад, одновременно с расширениями соответствующих файлов. По-моему, когда УЦ инициализирует токен с помощью того же КриптоПро, и после этого никто, кроме КриптоПро, не может с этим токеном работать — безусловный и законченный идиотизм. Но такая схема почему-то считается не только допустимой, но еще и «высокопрофессиональной». :)
                                                  0
                                                  Нет, там ещё хранится метаинформация. Как то: имя контейнера, его флаги. Например, флаг того, что ключ «некопируемый» — тогда софтина, уважающая этот флаг, откажется копировать ключ ) Ну всякое такое околомаркетойдное добро. Расширений файлов там нет. «Имена» файлов на файловых системах смарт-карт (а именно так прикидывается обычный токен) числовые. Конкретным числам присваивают конкретную семантику. Это так обычно работает.

                                                  Как так сложилось, что у всех разное — это вопрос не ко мне, это вам сюда. По-моему, я даже видел где-то утилиты для конвертации форматов контейнеров. Но зачем это всё существует не знаю. Возможно, так реализуется право на труд )
                                                    0
                                                    Понятно, что какая-то дополнительная информация хранится. Удивляет прежде всего то, что разработчики не предлагают средств диагностики — как для пользователей, так и для администраторов организаций, в которых используются токены. Если вдруг CSP перестал находить сертификат на токене — как понять, то ли где-то в софте глюк, то ли токен испортился, то ли его кто-то по ошибке (или намеренно) проинициализировал? Это все равно, что в ОС не иметь возможности просмотреть содержимое ФС, чтобы проверить наличие/отсутствие требуемых файлов.

                                                    И политика распространения софта тоже изумляет: можно купить токен в магазине, но софт к нему придется добывать извилистыми путями — или через поставщика сертификата, или через переговоры с производителем, или из левых источников.
                                                      0
                                                      Слушайте, я использую для этих целей Рутокен. Там такой проблемы нет, всё выложено у них на сайте.
                                                        0
                                                        То есть, eToken можно считать неудачным исключением, где все через задницу, или он таки не исключение?
                                                          0
                                                          Я за всю Одессу не готов говорить. Я не занимаюсь этим профессионально. Просто мне не безразличны кое-какие моменты во всей этой околоГОСТовой движухи, и я слежу за темой.

                                                          На сколько мне известно, ключевые носители делают Рутокен, Аладин (теперь Гемальто) и ещё джакарта. Вот про последнюю я вообще ничего не знаю. Остальные два кейса нами с вами покрыты )
                                                            +1
                                                            Спасибо, попробую следующую подпись взять на рутокене, авось не так будет раздражать. :)
                                        0
                                        Вообще было бы весело прийти к этому кренделю в УЦ со своим носителем, который ими не поддерживается и посмотреть, как они будут выпускать на нём сертификат ) У меня такой нестандартный носитель был (Функциональный ключевой носитель из комплекта КриптоПро Рутокен CSP).

                                        Любил я им вымораживать поддержку разных УЦ ) К сожалению, неудачно повернулся в кресле и снёс этот брелок, когда он был вставлен в системник. Покупать новый нету смысла, т.к. с нового года старые ГОСТы превращаются в тыкву, а новых он не умел.

                                        Зато теперь покупаю только «микро» варианты токенов — которые не торчат на 5 сантиметров, будучи вставленными в порт ) И вам советую )
                                          0
                                          Подозреваю, что они сразу бы и заявили, что данный тип не поддерживается, а такое-то положение устава/регламента не обязывает их записывать сертификат в неподдерживаемый носитель. :)

                                          Я, честно говоря, вообще не пользовался бы токенами — абсолютной защиты они не дают (есть трояны, умеющие прикидываться пользователем), но пришлось — тот же GlobalSign принципиально не желает выпускать сертификат в файле. Подозреваю, что есть возможность его как-то обмануть, подменив модули CSP или ссылки на них, но это уже хлопотно, проще смириться.
                                            0
                                            Когда речь про тупой токен, то я тоже не вижу разницы с обычной флэшкой. Когда речь про смарт-токен (с неизвлекаемым ключом), то максимум, что может сделать троян — это подписать моей подписью что-то своё. Ключ выкрасть не получится. Конечно, теоретически может быть такой троян, который получает какой-то документ на подпись через бэкдор и подписывает его. Но, обычно, хакеру требуется больше времени на то, чтобы придумать что делать с информацией, чем на том, чтобы её получить. Так что получив удалённый доступ к смарт-токену — совершенно не факт, что найдётся что-то что нужно будет ей подписать )
                                              0
                                              Недавно где-то видел рассказ о том, как троян подписал за юзера банковскую платежку — разумеется, со своими реквизитами. :)
                                                0
                                                Это ещё что! У нас дело уже дошло до хранения закрытых ключей в облаке )
                                                  +1
                                                  Если те ключи надежно зашифрованы, и расшифровываются только на стороне клиента — это ничем не хуже хранения на сменном носителе или в реестре, как это умеет сертифицированный со всех сторон КриптоПро. :)
                                                    0
                                                    Нет, вы не поняли. Как они там хранятся — хз. Но там не только хранение, там же в облаке и криптопровайдер, который, например, через вэб-интерфейс может подписывать загружаемые в облако документы. То есть доступ к ключам там есть не только у владельца ключа, но и у владельца облака как минимум.

                                                    «Приватный ключ, доступ к которому имеют более одного человека, называется публичным ключом» (с) )))
                                                      0
                                                      У-у-у, это жесть, согласен. :)
                          0
                            0
                            Саму дллку не выложите?
                              0
                              Пожалуйста.
                              Возможно, вам потребуется также eToken PKI Client вот отсюда.
                                0
                                Спасибо. Стоит SafeNet client, с ним не завелось, говорит об ошибке авторизации на токене — пин код даже не запрашивается. Возможно, оно с ним несовместимо… Вот только параллельно PKI Client не ставится.
                                  0
                                  Как я говорил, в скрипте нет уймы проверок, которые могли бы сказать подробнее.
                                  Возможно не прошел Bind, а возможно, сопоставление Id. Попробуйте повыводить значение разных переменных в консоль с помощью ConsoleWrite($Var&@CRLF)
                                    0
                                    После установки PKI Client завелось.

                                    Но спотыкнулось об ETDirEnumNext — ошибку здесь выдаёт. Не знаю правда какую.

                                    И на ETFilesEnumNext тоже.
                                      0
                                      Токен от какой СКЗИ пытаетесь копировать?
                                        0
                                        Это не от СКЗИ, я пытаюсь считать данные корпоративного токена.

                                        Я прогнал в цикле по всем возможным RootDir — такое ощущение что они все пустые.
                                          0
                                          Возможно, он у вас использует тот самый неизвлекаемый RSA-ключ?
                                            0
                                            На токенах с неизвлекаемым ключем всё равно RootDir обычно не пустой. Там как правило хранится сертификат, открытый ключ и какая-нибудь мета-информация типа имени контейнера или его ID. И файл, в который «записывают» подписываемый хэш, например.

                                            Хотя безусловно, можно избежать концепции файлов полностью, используя полность проприетарный APDU-субпротокол.
                                              0
                                              Честно говоря не знаю. Это eToken Pro 72k (Java)
                                0
                                del
                                0
                                Для копания в eToken была (может всё ещё есть, но, возможно, только у разработчиков) утилита eToken Editor.
                                Также с ключами работал OpenSC.
                                Пробовали такие?



                                  0
                                  Первая входит в пакет разработчика, поэтому у меня ее не было. Вторую не пробовал.
                                    0
                                    Кстати, глядя на ваш скриншот:
                                    В коде часто встречалась строка «AKS ifdh» — так обозначаются смарт-карты, которые не брелки?
                                      0
                                      В данном случае это точно были eToken. Этих идентификаторов могло быть больше, чем реальных устройств. Я для себя понимал это как аналог точки монтирования, чтобы она оставалась после отключения физического устройства и можно было на неё настроить ПО.
                                      0
                                      А у вас не остался этот eToken Editor?
                                        0
                                        Даже если и лежит где-то в архивах, он с вероятностью 99% не работает с современными драйверами и устройствами. Оно должно быть в SDK.
                                          0
                                          Всё же если остался скиньте плз, хочу попробовать. Желательно виндовую версию. Сам етокен достаточно старый, может быть и получится.
                                            0
                                            К сожалению не сохранилось.
                                            Если вы что-то разрабатываете для организации или занимаетесь интеграцией, запросите у Aladdin SDK, кажется его бесплатно отдают (но не уверен, что всем).
                                      0
                                      Интересно, после этого компании закроются, или всем всё пофиг и «ключи безопаснее пароля, продолжайте кушать кактус»?

                                      С другой стороны, возможность выковырять ключ открывает волшебные возможности к написанию софтового эмулятора, позволяющего засунуть всё в виртуалку и не париться.
                                        0
                                        А что такого здесь сенсационного? Те, кто плотно занимается криптографией, знали этот факт с незапамятных времен. Никто никого не обманывает. Сотрудники обеих компаний честно отвечают на правильно поставленные вопросы.
                                          0
                                          Чем тогда токен отличается от дискетки с файлом и ради чего весь сыр-бор?
                                            0
                                            В данном конкретном случае наличием системы разграничения доступа. Потеря дискеты влечет компрометацию ключа, а для получения ключа с токена нужно еще подобрать пароль, на что даётся ограниченное число попыток.
                                        0
                                        Думаю, прежде чем писать подобные «разгромные» статьи, следует разобраться в мат.части, тем более, что она уже неоднократно обсуждалась на этом ресурсе.
                                        Смарт-карты (так же как и USB-ключи) имеют защищённое хранилище, в котором лежат закрытые ключи, сгенерированные непосредственно на смарт-карте, некоторые смарт-карты позволяют импортировать в данное защищённое хранилище ключи, сгенерированные за пределами смарт-карты. При этом, ключи могут быть как RSA, так и ГОСТ (например, Gemalto CryptoPro ФКН, JaCarta ГОСТ, ruToken — не помню точно как называется модель). То есть если ключ сгенерирован на смарт-карте, то он никогда эту смарт-карту не покидает и является неизвлекаемым. И вся работа с закрытым ключом происходит только внутри смарт-карты.
                                        Но исторически сложилось так, что на Российском рынке сначала появились ключи, которые умели только RSA и совсем не умели ГОСТ. При этом, компании — разработчики крипто-провайдеров решили использовать данные ключи как флешку с пин-кодом. То есть, те же Крипто Про, СогналКом, ЛИССИ и другие генерируют криптопару (открытый и закрытый ключи) на компьютере и далее кладут криптоконтейнер на смарт-карту в её файловое хранилище (доступ к которому Вы и получили). При этом, при работе, криптоконтейнер (вместе с закрытым ключом) копируется на компьютер, распаковывается и используется для вычислений. При таком подходе, в момент работы с закрытым ключом, он находится в памяти компьютера в открытом виде.
                                        Таким образом, неизвлекаемость ключа обеспечивается его правильной генерацией.
                                        А если Вы работаете с продуктом, который использует смарт-карту как обычную флешку с пин-кодом, тогда она и будет выполнять роль флешки. И не понимаю чем вызвано подобное удивление.
                                        Как мне всегда говорили старшие коллеги — RTFM!

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое