Программа установки защищенных сетевых соединений с использованием протокола ISAKMP
p align="left">FD_SET (pipefd[0],&rfds); /* Добавление в массив дескриптора pipe*/retval=select (1024,&rfds, NULL, NULL, NULL); if (SOCKET_ERROR == retval) { /* Обработка ошибки*/ } if (FD_ISSET (sockdscr, &rfds)) { /* Действия, выполняемые при приходе пакета */ } if (FD_ISSET (pipefd[0], &rfds)) { /* Действия, выполняемые при получении запроса */ } В случае прихода пакета он целиком передается нити распределения пакетов (вместе с ним передаются также IP адрес и номер порта отправителя). При получении запроса на посылку пакета пакет отсылается, причем адрес и номер порта получателя должен находиться в запросе. Нить распределения пакетов. В задачу данной нити входит предварительный разбор заголовка пакета, проверка правильности структуры пакета и передача пакета нити, для которой он предназначен. Вся информация для проверки пакета и нахождения нити приемника берется из ISAKMP заголовка пакета. Данный заголовок должен находиться в начале каждого пакета и служит для определения, к какой именно попытке установления соединения принадлежит данный пакет. Структура ISAKMP заголовка приведена на рисунке 10 [4]. Первые 8 байт занимает Initiator Cookie - иидентификатор попытки установления соединения со стороны инициатора. Значение данного поля выбирается на стороне инициатора (случайным или предопределенным образом) и служит при дальнейшем распределении пакетов. Responder Cookie играет такое же значение, но для ответчика. Рис. 10. Структура ISAKMP заголовка Следующим полем идет Next Payload, которое показывает тип компонента (payload) следующего за заголовком. Version показывает версию используемого протокола. Exchange type говорит о режиме, при котором используется данный пакет (Main Mode, Aggressive Mode, Quick Mode и т.п.). Флаги содержат информацию о состоянии пакета, например, зашифрован он или нет. Еще одним идентификатором пакета является Message ID. Последние 4 байта содержат длину всего пакета, включая сам заголовок. Идентификация пакета проводится по следующим принципам. В первом пакете инициатор проставляет Initiator Cookie, а Responder Cookie оставляет нулевым, давая возможность ответчику при ответе заполнить его. Message ID служит для идентификации разных попыток установления соединения во второй фазе, идущих под защитой одной и той же первой фазы, а, следовательно, имеющих одинаковые CookieI и CookieR. Порядок обработки пакета следующий Проверка длины пакета. Производится простым сравнением длины полученного пакета, которую мы узнаем при чтении пакета из порта и значением соответствующего поля в ISAKMP заголовке. Данная проверка является очень простой, но в то же время весьма эффективной, т. к. позволяет быстро (фактически на самом первом этапе), без затрачивания больших ресурсов отсечь случайные пакеты. Здесь мы впервые встречаемся с проблемой разного способа хранения чисел на разных архитектурах. Если рассмотреть конкретный пример, то число 0x01020304 в системе с процессором Sun Sparc будет представлено в виде т.е. сначала идут старшие цифры (так называемое big-endian представление), а для процессора Intel сначала идут младшие цифры (little-endian). Из-за требования поддерживать обе платформы использовать простое проведение памяти к типу unsigned int нельзя, т. к. значение длины пакета, например, 100 для Sun Sparc будет 100, а для Intel 1677721600. Для решения этой проблемы были написаны макросы для перевода чисел из одного состояния в другое для обеих платформ. #define GET_32BIT(cp) \ (((unsigned long) (unsigned char) (cp) [3]) | \ ((unsigned long) (unsigned char) (cp) [2] << 8) | \ ((unsigned long) (unsigned char) (cp) [1] << 16) | \ ((unsigned long) (unsigned char) (cp) [0] << 24)) #define GET_16BIT(cp) \ (((unsigned long) (unsigned char) (cp) [1]) | \ ((unsigned long) (unsigned char) (cp) [0] << 8)) #define PUT_32BIT (cp, value)\ (cp) [3] = (value); \ (cp) [2] = (value) >> 8; \ (cp) [1] = (value) >> 16; \ (cp) [0] = (value) >> 24; #define PUT_16BIT (cp, value)\ (cp) [1] = (value); \ (cp) [0] = (value) >> 8; Если проверка не прошла, то дальнейшее рассмотрение пакета заканчивается и пакет удаляется. Проверяем допустимость значений некоторых других полей заголовка - Next Payload, Version, Exchange Type и Flags. Эти поля проверяются не на точное совпадение, как длина пакета, а на вхождение значения в диапазон допустимых значений. Проверка корректности данных значений будет произведена позже, при детальном рассмотрении структуры пакета Поиск в таблице нитей первой фазы возможного получателя пакета. Поиск ведется на основе значений CookieI и CookieR. Т.к. некоторые пакеты могут представлять собой запросы на создание соединений с другой стороны (т.е. нить для их обработки еще не создана), переповторы этих запросов (нить уже создана и уже проставлено значение CookieR, т.е. в данную нить пакет не попадет), а также ответы на наши запросы (CookieR в таблице стоят нулевые), то порядок поиска подходящей записи в таблице следующий: «если значение CookieR в пакете или таблице нулевое, то запись считается сработавшей, если совпало только значение CookieI, иначе должны совпасть значение и CookieR, и CookieI». Если мы нашли сработавшую запись, то мы получим дескриптор записи для pipe, связывающей с нужной нитью, по которому и передадим пакет. Если запись не найдена и значение CookieR равно нулю это означает, что это первый пакет новой попытки установления соединения. Для данного пакета мы создаем новую нить, pipe для связи с этой нитью, делаем добавление записи в таблицу нитей первой фазы, после чего передаем пакет только что созданной нити. Если не выполнилось ни одного из вышеперечисленных условий, то пакет считается неверным и удаляется. Таким образом, происходит обработка пришедшего пакета, но нить распределения пакетов может также получить запрос на создание секретного соединения в качестве инициатора. В этом случае создается нить, pipe для связи с ней и нити передается пустой пакет, как знак начала работы в качестве инициатора. Нить выполнения первой фазы. Данная нить предназначена для проведения первой фазы установления соединения. Как было указано выше нити можно представить как независимое выполнение программы. Но, не смотря на это, каждая нить имеет свой собственный стек, а значит все переменные, объявленные в функции принадлежат только нити. Этот факт используется для хранения информации, связанной только с данной попыткой установления соединения (ключи шифрования, рабочие константы, метод аутентификации, текущие CookieI и CookieR и т.д.). Обработка пришедшего пакета начинается с более тщательной проверки пакета. Сначала проверяется то, что не изменился тип обмена (если это первый пакет, то данное значение сохраняется для последующих проверок). То же самое делается со значением версии в ISAKMP заголовке. После этого происходит расшифрование пакета (если это требуется). Дальше происходит разбор пакета, выполнение промежуточных действий (расчетов), составление и отсылка ответного пакета. Так как набор функций, реализующих перечисленные выше действия, отличается для каждого пакета, построением простого цикла задача не решается. Для решения этой проблемы была введена переменная, которая отражала текущие состояние обмена, и по значению которой можно было узнать какие функции выполнять. Для наглядности и простоты работы с этой переменной были описаны ряд define. #define INITIATOR 0x0 #define RESPONDER 0x80 #define MAIN 0x0 #define AGGRESSIVE 0x40 #define ABSENT 0x0 /* Метод аутентификации еще не определен*/ #define PRESHARED 0x8 #define DSA_SIGN 0x10 #define RSA_SIGN 0x18 #define RSA_ENC 0x20 #define RSA_REV_ENC 0x28 #define RENCRYPT 0x30 #define GET_ROLE(State) (State&0x80) #define GET_EXCH(State) (State&0x40) #define GET_MODE(State) (State&0x38) #define GET_STEP(State) (State&0x03) #define SET_ROLE (State, Role) {State &= 0x80; State+=Role;} #define SET_MODE (State, Meth) {State &= 0xC7; State+=Meth;} #define SET_EXCH (State, Meth) {State &= 0xBF; State+=Meth;} #define STATE (role, exch_type, mode, step) (role+exch_type+mode+step) Пример использования данной переменной при работе с программой будет приведен далее. Переход к требуемому набору функций осуществляется с помощью оператора выбора switch. В конце каждого набора функций переменная текущего состояния должна переводиться в следующее состояние (чаще всего это просто увеличение номера пакета) или должен выставляться флаг, говорящий об окончании первой фазы. /*Получение о предварительная обработка пакета*/ switch (state. State) /* Выбор набора требуемых функций */ { case STATE (RESPONDER, AGGRESSIVE, ABSENT, 0): /* Набор функций для данного состояния */ break; case STATE (RESPONDER, MAIN, ABSENT, 0): /* Набор функций для данного состояния */ break; ……………………… case STATE (INITIATOR, MAIN, DSA_SIGN, 2): /* Набор функций для данного состояния */ break; } Переменная текущего состояния также активно используется при расчетах, где формулы отличаются в зависимости от метода аутентификации или исполняемой роли (инициатор или ответчик). Отсылка созданного пакета осуществляется через доступный каждой нити дескриптор записи pipe нити работы с сетью После окончания первой фазы нить переходит в режим управления нитями второй фазы. Пакеты для этих нитей по значению CookieI и CookieR приходят в данную нить, а затем согласно значению Message ID отправляются в нити второй фазы или инициируют их создание. Для проведения правильной идентификации пакетов каждая нить первой фазы содержит свою таблицу нитей второй фазы, по которой и проводит поиск. Нить выполнения второй фазы. Задача данной нити - проведение второй фазы установления соединения. Структура и принцип работы полностью такие же, как и в нити первой фазы. При создании вместе с пакетом нити передаются также значения некоторых переменных (рабочие константы, ключи шифрования и т.п.) необходимые для нормальной работоспособности нити. По завершению второй фазы нить выдает полученные результаты, удаляет себя из таблицы нитей второй фазы и заканчивает работу. Таблицы поиска нитейТаблица нитей выполняющих первую фазу представляет собой список CookieTable_t структур struct CookieTable_t; typedef struct CookieTable_t { uchar CookieI[8]; /*Initiator Cookie */ uchar CookieR[8]; /* Responder Cookie */ int pd; /* Pipe Descriptor */ uchar Ready; /* Ready Flag */ struct in_addr AlienAddr; /* Peer IP Address */ struct CookieTable_t *next; /* Next Member (NULL if last) */ }; Необъясненными в данной структуре остались два поля: флаг Ready и структура IP адреса. В структуре IP адреса находится адрес партнера, с которым мы ведем процесс установления соединения. Флаг Ready показывает, закончилось или нет проведение первой фазы. Он используется, если со стороны модуля управления пришло 2 запроса на инициацию попытки установления соединения. В этом случае просматривается таблица нитей первой фазы в поисках записи с указанным IP адресом. Если флаг Ready в данной записи говорит о том, что первая фаза уже завершена, то запрос формируется на проведение сразу второй фазы. IP адрес может также использоваться при поиске нити для пришедшего пакета. Ниже приведен пример функции поиска записи в таблице по совпадению и CookieI и CookieR. CookieTable_t *FindCookieRecord (uchar *CI, uchar *CR) { uchar Test[8] = {0, 0, 0, 0, 0, 0, 0, 0}; struct CookieTable_t *ptr = CookieTable; while(ptr) (MEMCMP (Test, ptr->CookieR, 8)&&MEMCMP (CR, ptr->CookieR, 8))) ptr = ptr->next; else return ptr;
return NULL; } Таблица нитей второй фазы тоже представляет собой список структур. struct Phase2Table_t; typedef struct Phase2Table_t { uchar MessageID[4]; /* Message ID */ int pd; /* Pipe Descriptor */ struct SPIlist_t SPIlist; /* List of SPIs */ struct Phase2Table_t next; /* Next Member (NULL if last) */ }; Метод работы со списками Phase2Table_t аналогичен вышеприведенному примеру. Входные и выходные данные Общими входными данными (те которые используются для инициатора и для ответчика) является список возможных параметров соединения для первой и второй фаз. Данная информация считывается из конфигурационного файла при запуске программы и хранится в глобальных переменных, доступная для всех нитей. Структуры, описывающие эту информацию, повторяют структуру SA payload, которые предназначены для передачи этих самых вариантов параметров и выбранного случая. Рассмотрим структуры для описания параметров соединения подробнее. typedef struct Proposal_t { uchar ProposalN; /* Номер Proposal */ uchar ProtocolID; /* Номер протокола */ NewGroup_t *NewGroup; uchar NTransforms; Proposal_t *nextPor; Proposal_t *nextPand; Transform_t *nextT; }; Структура Proposal_t описывает Proposal payload, входящий в состав SA payload. Данная структура содержит все поля необходимые для создания и заполнения данного компонента пакета. Полями структуры являются номер компонента (ProposalN), идентификатор протокола, представляемого этим компонентом (ProtocolID), указатель на структуру содержащую параметры для New Group Mode (если он равен NULL, данные режим не нужен), количество Transform payload. Для связи между собой в данных структурах предусмотрено два указателя - указывающий на следующую структуру объединенную по «И» (nextPand), и указывающий на первую структуру следующей группы, объединенной по ИЛИ (nextPor). Также есть указатель на список соответствующих Transform payload структур. typedef struct Transform_t { uchar TransformN; uchar TransformID; Transform_t *nextT; Attributes_t *nextA; }; Структура содержит номер структуры в данном списке, идентификатор представляемого алгоритма, указатель на следующий элемент (напомним, что список одномерный) и указатель на принадлежащий Transform payload список атрибутов. typedef struct Attributes_t { uint type; ushort SmallVal; BUFFER BigVal; Attributes_t *nextA; }; Для атрибутов различают два представления - короткое и длинное. При коротком представлении значение атрибута не превышает 65535 (2 байта), а при длинном задается длина и буфер, содержащий значение атрибута. То, в каком представлении задан (прислан) атрибут определяется первым битом поля type. Если он выставлен, то представление короткое. Остальные биты поля type показывают, какой это именно атрибут (длина ключа, метод аутентификации и т.п.). Поля SmallVal и BigVal предназначены для хранения значения атрибута при соответственно коротком и длинном представлении. Для каждого типа атрибута определено его представление. Если атрибут считается длинным, то допускается его представление в коротком формате (если значение умещается в 2 байта). Но обратное утверждение не верно - короткий атрибут всегда остается коротким. Для хранения информации из конфигурации в программе описаны два указателя на структуру Proposal_t, которые содержат набор параметров соответственно для первой и второй фаз. При получении SA payload от партнера, его содержимое также переводится в данные структуры для удобства работы с информацией. С помощью этих же структур происходит выдача результатов работы программы. Но, т. к. ключевой материал не входит в структуры, то буфер посчитанным ключевым материалом передается отдельно. Алгоритм обработки входящего пакета При обработке входящего пакета решается две задачи. Первая задача это отбраковать плохие пакеты (посланные случайно или преднамеренно) по внешним признакам, чтобы избежать лишней траты ресурсов. Вторая задача это найти нить, для которой предназначен пакет. При решении первой задачи рассматриваются два признака, по которым пакеты проверяются. Первый - значение поля Initiator Cookie. Это поле ни в одном пакете не может быть нулевым. Второй пункт при проверке это длина пакета. Т.к. ее значение передается в ISAKMP заголовке, а он никогда не шифруется, то для каждого пакета мы можем узнать заявленную длину и сравнить с длиной реальной. Вторая задача решается на основе значений полей CookieI и CookieR путем поиска требуемой нити в таблице нитей первой фазы. Способ решения данных задач была подробнее рассмотрена при описании нити распределения пакета, поэтому в данном разделе будет объяснен алгоритм разбора пакета на его компоненты (payload). Данный заголовок стоит в начале каждого компонента и служит для связывания компонент в список. Первым полем в нем указан тип следующего компонента (тип первого компонента указывается в соответствующем поле ISAKMP заголовка). У последнего компонента данное поле должно быть равно нулю. Второй байт в заголовке является зарезервированным для будущего использования и должен равняться нулю. Последние два байта содержат длину компонента вместе с общим заголовком. Далее будет рассмотрена функция CheckPacket, которая осуществляет проверку структуры списка компонент. int CheckPacket (State_t *state) { int next = state->FirstPayload, Length = ISAKMP_HEADER_SIZE; state->LastPacket = 0; state->ptr = state->Packet.buf + ISAKMP_HEADER_SIZE; do { if (state->ptr[1]!= 0) return ERROR /*Поле RESERVED не ноль*/ Length += GET_16BIT (state->ptr+2); if (Length > state->Packet.len) return ERROR /* Вышли за пределы пакета */ switch(next) = PAYLOAD_KEY; break; …………………………………………. case 13: if (CheckVendor(state) < 0) return ERROR; state->LastPacket next = state->ptr[0] state->ptr += GET_16BIT (state->ptr+2); } while(next); return 0; } В функцию в качестве параметра передается структура state, которая содержит всю информацию, относящуюся к данной нити. В данном случае нам потребовалось значение типа первого компонента state->FirstPayload (оно было получено при разборе ISAKMP заголовка), указатель на буфер, содержащий пришедший пакет и длина пакета. Т.к. ISAKMP заголовок уже был разобран, временный указатель смещен на начало первого компонента. Затем начинается цикл по всем компонентам. Сначала проверяется правильность общего заголовка. Для этого проверяем равенство нулю зарезервированного поля. Длину компонента добавляем к сумматору общей длины пакета (Length) и проверяем, что заявленная длина компонент не больше длины самого пакета. Затем стоит оператор выбора (case), который анализирует значение типа заголовка. Этим выполняется проверка правильности типа компонента, и если тип оказывается неизвестным (или неподдерживаемым), то программа заканчивается с ошибкой. Для каждого известного компонента сначала происходит проверка правильности структуры компонента. Это обусловлено наличием в некоторых из них зарезервированных полей, а также тем, что некоторые из них содержат в себе другие компоненты (SA payload). После проверки выставляется флаг наличия данного компонента в пакете. Данные флаги будут необходимы при семантическом анализе пакета. Следующим действием мы присваиваем переменной содержащей тип следующего компонента новое значение и передвигаем указатель на начало следующего компонента. Выход из данного цикла осуществляется по нулевому значению поля общего заголовка Next payload. Гарантией того, что процесс проверки вообще когда-нибудь кончиться служит проверка того, что длина разбираемых компонент меньше длины пакета. Нормальный выход из цикла означает правильную структуру пакета. Написание программы и проведение тестирования В данном разделе будет описан процесс написания и тестирования отдельных функций и модулей. При написании программы, реализующей протокол ISAKMP, тестирование приходилось проводить после написания почти каждой функции обработки очередного пакета. Сначала будут описаны служебные функции и модули, а затем модули непосредственно реализующие протокол ISAKMP. Служебные функции и модули. К служебным функциям относятся функции, реализующие некоторые вспомогательные действия, с помощью которых реализуется сам протокол. Функции работы с памятью.Наряду с системными функциями работы в моей программе были реализованы функции работы со структурой виртуального буфера BUFFER typedef struct BUFFER { uchar *buf; int len; int Len; }; Данная структура кроме указателя содержит две длины - размер зарезервированного буфера и сколько байт используется в настоящее время. Набор функций выполняет стандартный набор действий (создание, копирование, сравнение, обнуление и удаление), а также конкатенацию двух буферов. Размер виртуального буфера увеличивался динамически при добавлении новых данных. Следующий пример показывает это. int MEMCPY (BUFFER* dst, int offset, uchar *src, int len) { uchar *tmp = NULL; if(! dst) return ERR_BADPARAM; if (offset + len > dst->Len) /* Проверка достаточности буфера*/ { dst->Len = ((offset+len)/ALLOC_SIZE+1)*ALLOC_SIZE; tmp = (uchar*) MALLOC (dst->Len); /* Занятие нового буфера */ if(! tmp) return ERR_NOMEMORY; memcpy (tmp, dst->buf, dst->len);/*Копирование старого значения*/ FREE (dst->buf); /* Удаление старого буфера */ dst->buf = tmp; } dst->len = offset + len; /* Новая длина */ memcpy (dst->buf + offset, src, len); /* Копирование нового значения */ return 0; } Тестирование данных функций производилось с помощью утилиты, которая считывала буферы из файла, и производила над ними требуемые действия. Результаты по желанию выводились на экран или обратно в файл, где и анализировались. Функции работы с сетью.Включает в себя функции инициализации порта, чтения и записи информации. Для тестирования было написано две программы: клиент и сервер. Задачей клиента было прослушивание заданного порта и вывод полученной информации на экран. Сервер запрашивал адрес клиента и отсылал информацию из файла по указанному адресу. Тестирование заключалось в сравнении отосланной и полученной информации. Также были протестированы случаи прихода сразу нескольких пакетов. Функции криптоалгоритмовВ программе используются алгоритмы шифрования DES и Triple DES, алгоритмы хеширования MD5 и SHA1и алгоритмы с открытым ключом RSA и DSA. Реализация всех алгоритмов была взята извне. Исключение составляет только Triple DES, реализация которой основана на основе функций реализации DES. void des_3cbc_encrypt (DESContext *ks1, DESContext *ks2, DESContext *ks3, u_char *iv, u_char *dest, const u_char *src, u_long len) { word32 iv0, iv1, out[2], out1 [2], out2 [2]; u_long i; iv0 = GET_32BIT(iv); /* Считывание значения IV */ iv1 = GET_32BIT (iv + 4); for (i = 0; i < len; i += 8) /*Обработка в цикле по 8 байт */ { iv0 ^= GET_32BIT (src + i); iv1 ^= GET_32BIT (src + i + 4); des_encrypt (iv0, iv1, out1, ks1, 1); /* Шифрование первым ключом */ des_encrypt (out1 [0], out1 [1], out2, ks2, 0); /* Расшифрование вторым*/ des_encrypt (out2 [0], out2 [1], out, ks3, 1); /* Шифрование третим */ iv0 = out[0]; iv1 = out[1]; PUT_32BIT (dest + i, iv0); PUT_32BIT (dest + i + 4, iv1); } PUT_32BIT_DES (iv, iv0); PUT_32BIT_DES (iv + 4, iv1); } Тестирование алгоритмов шифрования и алгоритмов с открытым ключом проводилось следующим образом. Сначала тестировалась работа самим с собой, т.е. функцией зашифровывался буфер, затем расшифровывался и сравнивался с оригинальным значением. После этого проверялась работа с тестовыми последовательностями, взятыми из стандарта по данным алгоритмам. Алгоритмы хеширования сразу тестировались на стандартных тестовых последовательностях. Создание нитей и организации передачи данных между ними.Нить создается стандартной функцией pthread_create. #include <pthread.h> int pthread_create (pthread_t* thread_ID, const pthread_attr_t *attr, void * (*start_func) (void *), void *arg); Первый параметр в данной функции это некий дескриптор нити, с помощью которого создающий процесс (нить) может потом ею управлять. Третий параметр - имя функции, выполнением которой и займется новая нить. Как видно из описания функция имеет определенный формат. И последним параметром передается указатель на параметры, передаваемые этой нити при старте. В программе описано 5 функций для запуска нитей - работа с сетью, распределения пакетов, выполнения первой фазы, выполнения второй фазы (одна для New Group Mode и одна для Quick Mode). Первые две нити создаются при старте программы, остальные по мере надобности. Рассмотрим пример создания нити для первой фазы. #define THREAD_T pthread_t #define THREAD_SIMPLE_CREATE (start_func, arg, tid) \ pthread_create (tid, NULL, start_func, arg) THREAD_T tid; ………………………. Record = AddCookieRecord(); /* Добавление в таблицу нитей первой фазы */ if (NULL == Record) return ERROR_MEMORY; MEMCPY (Record->CookieI, buff, 8); Record->Ready = 0; BUFptr = (BUFFER*) MALLOC (sizeof(BUFFER)); /* Создание параметра */ if (NULL == BUFptr) return ERROR_MEMORY; MEMINIT(BUFptr); MEMADD (BUFptr, null, 1); PUT_32BIT (addr_tmp, clientaddr.sin_addr.s_addr)/* Запись адреса партнера */ MEMADD (BUFptr, addr_tmp, 4); PUT_16BIT (null, length); MEMADD (BUFptr, null, 2); MEMADD (BUFptr, buff, length); /* Запись длины пакета */ if (THREAD_SIMPLE_CREATE (WorkThread, (void*) BUFptr, &tid)) { printf («Thread creation failed\n»); return 1; } ………………………. После принятия решения о том, что для принятого пакета нужно создавать отдельную нить я сначала добавляю новую запись в таблицу нитей первой фазы. В эту запись заноситься Cookie Initiator и флаг Ready обнуляется как знак того, что первая фаза еще не закончена. После этого формируется массив, содержащий информацию необходимую для начала работы нити. В него входит IP адрес отославшего сообщение, длина пакета и собственно пакет. Указатель на виртуальный буфер, содержащий эту информацию, передается в качестве параметра в функцию нити. В самом начале работы каждая нить создает pipe, для того чтобы ей можно было передавать пакеты. Читающий дескриптор она оставляет у себя, а дескриптор для записи она записывает в таблицу нитей первой фазы (запись она находит, зная значение Cookie Initiator). Туда же записывается и значение Cookie Responder, после того как оно будет определено. Для нитей второй фазы процесс создания во многом похож, за исключением того, что в качестве параметра передается указатель на структуру state, которая содержит всю требуемую информацию (ключи шифрования, рабочие константы, адреса и т.п.). Процесс распределения дескрипторов pipe для связи остается таким же. Дескриптор записи в нить работы с сетью является глобальной переменной и доступен каждой нити. Тестирование проводилось путем создания простой модели программы. Простота заключается в отсутствии кода реализующего протокол. Нить просто принимает пакет, увеличивает внутренний счетчик и посылает ответ. При достижении счетчика определенного значения нити первой фазы начинают создавать нити второй фазы, а нити второй фазы, при том же значении счетчика заканчивают работу. Программа для данного тестирования явилась каркасом будущей программы, т. к. потом увеличение счетчика было заменено обработкой и анализом пакета. В данном тесте также проводилось тестирование и функций работы с памятью и функций работы с сетью. Модули реализации протокола ISAKMP Данные модули включают в себя функции анализа пакета, обработки и проверки пришедших данных, выполнение требуемых расчетов и создание данных необходимых для сборки пакета. Как уже упоминалось раньше, весь процесс работы рабочих нитей представляет собой переход из одного так называемого состояния в другое. Под состоянием здесь понимается набор определенных значений некоторых параметров. В программе рассматриваются следующие параметры: роль играемая нитью (инициатор или ответчик), тип обмена, номер пакета и метод аутентификации (только для первой фазы). Все параметры, кроме последнего, могут быть определены в самом начале работы, поэтому для метода аутентификации добавляется значение NONE, означающее неопределенность параметра. Каждый набор параметров однозначно указывает на какое-либо состояние. Состояние, в свою очередь, состоит из следующих частей: анализ и проверка данных из пакета, проведение расчетов, составление данных для отсылаемого пакета и определение следующего состояния. Анализ и проверка пришедших данных (часто называемый «семантическим разбором пакета») включает в себя в первую очередь анализ качественного состава пакета, т.е. проверяется все ли из необходимых в данном состоянии компонент присутствуют и нет ли лишних. Все эти проверки осуществляются при анализе переменной, содержащей флаги имеющихся компонент (то, как она определяется, описано в разделе, описывающем работу нити первой фазы). Затем проходит проверка пришедших данных. Для каждого компонента происходит своя проверка, которая может и отсутствовать, например, Nonce payload значение которого просто запоминается. Для SA payload проверка заключается в сравнении пришедшей политики с описанной в конфигурации для ответчика и в проверке правильности присланного ответа для инициатора. Для KE payload происходит проверка длины присланной ключевой информации согласно параметрам, присланным в политике. В Certificate и Certificate Request payloads проверяется тип используемых сертификатов (у меня в программе допустимы только x509 сертификаты). В Identification payload проверяется тип присылаемой информации (допускается только IP адреса) и собственно содержимое компонента. Компонент со значением хеш-функции (Hash payload) проверяется путем подсчета своего варианта и сравнения его с пришедшим значением. В примере приведена функция, вычисляющая значение хеш-функции противоположной стороны. int CalculateAlienHash (State_t *state, BUFFER *Hash) { uchar save; BUFFER DATA; MEMINIT(&DATA); /* Инициализация буфера */ if(! ((state->SKEYID).len)) CalculateSKEYID(state); /* Подсчет SKEYID */ MEMADD (&DATA, &(state->AlienKE)); /* g^x (i/r) */ MEMADD (&DATA, &(state->MyKE)); /* g^x (i/r) */ if (GET_ROLE (state->State) == INITIATOR) { MEMADD (&DATA, state->CookieR, 8); MEMADD (&DATA, state->CookieI, 8); } else { MEMADD (&DATA, state->CookieI, 8); MEMADD (&DATA, state->CookieR, 8); } MEMADD (&DATA, &(state->SAi_b)); MEMADD (&DATA, &((state->AlienIdent).Type), 1); MEMADD (&DATA, (state->AlienIdent).DOI, 3); MEMADD (&DATA, &((state->AlienIdent).Data)); if (GET_MODE (state->State) == DSA_SIGN) { save = state->HashAlg; state->HashAlg = HASH_ALG_SHA; M (PRF(state, &(state->SKEYID), &DATA, Hash)); state->HashAlg = save; } else M (PRF(state, &(state->SKEYID), &DATA, Hash)); return 0; } Формулы, реализованные этой функцией, были представлены при описании фазы 1 (Main Mode). Следует заметить, что эта функция считает не значение инициатора или ответчика, а значение хеш-функции противоположной стороны. Внутри функции это достигается анализом переменной показывающей текущее состояние. Для проверки подписи (располагается в Signature payload) считается данное значение хеш-функции и подписывается требуемым алгоритмом. В приведенном примере есть еще один пример использования переменной состояния. DSA алгоритм может подписывать хеш только от алгоритма SHA. Специально для этого случая значение текущего алгоритма хеширования принудительно приравнивается значению алгоритму SHA. Следует заметить, что в процессе проверки может поменяться текущее значение состояния. Это может произойти при сравнении политик, когда партнеры договариваются о методе аутентификации
Страницы: 1, 2, 3
|