Здравствуйте, господа программисты и не только. Снова наш веселый Отдел Перспективных Разработок выходит на связь с новой заметкой, посвященной очередной бесполезной, но, интересной для весьма узких кругов теме.
Сегодня речь пойдет о реализации генератора TOTP, то есть, алгоритма создания одноразовых паролей для защищенной аутентификации на малопригодном для этого устройстве.
Запалом послужила вот эта статья на Хабре, посвященная созданию генератора паролей на базе Commodore 64. Жаль, что это перевод иностранного материала, тем не менее, мы перехватим эстафетную палочку и создадим генератор на чём-нибудь таком же бесполезном в наше время, что есть под рукой. Например, на компьютере Z80-MBC2 с процессором Z80 на борту.
Синопсис
Компьютер Z80-MBC2 уже описывался мной в предыдущих статьях. Это минималистичная поделка на четырех микросхемах и паре блоков (часы и ридер SD-карт с образами дискет), работающий на ОС, совместимых с процессором z80. Я предпочитаю использовать CP-M 3.0, под него и буду собирать приложение. Скорее всего, это все заведется и на CP-M 2.2.
Поскольку CP-M – [была] довольно известная ОС, на нее не представляет труда найти компилятор С и не возиться с ассемблером, что [хоть и не так круто, но] здорово облегчит нам задачу. К компьютеру даже принято поставлять образ дискеты с компилятором Hi-Tech-C.
Отладку я проводил на linux-машине, собирая приложение под gcc и, одновременно, на том же Hi-Tech C под эмулятор CP-M.
Помимо этого, под Linux и Windows для особых ценителей есть и эмулятор самого Z80-MBC2.
Для операций с образами дискет можно применить утилиту CPMToolsGUI, которая позволит записать исходник на образ дискеты с С, и затем скомпилировать его уже на целевой системе.
Теория
Первым шагом, понадобится где-то получить время. К счастью, компьютер Z80-MBC2 имеет возможность подключать блок с часами на базе микросхемы DS3231, и с него получать текущую дату и время, что, опять же, облегчает нам задачу, вплоть до того, что часы работают на уровне прерываний ОС, и компилятор С вполне нормально получает время с помощью функции time().
Правда, в Z80-MBC2 время получается хоть и как обыкновенное UNIX-время, но, почему-то, на 10800 секунд дальше. Это можно вполне поправить простым декрементом переменной.
Вторым шагом нужно определиться с длинами типов данных в современных машинах и в процессоре z80. Естественно, длинные типы данных не будут совпадать, и потребуется выкрутиться из этой ситуации с помощью переопределений типов.
Тут я не стал изобретать что-то велосипедное и поступил (как и всегда) довольно лениво, доверив компилятору задавать типы данных по типу процессора. Hi-Tech C это вполне прожует.
#if z80
#define ulong_type unsigned long
#define long_type long
#else
#define ulong_type unsigned int
#define long_type int
#define size_t short
#endif
Самая подлая проблема здесь крылась опять с работой со временем. В современном gcc функция time() возвращает 8-байтный long, а в Hi-Tech C нет таких длинных типов, все что есть – это четырехбайтный int. К счастью, из длинного 8-байтного long итак будут использоваться в вычислениях лишь 4 первых байта, потому, остальное можно беззастенчиво отрезать.
Помимо этого, в Hi-Tech С мне встретились странности с разбором длинных чисел на байты с помощью битовых сдвигов, которых, почему-то, не встречается в скомпиленных gcc приложениях. То ли это глюки компилятора, то ли это моя низкая программистская квалификация, где я запутался в нотациях Big Endian и Little Endian в представлении чисел на разных процессорах, но, выкрутился из этой ситуации костылем.
В частности, в алгоритме генерации хэша (функция getCode() в исходнике), чтобы собрать 4х-байтную переменную OTP из отдельных байт хэша, потребовалось применить вот такую структурку для конвертирования:
union test_u {
struct test_s {
unsigned char b0;
unsigned char b1;
unsigned char b2;
unsigned char b3;
} bytes;
ulong_type ullong;
} lpack;
То есть, в отдельные байты b0..b3 записываются байты хэша, а потом вся последовательность байт читается как четырехбайтное число из переменной lpack.ullong. Это сработало и в Hi-Tech C, и в gcc, и на том я успокоился.
О входных данных:
Программа принимает с командной строки один аргумент – Secret Key - в удобном представлении base32. Это позволило проще кодировать последовательность самых разных байт ключа в что-то удобоваримое, что можно набрать в командной строке.
Например, последовательность ключа “0xAA, 0xAA, 0xAA, 0xAA, 0xAA”, которую я использовал для отладки, удобно представлять в виде кодированной base32-строки “VKVKVKVK”. А конвертер из хекса в base32 можно найти в интернетах.
Для генерации самой OTP-последовательности применялся алгоритм SHA1. Естественно, не написанный с нуля, а выдернутый и переделанный из найденной на просторах интернета библиотечки реализации TOTP для Arduino.
Исходник всего этого проекта есть на GitHub, там же описание, полезные ссылки. Полезные ссылки можно найти так же в конце статьи.
Практика
Собирать все это воедино довольно просто.
Для компиляции исходника под gcc выполните команду
# gcc -Wall ./ttp.c -o ./ttp
Для компиляции под Hi-Tech C на машине с Linux можно установить эмулятор среды CP-M, который зовется ZXCC. Проще всего это сделать из исходников.
Далее, в отдельную папку клонируется из GitHub Hi-Tech C. В эту же папку можно перенести и исходник проекта для удобства (ttp.c, ttp.h). Теперь, коли все верно собралось, можно запустить компиляцию прямо из текущей директории:
# zxc ttp.c
Если никаких сообщений об ошибках выдано не было, то, значит, в текущей папке был сгенерирован исполняемый файл “ttp.com”, который запускается под CP-M. Прямо здесь же можно собрать и вариант под gcc, для последующего сравнения.
Запуск теперь можно проводить несколькими путями.
Gcc-версия запускается с командной строки командой
# ./ttp VKVKVKVK
Где “VKVKVKVK” – это мой тестовый ключ, кодированный в base32. Можете придумать и закодировать себе любой другой.
Версию для CPM можно запустить тремя способами:
1. Использовать установленный ранее ZXCC. Для этого набрать в текущей же директории
# zxcc ttp.com -VKVKVKVK
Здесь дефис перед первым аргументом командной строки требуется не самой программой, а регламентируется синтаксисом задания аргументов самого ZXCC, не потеряйте его.
2. Можно запустить с помощью эмулятора Z80-MBC2 на машине с linux или windows. Для этого нужно скачать и собрать с помощью Cargo сам эмулятор. Затем скачать для него образы дискет (выполнив download.sh из директории с эмулятором), записать утилитой CPMToolsGUI (работат из-под wine, если нужно) исходник на образ дискеты (для CPM 3 образ дискеты, содержащей компилятор С будет находиться в файле sd/DS2N02.DSK), а именно, файлы ttp.c, ttp.h( можно и уже ранее скомпиленный ttp.com).
Теперь эмулятор запускается из папки с эмулятором командой
# z80-mbc2-emu cpm3
После инициализации вы окажетесь на виртуальном диске A:. Перейдите на диск С: командой
C:
На данном диске должен находиться компилятор С и записанные нами ранее файлы исходника. Убедитесь в этом, набрав команду
DIR
Теперь можно скомпилировать нашу программу, набрав
C TTP.C
Или сразу запустить, если уже записали скомпилированную версию на дискету, командой
TTP VKVKVKVK
Где "VKVKVKVK" уже известный ранее нам кодированный ключ.
Завершить работу можно нажатием Ctrl + C.
3. Этот способ самый верный – записать утилитой CPMToolsGUI исходник прямо на образ дискеты на SD-карте к компьютеру Z80-MBC2, вставить ее непосредственно в компьютер и запустить, так сказать, нативно, выполняя те же шаги из варианта 2.
Итого, мы имеем меняющуюся раз в 30 секунд шестизначную TOTP-последовательность.
Сверить можно с любым онлайн-генератором. Нужно только не забыть ввести в него свой кодированный ключ.
Выглядит, конечно, не так красиво, как на Commodore 64, и не на ассемблере, но, я еще с этим повожусь.
Из выявленных проблем: На Hi-Tech C у меня не отрабатывает функция kbhit(), а как реализовать неждущий нажатия getchar() здесь, я не знаю, потому, выход из программы на CPM возможен только со сбросом эмулятора, либо без внутреннего цикла ожидания в программе.
Если что не так, простите, я старый панк и раздолбай, но, старался. Передаю эстафету по портированию дальше. Это, конечно, не DOOM, но, может быть, кто-то захочет реализовать TOTP еще на каком-нибудь тостере или бензопиле. Всё лучше, чем кротовуха.
Если хотите, задавайте свои ответы.
Использованные в разработке материалы:
Полезные ссылки:
olku
Вопрос коллективному хабраразуму - с помощью чего можно узнать что прошли сутки? Чем пассивнее (без счетчиков и постоянного электропитания) тем лучше.
rheinhard Автор
А питание предполагается вообще не иметь, или хотя бы какое-то иметь? Можно ведь по факторам среды попробовать регистрировать. По изменению температуры, освещения. Если совсем примитив - то, например, если есть только солнечные батареи, то они могут завести устройство при наступлении светлого времени суток и погасить, когда освещения будет нехватать.
Но, думается мне, без счетчиков все же никуда.
olku
Про сигналы со спутника и радио на Хабре есть статьи. Хотелось бы конечно физические или химические процессы аналогичные атомным часам, можно и не такие точные.
vpuhoff
Альтернатива близкая по сути кварцевый резонатор
Didimus
Посмотрите как системы защиты от протечек воды решают этот вопрос. Раз в месяц они перекрывают ночью воду, чтобы краны не закисали
vassabi
мерять саморазряд конденсатора\аккумулятора ?
UncleSam27
Слишком погрешность большая. Через пару десятков суток окончательно потеряется.
UncleSam27
Плата часов реального времени с независимым питанием от батарейки или GPS модуль нынче стоит копейки и то и другое позволяет легко это сделать.