Sunday, February 1, 2009

Автостопом по крипто апи.



Пребью
Криптографии в разработке системных и не только приложениях принято уделять повышенное внимание. Особенно сейчас, когда набитость сундуков многих благородных сэров, как и степень их свободы, напрямую зависит от степени конфеденциальости их информации. Чтобы секономить время и не ходить лишний раз к проктологу восьпользуемся тем что, большинство стойких и эффективных алгоритмов уже реализованно професионалами этого дела. Плод их трудов открыто предоставляется на комерческой и бесплатной основе. Разработчики операционных систем тоже не сидят сложа руки предоставляя разработчикам специальные API. В этой статье мы хотим рассказать об исползования таких API в модуле ядра для операционной системы Linux, котороый будет шифровать исходящий трафик и расшифровывать входящий.

Летс гоу.
А начнем пожалуй с подготовки системы. Для начала надо убедится что система готова к созданию разного рода приложений, а конкретно к разработке модулей ядра. Помимо присутсвия GCC надо еще убедиться что ядро было скомпилированно с поддержкой модульности и криптографических API. Про установку GCC и включение модульности написано много мануалов поэтому не будем тратить ни времени ни чернил, в информации по созданию модулей недостатка тоже не чувствуется, так что приступим сразу к делу. Крипто API включаются выставлением параметров CONFIG_CRYPTO* в конфигурационном файде ядра (/usr/src/.config). А самый легкий способ проверки их доступности это попытаться скомпилировать пример который находится все в техже исходниках ядра. Заходим в папку с иходниками ядра, дальше как наверное не трудно догадаться в дирректорию crypto и компилируем tcrypt.c. Если он компелируется без фатальных ошибок и даже работает, то собираемся с духом и идем дальше.

Вот из ит.
Теперь надо немного разобраться чтоже всетаки мы подключили и где это найти. Исходники этого всего дела лежат в тойже директории что и упомянутый выше tcrypt.c, а сам он является примером в котором показано как использовать все реализованные в Cryptographic API алгоритмы. Пример этот хорошо коментирован, а если что-то остается не совсем понятным то всегда можно заглянуть в лежащие рядом исходники самих Cryptographic API. Но как говорится сказать или даже написать проще чем сделать. На практике довольно часто можно свалиться в штопор в самых неожиданных местах, и потом довольно долго по частям собирать то что всего пять минут назад летело со скоростью звука. К сожалению ошибки всегда банальны, но постоянны, и каждый раз вспоминать как приклеивается один и тоже кусок становится утомительным. Сделать один раз инструкцию и современем её дополнять гораздо эффективнее и проще. Относительно наших API можно сказать тоже самое. Нормального мана на русском языке по ним нам найти не удалось, а повторно вкуривать одну и туже траву это не тру. Поэтому хотим оставить своего рода рабочие записки и надеемся что это еще комунибудь поможет.

Литл море
Предположим мы хотим реализвать немного устаревший но достаточно быстрый и провереный алгоритм DES. У этого алгоритма существует несколько режимов:

* ECB (Electronic Code Book) - режим все блоки шифруются независимо друг от друга и не сцепляются;
* CBC (Cipher Block Chaining)- режим в котором результат шифрования предыдущего блока используется для шифрации текущего.

Под блоками тут подразумевается часть шифротекста. Так с виду один и тот же алгоритм может быть реализован в разных режимах. Если кому интересно, то в качестве домашнего задания можно еще самостоятельно покурить CFB и OFB.
Однако, мы отвлеклись от темы. Для своих целей выберем режим CBC как более надежный и приступим сделав наброски будующего кода.
Как это часто бывает начнем с написания и описания ключевых структур и переменных.

struct completion comp;
struct scatterlist sg[8];
struct crypto_ablkcipher *tfm;
struct ablkcipher_request *req;


Переменная comp служит для синхронизации выполнения между нашей функцией и непосредственно шифрованием. Суть этого станет ясна чуть ниже. А пока рассмотрим саму структуру. Она объявлена в include/linux/completion.h и содержит всего два поля:
struct completion {
unsigned int done;
wait_queue_head_t wait;
};

