Web-аутентификация с помощью USB-токенов / Хабр

Web-аутентификация с помощью USB-токенов / Хабр Электронная цифровая подпись

«почему всем можно, а мне нельзя?» или реверсим 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.

Читайте также:  Приказ Минфина России от 5 февраля 2021 г. N 14н “Об утверждении Порядка выставления и получения счетов-фактур в электронной форме по телекоммуникационным каналам связи с применением усиленной квалифицированной электронной подписи”

На скриншоте видно, что есть два вызова функции, различающиеся тем, что в первом случае второй параметр равен нулю, а во втором – какому-то числу. Третий параметр всегда указатель, поэтому предположим, что это результат, а первый – было бы логично предположить, что идентификатор связки с токеном. Пробуем запустить с нулем в качестве второго параметра – первые 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 – не более чем флэшка с паролем и замысловатым интерфейсом.

Ssh-аутентификация при помощи usb-ключа etoken pro

В этой заметке я расскажу вам, как настроить SSH-аутентификацию при помощи USB-ключа eToken.

image

Для этого нам понадобятся:

— Собственно сам USB-ключ eToken Pro (в моем случае это eToken Pro 64k)
— Операционная система Windows с установленными драйверами eToken PKI Client
— Mozilla Firefox с установленным плагином Key Manager
— SSH-клиент с поддержкой смарт-карт. Лично мне нравится PuTTY SC — модифицированная версия PuTTY.

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

Создание открытого/закрытого ключа.

1) Запускаем Firefox и открываем Key Manager (Инструменты -> Key Manager Tool Box -> Key Manager)

image

2) Вводим пользовательский пароль для eToken

image

3) В главном меню Key Manager выбираем «Generate Self-Signed Cert»

image

4) Заполняем поля как показано на рисунке и нажимаем «Generate Self-Signed Cert». Естественно вам необходимо ввести свои данные, такие как город, страна, и fqdn сервера. Также по желанию можно указать дополнительные свойства сертификата — срок действия и т.д.

Читайте также:  криптопро лаборатория

image

5) Выберите нужный токен из списка и нажмите «ОК». Обратите внимание, что если вы генерируете сертификат сразу на токене то экспортировать закрытый ключ потом будет невозможно.

image

6) В главном меню Key Manager выделите только что созданный сертификат, и нажмите кнопку «Export»

image

7) В появившемся окне выберите формат «OpenSSH Pubkey» и нажмите кнопку «ОК»

image

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

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDL9ViU3 /80xZka0G91eoDyNuPT4eb1/8ro0iZEBnRI7 B LwJbWrvqfJxNQluMiGPhftBCao4VjCcby21g0q 1sp42MR1bPD8BgA42ZDuum/sq5gFEM16n g8 bXxtoZ/kF2bPJ4fGsowmdQCc8I7xECcYazz2AG8oZqU9l0anw==

Настройка аутентификации по ключу на сервере.

В качестве сервера в моем случае выступает машина с установленным Debian Lenny.

Вам необходимо убедиться что в конфигурации sshd разрешена аутентификация при помощи публичных ключей. Для этого необходимо в файле «sshd_config» указать значение параметра «PubkeyAuthentication» в «yes».

Затем в файл “~/.ssh/authorized_keys” добавляем наш публичный ключ полученный ранее (одной строкой). Обратите внимание, файл “.ssh/authorized_keys” находится в домашнем каталоге того пользователя, который потом будет логиниться по публичному ключу.

Настройка SSH-клиента PuTTY SC на использование смарт-карт.

PuTTY SC — это модифицированная версия PuTTY. Единственное значительное отличие от оригинальной версии — поддержка смарт-карт.

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

Для использования аутентификации при помощи смарт-карты зайдите в раздел «Connection -> SSH -> Pkcs11», отметьте галочку «Attempt PKCS#11 smartcard auth (SSH-2)», укажите путь к библиотеке eToken — «C:WindowsSystem32eToken.dll», выберите в списке название токена и закрытый ключ, который мы создали ранее.

image

При подключении вас попросят ввести пользовательский пин-код к USB-ключу.

image

Поздравляю, теперь вы можете использовать SSH-аутентификацию при помощи eToken Pro!

Примечание: несомненно, генерировать ключи можно различными способами, используя различное ПО и различные операционные системы. Связка Windows Firefox была выбрана как наиболее простая и понятная.

Еще примечание: по вкусу можно отключить на сервере другие методы аутентификации, оставив только аутентификацию по ключу.

Web-аутентификация с помощью usb-токенов

Развивая тему, начатую

здесь

и

здесь

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

image

Основной задачей алгоритмов, описанных в предыдущих статьях, была защита пароля от перехвата и безопасное хранения секрета (например, хеша пароля) в БД сервера. Однако существует еще одна серьезная угроза. Это небезопасная среда в которой мы используем пароли. Программные и аппаратные кейлогеры, шпионское ПО контролирующее формы ввода браузеров, атака MitM, контролирующая не только протокол аутентификации, но и саму структуру html-страницы, на которой вводится пароль, да и просто сосед подсмотревший за вами представляют угрозу, которой никакая схема парольной аутентификации ничего не сможет противопоставить. Эту проблему решили в свое время придумав многофакторную аутентификацию. Суть ее заключается в том, что для успешной аутентификации надо знать секрет и владеть каким-либо предметом (в нашем случае usb-токен и его пин-код).

