typedefstructTransform_t {
uchar TransformN;
uchar TransformID;
Transform_t *nextT;
Attributes_t *nextA;
};
Структура содержит номер структуры в данном списке, идентификатор представляемого алгоритма, указатель на следующий элемент (напомним, что список одномерный) и указатель на принадлежащий Transformpayload список атрибутов.
typedef struct Attributes_t {
uint type;
ushort SmallVal;
BUFFER BigVal;
Attributes_t *nextA;
};
Для атрибутов различают два представления – короткое и длинное. При коротком представлении значение атрибута не превышает 65535 (2 байта), а при длинном задается длина и буфер, содержащий значение атрибута. То, в каком представлении задан (прислан) атрибут определяется первым битом поля type. Если он выставлен, то представление короткое. Остальные биты поля type показывают, какой это именно атрибут (длина ключа, метод аутентификации и т.п.). Поля SmallVal и BigVal предназначены для хранения значения атрибута при соответственно коротком и длинном представлении. Для каждого типа атрибута определено его представление. Если атрибут считается длинным, то допускается его представление в коротком формате (если значение умещается в 2 байта). Но обратное утверждение не верно – короткий атрибут всегда остается коротким.
Для хранения информации из конфигурации в программе описаны два указателя на структуру Proposal_t, которые содержат набор параметров соответственно для первой и второй фаз. При получении SApayload от партнера, его содержимое также переводится в данные структуры для удобства работы с информацией.
С помощью этих же структур происходит выдача результатов работы программы. Но, т. к. ключевой материал не входит в структуры, то буфер посчитанным ключевым материалом передается отдельно.
Алгоритм обработки входящего пакета
При обработке входящего пакета решается две задачи. Первая задача это отбраковать плохие пакеты (посланные случайно или преднамеренно) по внешним признакам, чтобы избежать лишней траты ресурсов. Вторая задача это найти нить, для которой предназначен пакет.
При решении первой задачи рассматриваются два признака, по которым пакеты проверяются. Первый – значение поля InitiatorCookie. Это поле ни в одном пакете не может быть нулевым. Второй пункт при проверке это длина пакета. Т.к. ее значение передается в 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)
{
case 1: if (CheckSA(state) < 0) return ERROR;
state->LastPacket |= PAYLOAD_SA;
break;
case 4: if (CheckKE(state) < 0) return ERROR;
state->LastPacket |= PAYLOAD_KEY;
break;
………………………………………….
case 13: if (CheckVendor(state) < 0) return ERROR;
state->LastPacket |= PAYLOAD_VENDOR;
break;
default:
return ERROR;
break;
}
next = state->ptr[0]
state->ptr += GET_16BIT (state->ptr+2);
} while(next);
return 0;
}
В функцию в качестве параметра передается структура state, которая содержит всю информацию, относящуюся к данной нити. В данном случае нам потребовалось значение типа первого компонента state->FirstPayload (оно было получено при разборе ISAKMP заголовка), указатель на буфер, содержащий пришедший пакет и длина пакета. Т.к. ISAKMP заголовок уже был разобран, временный указатель смещен на начало первого компонента. Затем начинается цикл по всем компонентам. Сначала проверяется правильность общего заголовка. Для этого проверяем равенство нулю зарезервированного поля. Длину компонента добавляем к сумматору общей длины пакета (Length) и проверяем, что заявленная длина компонент не больше длины самого пакета. Затем стоит оператор выбора (case), который анализирует значение типа заголовка. Этим выполняется проверка правильности типа компонента, и если тип оказывается неизвестным (или неподдерживаемым), то программа заканчивается с ошибкой. Для каждого известного компонента сначала происходит проверка правильности структуры компонента. Это обусловлено наличием в некоторых из них зарезервированных полей, а также тем, что некоторые из них содержат в себе другие компоненты (SApayload). После проверки выставляется флаг наличия данного компонента в пакете. Данные флаги будут необходимы при семантическом анализе пакета. Следующим действием мы присваиваем переменной содержащей тип следующего компонента новое значение и передвигаем указатель на начало следующего компонента. Выход из данного цикла осуществляется по нулевому значению поля общего заголовка Nextpayload. Гарантией того, что процесс проверки вообще когда-нибудь кончиться служит проверка того, что длина разбираемых компонент меньше длины пакета. Нормальный выход из цикла означает правильную структуру пакета.
Написание программы и проведение тестирования
В данном разделе будет описан процесс написания и тестирования отдельных функций и модулей. При написании программы, реализующей протокол ISAKMP, тестирование приходилось проводить после написания почти каждой функции обработки очередного пакета. Сначала будут описаны служебные функции и модули, а затем модули непосредственно реализующие протокол ISAKMP.
К служебным функциям относятся функции, реализующие некоторые вспомогательные действия, с помощью которых реализуется сам протокол.
Наряду с системными функциями работы в моей программе были реализованы функции работы со структурой виртуального буфера BUFFER
typedef struct BUFFER {
uchar *buf;
intlen;
intLen;
};
Данная структура кроме указателя содержит две длины – размер зарезервированного буфера и сколько байт используется в настоящее время. Набор функций выполняет стандартный набор действий (создание, копирование, сравнение, обнуление и удаление), а также конкатенацию двух буферов. Размер виртуального буфера увеличивался динамически при добавлении новых данных. Следующий пример показывает это.
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 и TripleDES, алгоритмы хеширования MD5 и SHA1и алгоритмы с открытым ключом RSA и DSA. Реализация всех алгоритмов была взята извне. Исключение составляет только TripleDES, реализация которой основана на основе функций реализации 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_longi;
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.