Первое это флаг выполнения, а второе указатель на очередь ожидающих задач.

Следующая переменная sg описана в include/linux/scatterlist.h. Это структура платформо зависимая, и на i386 платформе она определена следующим образом:
struct scatterlist {
struct page *page;
unsigned int offset;
dma_addr_t dma_address;
unsigned int length;
};

Заполнение полей этой структуры вполне можно поручить функции sg_init_one(). В этой функции определяется страница памяти, с которой "начинается" buf, и определяется смещение указателя buf относительно адреса начала страницы.
tfm и есть наша основная структура, в файле include/linux/crypto.h находится ее описание
struct crypto_ablkcipher {
struct crypto_tfm base;
};


Не очень информативно поэтому посмотрим чуть выше, где находится
struct crypto_tfm {
u32 crt_flags;
union {
struct ablkcipher_tfm ablkcipher;
struct aead_tfm aead;
struct blkcipher_tfm blkcipher;
struct cipher_tfm cipher;
struct hash_tfm hash;
struct compress_tfm compress;
} crt_u;
struct crypto_alg *__crt_alg;
void *__crt_ctx[] CRYPTO_MINALIGN_ATTR;
};

Уже что-то неправдали? первым полем идут флаги, о некоторых из них расскажем чуть ниже. Далее объединение более низкуровневых структур для разных типов криптографических задач, прямой доступ к которым использовался в предыдущих версиях. В crypto_alg содержится указатель на структуру. в которой есть все необходимое для корректной работы непосредственно алгоритма шифрования. А именно имя самого алгоритма, приоритет и т.д. /*про это можно подробнее отсюда http://diploma-thesis.siewior.net/html/diplomarbeitch4.html*/

Следующая переменная req содержит указатель на структуру запросов struct ablkcipher_request.
struct ablkcipher_request {
struct crypto_async_request base;
unsigned int nbytes;
void *info;

struct scatterlist *src;
struct scatterlist *dst;

void *__ctx[] CRYPTO_MINALIGN_ATTR;
};


С помошью этой прменной мы обращамся к страницам памяти где находится шифрованный и расшифрованый текст.

Когда мы разобрались с основными переменными можно приступать к написанию самого кода. Начнем с инициализации переменной comp с помощью функции init_completion().
init_completion(&comp);

Эта функция обнулят флаг done, и добвляет comp в голову очереди wait_queue_head_t, о которых рассказывалось выше. Подробнее о механизме работы этой структуры и о том для чего мы ее используем будет рассказано еще чуть ниже ;).

Теперь выделим память дла нашей оновной пременной.
tfm = crypto_alloc_ablckhipher ("cbc(des)", 0, CRYPO_TFM_REQ_WEAK_KEY);

В качестве первого параметра мы передаем имя алгоритма, второй параметр это тип шифрования (вспомните объединение crt_u в опиании структуры), третьим параметром мы передам флаги, переданный флаг означает что алгоритм должен принимать даже слабые ключи.

Далее выделям память для ablkcipher_request
req = ablkcipher_request_alloc (tfm, GFP_KERNEL);

С первым параметром думаю все понятно и так. Непосредственно память под структуру выделяется функцией kmalloc, которая в качестве параметра принимает флаг, говорящий о том как именно стоит выделять память. Флаг GFP_KERNEL (GFP - Get Free Page) резервирует блок памяти, выделяя страницы памяти по мере обращения к ним. Существует и другие флаги например:
GFP_ATOMIC выделяет требуемую память немедленно (при необходимости вытесняя другие страницы на диск);
GFP_BUFFER никогда не вытесняет другие страницы, и если запрошенная память недоступна, с выделением наступает облом.
Фактически приходится выбирать между GFP_ATOMIC и GFP_KERNEL. Обычно используют GFP_KERNEL, так как он ведет себя не столь агрессивно.