Вот что предлагают разработчики средств защиты информации.

USB-токен — аппаратное устройство, умеющее формировать ключевую пару и осуществляющее электронную цифровую подпись, для выполнения операций требует ввод пин-кода. При формировании ЭЦП используется криптография на эллиптических кривых. Не требует установки драйверов, определяется как HID-устройство.

Кроссбраузерный плагин — умеет работать с usb-токеном, имеет программный интерфейс доступа к криптографическим функциям. Не требует административных прав для установки.

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

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

Регистрация:

  1. Клиент генерирует в токене ключевую пару e,d;
  2. Публичный ключ e клиент отсылает на сервер;

image
Аутентификация:

  1. Клиент отсылает серверу логин;
  2. Сервер генерирует RND и отсылает клиенту;
  3. Клиент генерирует RND и отсылает серверу подписанное сообщение (RND-server||RND-client||Server-name);
  4. Сервер проверяет подлинность ЭЦП использую публичный ключ клиента;

image

Для тех кто с недоверием относится к «велосипедам» — погуглить «ISO public-Key Two-pass Unilateral Authentication Protocol».

Как обычно — демонстрация. Для работы необходимо иметь USB-токен и установить плагин. Токены для тестирования можно взять у меня, есть 10 штук — пишите в личку. Купить можно тут.

Как записать на флешку эцп

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

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

Как перенести электронную подпись из реестра ОС на «флешку»:

  1. Соединяем устройство с рабочим компьютером.
  2. Запускаем криптопровайдер.
  3. Выбираем «Сервис».
  4. Жмем «Скопировать».
  5. В окне «Копирование контейнера закрытого ключа» кликаем на «Обзор».
  6. В списке с контейнерами выделяем необходимый – в левой колонке со считывателями должен быть указан «Реестр».
  7. «Ok» –> «Далее».
  8. Откроется форма с электронным полем для ввода PIN-кода. Введите его и нажмите «Ok».
  9. В адресной строке нового окошка укажите путь и (по желанию) имя нового контейнера – он будет создан на «флешке» (в виде папки).
  10. Жмите «Готово».
  11. В окне выбора носителя в левом списке с устройствами выделите латинскую букву, которую ОС присвоила вашей «флешке», в правом – ее имя.
  12. Щелкните «Ok».
  13. В окне аутентификации потребуется ввод нового пароля (к каталогу с секретным ключом на «флешке»). Введите его в строку «Новый пароль», а в поле «Повторите ввод» – подтвердите.

Криптопровайдер вернется к основному окну копирования контейнера секретного ключа.

Как записать на флешку ЭЦП – переносим ее с Рутокен / eToken / JaCarta:

  1. Подключаем устройства к рабочей машине.
  2. Придерживаемся пп. 2 – 5 – см. инструкцию выше.
  3. В списке с контейнерами отмечаем необходимый – в левой колонке должен быть указан Рутокен / eToken / JaCarta.
  4. Следуем пп. 7 – 13.

Примечание. Держите  PIN-код в недоступном для других пользователей месте. Если его забыть или потерять, восстановить доступ к каталогу с секретным ключом станет невозможно.

Как пользоваться электронной подписью с флешки

Работник удостоверяющего центра (далее – УЦ) по обращению заявителя выдает ему средства ЭП. В их перечень входят:

  • открытый (публичный) и закрытый (секретный) ключи пользователя;
  • свой корневой сертификат (далее – КС);
  • (по запросу) защищенный носитель (токен, смарт-карта), на который записываются упомянутые персональные данные.

Возможность приобретения токена/смарт-карты обозначена на сайтах многих УЦ в качестве дополнительной услуги. Почему ее предлагают в качестве выделенной опции, не включая в стандартный пакет, и на какие типы устройств вам могут записать ЦП?

Классический USB-flash-накопитель. В обычный договор о получении ЭП входят только программные средства ее генерации и верификации с последующим их предоставлением заявителю. Аккредитованные УЦ по собственной инициативе не станут переносить персональные данные на носители упомянутого типа – они считаются незащищенными. Сначала заявителя ознакомят с рисками. Только после этого, если клиент будет настаивать, ЦП запишут на его «флешку».

USB-flash-накопитель с безопасным хранилищем. Флешка, «превращенная» в подобие токена. Ее память сегментирована – доступ к участку с личной информацией охраняется  PIN-кодом (или паролем). Вы можете создать такое устройство самостоятельно.

Токен/смарт-карта. На момент написания статьи большинство центров сертификации выдают ЭП именно на них.

Таблица 1 – типы защищенных носителей

ХарактеристикиМодель
Рутокен 1.0Рутокен 2.0, JaCarta ГОСТ/2 ГОСТ, eToken PRO
криптопроцессор
функция генерации открытого ключа ЭП– (при установке личного сертификата в систему используется секретный ключ) (публичный ключ пользователя создается на основе закрытого аппаратными средствами устройства
дополнительная защиталичные данные становятся доступны только после ввода PIN-кода
уровень безопасностисреднийвысокий

Примечание. Сегодня под «флешками» понимают не только обычные USB-flash-накопители, но и токены.

Чтобы начать пользоваться электронной подписью с «флешки», нужно знать, как настроить автоматизированное рабочее место (далее – АРМ). Эксперты рекомендуют владельцам ЦП работать в ОС Windows 7 и выше.

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