воскресенье, 16 октября 2016 г.

C#: Двухфакторная аутентификация без смартфона

Возникла тут интересная проблема. Если вкратце, то одна из "программ-от-знакомства-с-которыми-нельзя-отказаться" после смены версии вдруг вообразила себя крутой финансовой системой и затребовала двухфакторную аутентификацию. Не знаю, чем уж там разработчики соблазнили заказчиков, но теперь после ввода логина-пароля эта чудесная софтина показывает некий QR-код и предлагает пользователю установить на смартфон Google Authenticator, которому надо скормить этот её QR-код, чтобы этот GA начал генерировать PIN-коды, которые теперь будут дополнительно требоваться при входе. Короче, если хочешь поработать, покупай смартфон.

Меня, гордого пользователя Samsung SGH-X100 с периодически отваливающимся от старости аккумулятором, такое положение дел устроить никак не могло. Поэтому пришлось познакомиться с этим Google Authenticator-ом поближе. Как я понял, механизм тут такой.

Пусть есть клиент и сервер, и этот сервер хочет предоставить клиенту дополнительную защиту от взлома, даже если у этого самого клиента уведут пароль. Сервер предоставляет клиенту при первом входе некое уникальное число X0, которое клиент запоминает у себя и бережно хранит.

Когда клиент хочет аутентифицироваться на сервере, он берёт это число X0, текущее время T и применяет к ним описанную в стандарте детерминированную функцию F(X0, T), которая на выходе выдаёт PIN-код, передаваемый серверу. Сервер, в свою очередь, проделывает у себя ту же операцию, сравнивает полученный и рассчитанный PIN-коды и, если они совпадают, позволяет клиенту работать.

Как видим, алгоритм сильно зависит от того, насколько хорошо синхронизированы часы на сервере и у клиента. Чтобы упростить задачу, в алгоритме используется не количество, например, миллисекунд, прошедших с начала эпохи UNIX, а количество 30-секундных интервалов. То есть, допускается разбег между часами клиента и сервера в пределах 30 секунд.

Уникальное же число X0 как раз и предоставляется сервером клиенту в виде картинки с QR-кодом - видимо, для пущей безопасности.

Вооружившись этими знаниями, написал простенькую программку, используя которую можно считать этот QR-код с экрана и получать PIN-коды для двухфакторной аутентификации. Ну, то есть, хрен бы я чего написал, если бы не поиск Google, подсказки http://stackoverflow.com и волшебная библиотека MessagingToolkit.QRCode.dll.

Вот тут научился работать с получением всемирного координированного времени по протоколу NTP:
http://stackoverflow.com/questions/1193955/how-to-query-an-ntp-server-using-c

Вот тут рассказали, как получить снимок части экрана:
http://stackoverflow.com/questions/5049122/capture-the-screen-shot-using-net

Вот тут научили работать с base 32:
http://stackoverflow.com/questions/641361/base32-decoding

Вот тут нашелся рабочий код для Google Authenticator:
http://stackoverflow.com/questions/6421950/is-there-a-tutorial-on-how-to-implement-google-authenticator-in-net-apps

Вот тут (кажется) скачал библиотеку MessagingToolkit.QRCode.dll:
http://osdn.net/projects/sfnet_qrreader/downloads/MessagingToolkit.QRCode.dll/

Ну и, собственно, сам результат:

Программа

Исходники

UPD 2017-01-20: Как оказалось, история не закончилась. Нашлись QR-коды, на которых MessagingToolkit сбоит. Например, если QR-код содержит картинку-логотип. Пришлось перетащить проект на другую библиотеку, ZXing.NET

Программа (ZXing)

Исходники (ZXing)

А тут - небольшая памятка, как этой библиотекой пользоваться.

четверг, 13 октября 2016 г.

Python: проблема с подключением к MSSQL при наличии триггера входа

История такова. Жил-был под линуксом скрипт, написанный когда-то на Python 2.7 и использующий pymssql, который успешно подключался к Microsoft SQL Server. В один прекрасный момент подключаться он перестал с ошибкой:
Logon failed for login 'huhmuh' due to trigger execution. DB-Lib error message 20018, severity 14:
General SQL Server error: Check messages from the SQL Server
DB-Lib error message 20002, severity 9:
Adaptive Server connection failed

Расследование выявило интересные вещи. Оказывается, pymssql использовал FreeTDS, который, собственно, и заглючил. Обмен FreeTDS можно подслушать примерно так:
import os
import pymssql

os.environ['TDSDUMP'] = 'stdout'

conn = pymssql.connect(host='mySQLserver', user='huhmuh', password='', charset='UTF-8', database='master', appname='my Application')
conn.close()
Анализ дампа выявил, что используется FreeTDS версии 0.91. Обновление до версии 1.00 не помогло.

При этом в соседнем каталоге лежит другая программа, написанная на C, которая через unixODBC получает данные с того же сервера без всяких проблем. Дальнейшие раскопки показали, что этот самый unixODBC взаимодействует с сервером через Microsoft SQL Server Native Client.

В конце концов оказалось, что на сервере разработчики добавили триггер входа. При наличии этого триггера FreeTDS не может нормально подключиться к базе данных.

Как перенацелить pymssql на использование нативного клиента, найти не удалось, поэтому пришлось перетаскивать скрипт на pyodbc. Тут тоже нашлась пара подводных камней. Самый крупный из которых - ошибка при выполнении пакета команд в одном запросе: Error: No results. Previous SQL was not a query. В этом случае виноваты, во-первых, всякие сообщения сервера "1 rows affected", которые легко подавить командой SET NOCOUNT ON в начале запроса, и, во-вторых, результаты работы команд print, причудливо раскиданных разработчиками внутри хранимых процедур, используемых в нашем пакете. Эти последние подавить нельзя, поэтому пришлось часть пакетов переписывать в виде группы отдельных запросов.

В общем, тот ещё опыт.