Потом установим ключ которым будем шифровать
ret = crypto_ablkcopher_sekey (tfm, key, strlen(key));

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

Теперь позаботимся о расположении текста который будем шифровать
sg_init_one (&sg[0], text, strlen(text));

Стоит отметить что текст должен быть кратен восьми, иначе при попытке зашифровать\дешифровать получите ошибку -21 (что означает «некорректные входные данные»). То как с этим боремся мы можно посмотреть в приложенном коде. Сама же функция просто заполняет страницы памяти текстом, который собираемся зашифровать или дешифровать :).

Подготовим запрос для шифрования/дешифрования сообщив ему с какими страницами и текстом какой длины предстоит работать
u8  iv = {0xff, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10};
ablkcipher_request_set_crypt (req, sg, sg, strlen(text), iv);

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

Теперь приступаем непосредствнно к шифрованию/дешифрованию
ret = enc ? crypto_ablkcipher_encrypt (req) : crypto_ablkcipher_decrypt(req);

Сами функции и их параметр в пояснении думаю не нуждаются, если не понятно перечтайте еще раз внимательно о req. Ну с ret все совсем понятно скажите вы. А вот и нет :). Да ret в случае удачного выполнения действительно будет равна 0. Но вот остальные значения не всегда означают ошибку.

Теперь пришло время рассказать совсем подробно о роли переменной comp и этом механизме.
switch (ret){
case 0:
break;

case -EINPROGRESS: case -EBUSY:
printk ("\nwait\n");
ret = wait_for_completion_interruptible (&comp);
if (!ret) {
INIT_COMPLETION(comp);
break;
}

default:
printk ("failred err=%d", -ret);
goto out;
}

Вспомните описание структуры completion, непонятные слова которые мы говорили о флаге выполнения и очереди задач. Дело в том что криптоалгоритмы не так быстры как хотелось бы. Но как же так скажите вы как мы можем вернутся в вызвавщую функцию до того как завершилась вызываемая. Дело в том что авторы этой библиотеки очень хитрые люди и оптимизировали работу криптоалгоритмов в расчете на распаралеливание вашего алгоритма и с учетом что к шифруемым дешифруемым двнным будут обращатся другие алгоритмы. Поэтому и получилось так что мы можем вернутся в вызвавшую функцию до того как шифрование/дешифрование завершится. Для того чтобы избежать обращения к еще не зашифрованным/ не расшифрованым данным мы и используем структуру completion. Механизм работы этой структуры и функций с ним связанным идентичен механизму работы блокирующих семафоров. Когда работа криптоалгоритма будет завершена он выставит поле done в 1 и наша функция продолжит работу. Если вы знакомы с семафорами то вопросов не возникнет, если нет то настоятельно рекомендуем познакомится.

Далее можно для проверки распечатать зашифрованый/дешифрованный текст или делать с ним все что угодно.

Дуй ит нау
Сам модуль можно найти здесь. Код подробно прокоментирован и не должен вызывать затруднений. По сути он добалвляет свою фукцию (nethook) по обработке полученных и отправленных пакетов на определенное устройство, символьное имя которого задается параметром device_name. А за шифрование/дешифрование отвечает функция my_des, в ней пременено все о чем мы писали выше. Инструкция по сборке и установке лежит рядом. Дерзайте ;)

3 comments:

  1. Статья супер
    2.6.35-std-def-alt8 #1 SMP Fri Nov 19 07:59:49 UTC 2010 x86_64 GNU/Linux

    В чем возможна проблема?

    ...........
    make[2]: *** [/tmp/ip_hack/ip_hack.o] Error 1
    make[1]: *** [_module_/tmp/ip_hack] Error 2
    make[1]: Leaving directory `/usr/src/linux-2.6.35-std-def-alt8'
    make: *** [all] Error 2

    ReplyDelete
  2. Не тестили на 35 ядре, можем посмотреть если более полный лог пришлёте.

    Спасибо!

    ReplyDelete