UNIX: разработка сетевых приложений — страница 21 из 42

Сокеты управления ключами

19.1. Введение

С появлением архитектуры безопасности IP (IPSec, см. RFC 2401 [64]) возникла потребность в стандартном механизме управления индивидуальными ключами шифрования и авторизации. Документ RFC 2367 [73] предлагает универсальный интерфейс управления ключами, который может использоваться с IPSec и иными криптографическими сетевыми службами. Подобно маршрутизирующим сокетам, этот интерфейс принадлежит к отдельному семейству протоколов, которое называется PF_KEY. В этом семействе поддерживаются только символьные сокеты.

ПРИМЕЧАНИЕ

Как отмечалось в разделе 4.2, на большинстве систем константа AF_KEY совпадает с PF_KEY. Однако в RFC 2367 совершенно четко утверждается, что с сокетами управления ключами должна использоваться константа PF_KEY.

Для открытия символьного сокета управления ключами требуются определенные привилегии. В системах с сегментированными привилегиями для этого действия должна иметься специальная привилегия. В обычных Unix-системах открывать такие сокеты может только привилегированный пользователь.

IPSec предоставляет криптографический сервис на базе соглашений о безопасности (security association, SA). Соглашение о безопасности представляет собой сочетание адресов отправителя и получателя (а при необходимости, транспортного протокола и портов), механизма (например, аутентификации) и ключей. К одному потоку трафика может относиться несколько соглашений (например, соглашения об аутентификации и о шифровании). Набор хранящихся в системе соглашений называется базой данных соглашений о безопасности (security association database, SADB).

База SADB может использоваться не только для работы с IPSec. В ней могут иметься записи для OSPFv2, RIPv2, RSVP и Mobile-IP. По этой причине нельзя считать, что сокеты PF_KEY предназначены только для работы с IPSec.

Для работы IPSec необходима также база политик безопасности (security policy database, SPDB). Политики описывают требования к трафику, например: «трафик между узлами А и В должен аутентифицироваться при помощи заголовков аутентификации IPSec (authentication header, АН); не удовлетворяющий требованию трафик должен сбрасываться». База соглашений описывает порядок выполнения требуемых для обеспечения безопасности действий, например, если трафик между узлами А и В использует заголовки аутентификации, то в SADB содержится соответствующий алгоритм и ключи. К сожалению, стандартного механизма управления SPDB не существует. Сокеты PF_KEY работают только с базой SADB, но не с SPDB. Реализация IPSec группы KAME использует для управления SPDB расширения PF_KEY, однако никаким стандартом эти расширения не описываются.

Сокеты управления ключами поддерживают три типа операций:

1. Процесс может отправить сообщение ядру и всем остальным процессам с открытыми сокетами, записав это сообщение в свой сокет. Таким способом добавляются и удаляются записи в базе соглашений о безопасности. Таким же способом процессы, обеспечивающие собственную безопасность самостоятельно (типа OSPFv2), могут запрашивать ключи у демона-ключника (демона, управляющего ключами).

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

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

19.2. Чтение и запись

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

. Все сообщения и расширения подвергаются 64-разрядному выравниванию и дополняются до длин, кратных 64 разрядам. Все поля длины оперируют 64-разрядными единицами, то есть значение длины 1 означает реальную длину 8 байт. Расширение, не содержащее достаточного количества данных, дополняется произвольным образом до длины, кратной 64 разрядам.

Значение

sadb_msg_type
задает одну из десяти команд управления ключами. Типы сообщений перечислены в табл. 19.1. За заголовком
sadb_msg
может следовать произвольное количество расширений. Большинство сообщений имеют обязательные и необязательные расширения, которые будут описаны в соответствующих разделах. Шестнадцать типов расширений с названиями структур, их определяющих, перечислены в табл. 19.3.

Листинг 19.1. Заголовок сообщения управления ключами

struct sadb_msg {

 u_int8_t  sadb_msg_version;  /* PF_KEY_V2 */

 u_int8_t  sadb_msg_type;     /* см. табл. 19.1 */

 u_int8_t  sadb_msg_errno;    /* код ошибки */

 u_int8_t  sadb_msg_satype;   /* см. табл. 19.2 */

 u_int16_t sadb_msg_len;      /* длина заголовка и расширений / 8 */

 u_int16_t sadb_msg_reserved; /* нуль при передаче, игнорируется

                                 при получении */

 u_int32_t sadb_msg_seq;      /* порядковый номер */

 u_int32_t sadb_msg_pid;      /* идентификатор процесса отправителя

                                 или получателя */

};


Таблица 19.1. Типы сообщений

Тип сообщенияК ядруОт ядраОписание
SADB_ACQUIREЗапрос на создание записи в SADB
SADB_ADDДобавление записи в полную базу безопасности
SADB_DELETEУдаление записи
SADB_DUMPДамп SADB (используется для отладки)
SADB_EXPIREУведомление об истечении срока действия записи
SADB_FLUSHОчистка всей базы безопасности
SADB_GETПолучение записи
SADB_GETSPIВыделение SPI для создания записи SADB
SADB_REGISTERРегистрация для ответа на SADB_ACQUIRE
SADB_UPDATEОбновление записи в частичной SADB

Таблица 19.2. Типы соглашений о безопасности

Тип соглашенияОписание
SADB_SATYPE_AHАутентифицирующий заголовок IPSec
SADB_SATYPE_ESPESP IPSec
SADB_SATYPE_MIPИдентификация мобильных пользователей (Mobile IP)
SADB_SATYPE_OSPFV2Аутентификация OSPFv2
SADB_SATYPE_RIPV2Аутентификация RIPv2
SADB_SATYPE_RSVPАутентификация RSVP
SADB_SATYPE_UNSPECIFIEDHe определен

Таблица 19.3. Типы расширений PF_KEY

Тип заголовка расширенияОписаниеСтруктура
SADB_EXT_ADDRESS_DSTАдрес получателя SAsadb_address
SADB_EXT_ADDRESS_PROXYАдрес прокси-сервера SAsadb_address
SADB_EXT_ADDRESS_SRCАдрес отправителя SAsadb_address
SADB_EXT_IDENTITY_DSTЛичность получателяsadb_ident
SADB_EXT_IDENTITY_SRCЛичность отправителяsadb_ident
SADB_EXT_KEY_AUTHКлюч аутентификацииsadb_key
SADB_EXT_KEY_ENCRYPTКлюч шифрованияsadb_key
SADB_EXT_LIFETIME_CURRENTТекущее время жизни SAsadb_lifetime
SADB_EXT_LIFETIME_HARDЖесткое ограничение на время жизни SAsadb_lifetime
SADB_EXT_LIFETIME_SOFTГибкое ограничение на время жизни SAsadb_lifetime
SADB_EXT_PROPOSALПредлагаемая ситуацияsadb_prop
SADB_EXT_SAСоглашение о безопасностиsadb_sa
SADB_EXT_SENSITIVITYВажность SAsadb_sens
SADB_EXT_SPIRANGEДиапазон допустимых значений SPIsadb_spirange
SADB_EXT_SUPPORTED_AUTHПоддерживаемые алгоритмы аутентификацииsadb_supported
SADB_EXT_SUPPORTED_ENCRYPTПоддерживаемые алгоритмы шифрованияsadb_supported

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

19.3. Дамп базы соглашений о безопасности

Для дампа текущей базы соглашений о безопасности используется сообщение

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

Поле

sadb_msg_satype
позволяет запросить только записи определенного типа. Значения этого поля следует брать из табл. 19.2. При указании значения
SADB_SATYPE_UNSPEC
возвращаются все записи базы. Не все типы соглашений о безопасности поддерживаются всеми реализациями. Реализация KAME поддерживает только соглашения, относящиеся к IPSec (
SADB_SATYPE_AH
и
SADB_SATYPE_ESP
), поэтому при попытке получить дамп записей
SADB_SATYPE_RIPV2
будет возвращена ошибка
EINVAL
. Если же записей, относящихся к запрошенному типу, в таблице нет (но они поддерживаются), функция возвращает ошибку
ENOENT
.

Программа, получающая записи из базы данных безопасности, приведена в листинге 19.2.

Листинг 19.2. Дамп базы соглашений о безопасности

//key/dump.c

 1 void

 2 sadb_dump(int type)

 3 {

 4  int s;

 5  char buf[4096];

 6  struct sadb_msg msg;

 7  int goteof;


 8  s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);


 9  /* формирование и отправка запроса SADB_DUMP */

10  bzero(&msg, sizeof(msg));

11  msg.sadb_msg_version = PF_KEY_V2;

12  msg.sadb_msg_type = SADB_DUMP;

13  msg.sadb_msg_satype = type;

14  msg.sadb_msg_len = sizeof(msg) / 8;

15  msg.sadb_msg_pid = getpid();

16  printf("Sending dump message:\n");

17  print_sadb_msg(&msg, sizeof(msg));

18  Write(s, &msg, sizeof(msg));


19  printf("\nMessages returned:\n");

20  /* считывание и вывод всех ответов SADB_DUMP */

21  goteof = 0;

22  while (goteof == 0) {

23   int msglen;

24   struct sadb_msg *msgp;


25   msglen = Read(s, &buf, sizeof(buf));

26   msgp = (struct sadb_msg*)&buf;

27   print_sadb_msg(msgp, msglen);

28   if (msgp->sadb_msg_seq == 0)

29    goteof = 1;

30  }

31  close(s);

32 }


33 int

34 main(int argc, char **argv)

35 {

36  int satype = SADB_SATYPE_UNSPEC;

37  int c;


38  opterr = 0; /* отключение записи в stderr для getopt() */

39  while ((c = getopt(argc, argv, "t:")) != -1) {

40   switch (c) {

41   case 't':

42    if ((satype = getsatypebyname(optarg)) == -1)

43     err_quit("invalid -t option %s", optarg);

44    break;


45   default:

46    err_quit("unrecognized option: %c", c);

47   }

48  }


49  sadb_dump(satype);

50 }

В этом листинге мы впервые встречаемся с функцией

getopt
, определяемой стандартом POSIX. Третий аргумент представляет собой строку символов, которые могут быть приняты в качестве аргументов командной строки: в нашем случае только
t
. За символом следует двоеточие, означающее, что за ключом должно быть указано численное значение. В программах, которые могут принимать несколько аргументов, эти аргументы должны объединяться. Например, в листинге 29.3 соответствующая строка имеет вид
0i:l:v
. Это означает, что ключи
i
и
l
сопровождаются дополнительными аргументами, а
0
и
v
— не сопровождаются.

Эта функция работает с четырьмя глобальными переменными, определенными в заголовочном файле

.

extern char *optarg;

extern int optind, opterr, optopt;

Перед вызовом

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

Открытие сокета PF_KEY

1-8
 Сначала мы открываем сокет
PF_KEY
. Для этого требуются определенные привилегии, поскольку сокет дает доступ к управлению ключами.

Формирование запроса SADB_DUMP

9-15
 Мы начинаем с обнуления структуры
sadb_msg
, что позволяет нам не инициализировать поля, которые должны остаться нулевыми. Затем мы заполняем все интересующие нас поля по отдельности.

Если при открытии сокета в качестве третьего аргумента использовалась константа

PF_KEY_V2
, все сообщения, направляемые в такой сокет, должны иметь версию
PF_KEY_V2
. Нужный нам тип сообщения —
SADB_DUMP
. Длина сообщения устанавливается равной длине заголовка без расширений, поскольку для запроса дампа расширения не нужны. Наконец, идентификатор процесса устанавливается равным идентификатору нашего процесса. Это обязательное условие для всех сообщений.

Отображение и отправка сообщения SADB_DUMP

16-18
 Мы отображаем сообщение при помощи функции
print_sadb_msg
. Мы не приводим листинг этой функции, потому что он достаточно длинный и не представляет особого интереса, однако он включен в набор свободно распространяемых программ, доступный для скачивания с сайта этой книги. Функция принимает сообщение, подготовленное к отправке или полученное от ядра, и выводит всю содержащуюся в этом сообщении информацию в удобной для чтения форме.

После вызова функции подготовленное сообщение записывается в сокет.

Чтение ответов

19-30
 Программа считывает сообщения и выводит их в цикле при помощи функции
print_sadb_msg
. Последнее сообщение последовательности имеет порядковый номер 0, что мы трактуем как «конец файла».

Закрытие сокета PF_KEY

31
Мы закрываем открытый в начале работы сокет управления ключами.

Обработка аргументов командной строки

38-48
 На долю функции
main
остается не так уж много работы. Программа принимает единственный аргумент — тип соглашений о безопасности, которые должны быть запрошены из базы. По умолчанию тип равен
SADB_SATYPE_UNSPEC
. Указав другой тип в аргументе командной строки, пользователь может выбрать интересующие его записи. Наша программа вызывает нашу же функцию
getsatypebyname
, возвращающую значение типа (константу) по его названию.

Вызов функции sadb_dump

49
 Наконец, мы вызываем функцию
sadb_dump
, которая уже была описана.

Пробный запуск

Ниже приведен пример выполнения программы дампа базы данных безопасности в системе с двумя статическими соглашениями о безопасности.

macosx % dump Sending dump message:

SADB Message Dump, errno 0, satype Unspecified, seq 0, pid 20623

Messages returned:

SADB Message Dump, errno 0. satype IPsec AH, seq 1, pid 20623

SA: SPI=258 Replay Window=0 State=Mature

Authentication Algorithm: HMAC-MD5

Encryption Algorithm: None

[unknown extension 19]

Current lifetime:

0 allocations, 0 bytes

added at Sun May 18 16:28:11 2003, never used

Source address: 2.3.4.5/128 (IP proto 255)

Dest address: 6.7.8.9/128 (IP proto 255)

Authentication key. 128 bits: 0x20202020202020200202020202020202

SADB Message Dump, errno 0, satype IPsec AH, seq 0, pid 20623

SA: SPI=257 Replay Window=0 State=Mature

Authentication Algorithm: HMAC-MD5

Encryption Algorithm: None

[unknown extension 19]

Current lifetime:

0 allocations, 0 bytes

added at Sun May 18 16:26:24 2003, never used

Source address: 1.2.3.4/128 (IP proto 255)

Dest address: 5.6.7.8/128 (IP proto 255)

Authentication key, 128 bits: 0x10101010101010100101010101010101

19.4. Создание статического соглашения о безопасности

Наиболее прямолинейным методом добавления соглашения о безопасности в базу является отправка сообщения

SADB_ADD
с заполненными параметрами, которые могут задаваться вручную. Последнее затрудняет смену ключей, которая необходима для предотвращения криптоаналитических атак, но зато упрощает настройку. Элис и Боб договариваются о ключах и алгоритмах без использования линий связи. Мы приводим последовательность действий по созданию и отправке сообщения
SADB_ADD
.

Сообщение

SADB_ADD
обязано иметь три расширения: соглашение о безопасности, адрес и ключ. Оно может дополняться и другими расширениями: временем жизни, личными данными и параметром важности (sensitivity). Сначала мы опишем обязательные расширения.

Расширение SA описывается структурой

sadb_sa
, представленной в листинге 19.3.

Листинг 19.3. Расширение SA

struct sadb_sa {

 u_int16_t sadb_sa_len;     /* длина расширения / 8 */

 u_int16_t sadb_sa_exttype; /* SADB_EXT_SA */

 u_int32_t sadb_sa_spi;     /* индекс параметров безопасности (SPI) */

 u_int8_t  sadb_sa_replay;  /* размер окна защиты от повторов или нуль */

 u_int8_t  sadb_sa_state;   /* состояние SA. см. табл. 19.4 */

 u_int8_t  sadb_sa_auth;    /* алгоритм аутентификации, см. табл. 19.5 */

 u_int8_t  sadb_sa_encrypt; /* алгоритм шифрования, см. табл. 19.5 */

 u_int32_t sadb_sa_flags;   /* флаги */

};


Таблица 19.4. Использование расширений

Состояние SAОписаниеВозможность использования
SADB_SASTATE_LARVALВ процессе созданияНет
SADB_SASTATE_MATUREПолностью сформированноеДа
SADB_SASTATE_DYINGПревышено гибкое ограничение на время жизниДа
SADB_SASTATE_DEADПревышено жесткое ограничение на время жизниНет

Таблица 19.5. Алгоритмы аутентификации и шифрования

АлгоритмОписаниеСсылка
SADB_AALG_NONEБез аутентификации
SADB_AALG_MD5HMACHMAC-MD5-96RFC 2403
SADB_AALG_SHA1HMACHMAC-SHA-1-96RFC 2404
SADB_EALG_NONEБез шифрования
SADB_EALG_DESCBCDES-CBCRFC 2405
SADB_EALG_3DESCBC3DES-CBCRFC 1851
SADB_EALG_NULLNULLRFC 2410

Поле

sadb_sa_spi
содержит индекс параметров безопасности (security parameters index, SPI). Это значение вместе с адресом получателя и используемым протоколом (например, IPSec АН) уникально идентифицирует соответствующее соглашение о безопасности. При получении пакета значение SPI используется для поиска соглашения, относящегося к пакету. При отправке пакета значение помещается в него для использования получателем. Никаких иных значений SPI не имеет, поэтому назначаться индекс может последовательно, в случайном порядке или с использованием метода, рекомендуемого собеседником. Поле
sadb_sa_replay
задает размер окна защиты от повторов. Поскольку статические соглашения о защите не дают возможности задействовать эту защиту, мы устанавливаем поле равным нулю. Значение поля
sadb_sa_state
меняется в зависимости от состояния динамически создаваемых соглашений о безопасности (см. табл. 19.4). Создаваемые вручную соглашения существуют исключительно в состоянии
SADB_SASTATE_MATURE
. С другими состояниями мы встретимся в разделе 19.5.

Поля

sadb_sa_auth
и
sadb_sa_encrypt
определяют алгоритмы аутентификации и шифрования для данного соглашения. Возможные значения этих полей перечислены в табл. 19.5. Единственное значение поля
sadb_sa_flags
определено в POSIX как константа
SADB_SAFLAGS_PFS
. Этот флаг требует совершенной безопасности пересылки (perfect forward security), которая состоит в том утверждении, что значение ключа не должно зависеть от предыдущих подключений или какого-либо главного ключа. Флаг используется при запросе ключей у приложения, заведующего ими, но не при создании статических соглашений.

Следующее обязательное расширение команды

SADB_ADD
должно содержать адреса отправителя и получателя, задаваемые константами
SADB_EXT_ADDRESS_SRC
и
SADB_EXT_ADDRESS_DST
. При необходимости может быть указан адрес прокси-сервера
SADB_EXT_ADDRESS_PROXY
. Подробнее об обработке адресов прокси-серверов вы можете прочесть в RFC 2367 [73]. Адреса задаются в структуре
sadb_address
, представленной в листинге 19.4. Поле
sadb_address_exttype
определяет тип адреса (отправителя, получателя или прокси-сервера). Поле
sadb_address_proto
позволяет выбрать протокол IP или произвольный протокол (значение 0). Поле
sadb_address_prefixlen
описывает значимый префикс адреса. Это позволяет использовать одно соглашение для множества адресов. За структурой
sadb_address
следует структура
sockaddr
соответствующего семейства (например,
sockaddr_in
или
sockaddr_in6
). Номер порта из структуры
sockaddr
используется только в том случае, если поле
sadb_address_proto
задает протокол, поддерживающий номера портов (например,
IPPROTO_TCP
).

Листинг 19.4. Структура sadb_address

struct sadb_address {

 u_int16_t sadb_address_len;       /* длина расширения с адресом / 8 */

 u_int16_t sadb_address_exttype;   /* SADB_EXT_ADDRESS_{SRC,DST,PROXY} */

 u_int8_t  sadb_address_proto;     /* протокол IP или 0 (любой) */

 u_int8_t  sadb_address_prefixlen; /* # значащих битов адреса */

 u_int16_t sadb_address_reserved;  /* зарезервирован для послед. использования */

};

/* далее следует структура sockaddr соответствующего семейства */

Завершают список обязательных расширений сообщения

SADB_ADD
ключи аутентификации и шифрования — расширения
SADB_EXT_KEY_AUTH
и
SADB_EXT_KEY_ENCRYPT
, описываемые структурой
sadb_key
(листинг 19.5). Поле
sadb_key_exttype
определяет тип ключа (ключ аутентификации или шифрования), поле
sadb_key_bits
задает длину ключа в битах, а сам ключ следует за структурой
sadb_key
.

Листинг 19.5. Структура sadb_key

struct sadb_key {

 u_int16_t sadb_key_len;      /* длина расширения с ключом / 8 */

 u_int16_t sadb_key_exttype;  /* SADB_EXT_KEY_{AUTH,ENCRYPT} */

 u_int16_t sadb_key_bits;     /* # битов в ключе */

 u_int16_t sadb_key_reserved; /* зарезервировано для расширения */

};

/* далее следуют данные о самом ключе */

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

Листинг 19.6. Программа, использующая команду SADB_ADD

//key/add с

 33 void

 34 sadb_add(struct sockaddr *src, struct sockaddr *dst, int type, int alg,

 35  int spi, int keybits, unsigned char *keydata)

 36  {

 37  int s;

 38  char buf[4096], *p; /* XXX */

 39  struct sadb_msg *msg;

 40  struct sadb_sa *saext;

 41  struct sadb_address *addrext;

 42  struct sadb_key *keyext;

 43  int len;

 44  int mypid;


 45  s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);


 46  mypid = getpid();


 47  /* Формирование и запись запроса SADB_ADD */

 48  bzero(&buf, sizeof(buf));

 49  p = buf;

 50  msg = (struct sadb_msg*)p;

 51  msg->sadb_msg_version = PF_KEY_V2;

 52  msg->sadb_msg_type = SADB_ADD;

 53  msg->sadb_msg_satype = type;

 54  msg->sadb_msg_pid = getpid();

 55  len = sizeof(*msg);

 56  p += sizeof(*msg);


 57  saext = (struct sadb_sa*)p;

 58  saext->sadb_sa_len = sizeof(*saext) / 8;

 59  saext->sadb_sa_exttype = SADB_EXT_SA;

 60  saext->sadb_sa_spi = htonl(spi);

 61  saext->sadb_sa_replay = 0; /* статические ключи не защищают от повтора */

 62  saext->sadb_sa_state = SADB_SASTATE_MATURE;

 63  saext->sadb_sa_auth = alg;

 64  saext->sadb_sa_encrypt = SADB_EALG_NONE;

 65  saext->sadb_sa_flags = 0;

 66  len += saext->sadb_sa_len * 8;

 67  p += saext->sadb_sa_len * 8;


 68  addrext = (struct sadb_address*)p;

 69  addrext->sadb_address_len = (sizeof(*addrext) + salen(src) + 7) / 8;

 70  addrext->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;

 71  addrext->sadb_address_proto = 0; /* any protocol */

 72  addrext->sadb_address_prefixlen = prefix_all(src);

 73  addrext->sadb_address_reserved = 0;

 74  memcpy(addrext + 1, src, salen(src));

 75  len += addrext->sadb_address_len * 8,

 76  p += addrext->sadb_address_len * 8;


 77  addrext = (struct sadb_address*)p;

 78  addrext->sadb_address_len = (sizeof(*addrext) + salen(dst) + 7) / 8;

 79  addrext->sadb_address_exttype = SADB_EXT_ADDRESS_DST;

 80  addrext->sadb_address_proto = 0; /* any protocol */

 81  addrext->sadb_address_prefixlen = prefix_all(dst);

 82  addrext->sadb_address_reserved = 0;

 83  memcpy(addrext + 1, dst, salen(dst));

 84  len += addrext->sadb_address_len * 8;

 85  p += addrext->sadb_address_len * 8;


 86  keyext = (struct sadb_key*)p;

 87  /* обеспечивает выравнивание */

 88  keyext->sadb_key_len = (sizeof(*keyext) + (keybits / 8) + 7) / 8;

 89  keyext->sadb_key_exttype = SADB_EXT_KEY_AUTH;

 90  keyext->sadb_key_bits = keybits;

 91  keyext->sadb_key_reserved = 0;

 92  memcpy(keyext + 1, keydata, keybits / 8);

 93  len += keyext->sadb_key_len * 8;

 94  p += keyext->sadb_key_len * 8;


 95  msg->sadb_msg_len = len / 8;

 96  printf("Sending add message:\n");

 97  print_sadb_msg(buf, len);

 98  Write(s, buf, len);


 99  printf("\nReply returned:\n");

100  /* считывание и вывод ответа SADB_ADD, игнорируя любые другие */

101  for (;;) {

102   int msglen;

103   struct sadb_msg *msgp;


104   msglen = Read(s, &buf, sizeof(buf));

105   msgp = (struct sadb_msg*)&buf;

106   if (msgp->sadb_msg_pid == mypid &&

107    msgp->sadb_msg_type == SADB_ADD) {

108    print_sadb_msg(msgp, msglen);

109    break;

110   }

111  }

112  close(s);

113 }

Открытие сокета PF_KEY и сохранение PID

55-56
 Как и в предыдущей программе, мы открываем сокет PF_KEY и сохраняем идентификатор нашего процесса для последующего его использования.

Формирование общего заголовка сообщений

47-56
 Мы формируем заголовок сообщения
SADB_ADD
. Поле
sadb_msg_len
устанавливается непосредственно перед отправкой сообщения, поскольку оно должно соответствовать истинной его длине. В переменной
len
хранится текущая длина сообщения, а указатель р всегда указывает на первый неиспользуемый байт буфера.

Добавление расширения SA

57-67
 Мы добавляем обязательное расширение
SA
(см. листинг 19.3). Поле
sadb_sa_spi
должно иметь сетевой порядок байтов, поэтому нам приходится применять функцию
htonl
к значению в порядке байтов узла. Мы отключаем защиту от повторов и устанавливаем состояние
SA
равным
SADB_SASTATE_MATURE
(см. табл. 19.4). Алгоритм аутентификации выбирается в соответствии с аргументом командной строки, а шифрование отключается при помощи константы
SADB_EALG_NONE
.

Добавление адреса отправителя

68-76
 К сообщению добавляется расширение
SADB_EXT_ADDRESS_SRC
, содержащее адрес отправителя для соглашения о безопасности.

Значение протокола устанавливается равным нулю, что подразумевает действительность соглашения для всех протоколов. Длина префикса устанавливается равной соответствующей длине версии IP (то есть 32 разряда для IPv4 и 128 разрядов для IPv6). При расчете значения поля длины мы добавляем к реальному значению число 7 перед делением на 8, что гарантирует выравнивание по 64-разрядной границе, обязательное для всех расширений, передаваемых через сокеты PF_KEY. Структура

sockaddr
копируется в буфер после заголовка расширения.

Добавление адреса получателя

77-85
 Адрес получателя добавляется в сообщение
SADB_EXT_ADDRESS_DST
. Процедура в точности совпадает с описанной выше.

Добавление ключа

86-94
 К сообщению добавляется расширение
SADB_EXT_KEY_AUTH
, содержащее ключ авторизации. Расчет поля длины производится точно так же, как и для обоих адресов. Ключ переменной длины требует соответствующего количества дополняющих нулей. Мы устанавливаем значение количества битов и копируем ключ вслед за заголовком расширения.

Запись сообщения в сокет

95-98
 Мы выводим сообщение на экран вызовом функции
print_sadb_msg
, после чего записываем его в сокет.

Считывание ответа

99-111
 Мы считываем все сообщения из сокета до тех пор, пока не будет получено сообщение, адресованное нашему процессу (проверяется по PID) и имеющее тип
SADB_ADD
. Это сообщение выводится на экран функций
print_sadb_msg
, после чего программа завершает работу.

Пример

Мы запускаем программу, требуя от нее установки соглашения о безопасности, касающегося трафика между узлами 127.0.0.1 и 127.0.0.1 (то есть локального трафика):

macosx % add 127.0.0.1 127.0.0.1 HMAC-SHA-1-96 160 \

 0123456789abcdef0123456789abcdef01234567

Sending add message:

SADB Message Add, errno 0, satype IPsec AH, seq 0, pid 6246

SA: SPI=39030 Replay Window=0 State=Mature

Authentication Algorithm: HMAC-SHA-1

Encryption Algorithm: None

Source address: 127.0.0.1/32

Dest address: 127.0.0.1/32

Authentication key. 160 bits: 0x0123456789abcdef0123456789abcdef01234567

Reply returned:

SADB Message Add, errno 0, satype IPsec AH, seq 0, pid 6246

SA: SPI=39030 Replay Window=0 State=Mature

Authentication Algorithm: HMAC-SHA-1

Encryption Algorithm: None

Source address: 127.0.0.1/32

Dest address: 127.0.0.1/32

Обратите внимание, что в ответе системы отсутствует ключ. Дело в том; что ответ направляется на все сокеты PF_KEY, которые, однако, могут принадлежать к разным доменам, а данные о ключах не должны передаваться между доменами. После добавления записи в базу данных мы даем команду

ping 127.0.0.1
, чтобы проверить, задействуется ли соглашение о безопасности, после чего запрашиваем дамп базы данных и смотрим, что в ней изменилось.

macosx % dump

Sending dump message:

SADB Message Dump, errno 0, satype Unspecified, seq 0, pid 6283

Messages returned:

SADB Message Dump, errno 0, satype IPsec AH, seq 0, pid 6283

SA: SPI=39030 Replay Window=0 State=Mature

Authentication Algorithm: HMAC-SHA-1

Encryption Algorithm: None

[unknown extension 19]

Current lifetime:

36 allocations. 0 bytes

added at Thu Jun 5 21:01:31 2003, first used at Thu Jun 5 21:15:07 2003

Source address: 127.0.0.1/128 (IP proto 255)

Dest address: 127.0.0.1/128 (IP proto 255)

Authentication key. 160 bits: 0x0123456789abcdef0123456789abcdef01234567

Из этого дампа видно, что ядро изменило значение протокола с 0 на 255. Это артефакт реализации, а не общее свойство сокетов PF_KEY. Кроме того, ядро изменило длину префикса с 32 на 128. Это какая-то проблема, связанная с протоколами IPv4 и IPv6. Ядро возвращает расширение (с номером 19), которое не обрабатывается нашей программой выведения дампа. Неизвестные расширения пропускаются (их длина имеется в соответствующем поле). Наконец, возвращается расширение времени жизни (листинг 19.7), содержащее информацию о текущем времени жизни соглашения о безопасности.

Листинг 19.7. Структура расширения времени жизни

struct sadb_lifetime {

 u_int16_t sadb_lifetime_len;     /* длина расширения / 8 */

 u_int16_t sadb_lifetime_exttype; /* SADB_EXT_LIFETIME_{SOFT,HARD,CURRENT} */

 u_int32_t sadb_lifetime_allocations; /* количество соединений, конечных

                                       точек или потоков */

 u_int64_t sadb_lifetime_bytes;   /* количество байтов */

 u_int64_t sadb_lifetime_addtime; /* время создания либо время от создания

                                     до устаревания */

 u_int64_t sadb_lifetime_usetime; /* время первого использования или время от

                                     первого использования до устаревания */

};

Расширения времени жизни бывают трех типов. Расширения

SADB_LIFETIME_SOFT
и
SADB_LIFETIME_HARD
задают гибкое и жесткое ограничения на время жизни соглашения. Сообщение
SADB_EXPIRE
отправляется ядром в случае превышения гибкого ограничения на время жизни. После достижения жесткого ограничения использование соглашения прекращается. Расширение
SADB_LIFETIME_CURRENT
возвращается в ответ на
SADB_DUMP
,
SADB_EXPIRE
и
SADB_GET
и описывает соответствующие параметры текущего соглашения.

19.5. Динамическое управление SA

Для повышения безопасности требуется периодическая смена ключей. Обычно для этого используется протокол типа IKE (RFC 2409 [43]).

ПРИМЕЧАНИЕ

В момент написания этой книги рабочая группа IETF по IPSec разрабатывала замену для протокола IKE.

Демон, обеспечивающий безопасность, регистрируется в ядре при помощи сообщения

SADB_REGISTER
, указывая в поле
sadb_msg_satype
(см. табл. 19.2) тип соглашения о безопасности, которое он умеет обрабатывать. Если демон может работать с несколькими типами соглашений, он должен отправить несколько сообщений
SADB_REGISTER
, зарегистрировав в каждом из них ровно один тип SA. В ответном сообщении
SADB_REGISTER
ядро указывает поддерживаемые алгоритмы шифрования или аутентификации (в отдельном расширении), а также длины ключей для этих алгоритмов. Расширение поддерживаемых алгоритмов описывается структурой
sadb_supported
, представленной в листинге 19.8. Структура содержит заголовок, за которым следуют описания алгоритма шифрования или аутентификации в полях
sadb_alg
.

Листинг 19.8. Структура, описывающая поддерживаемые алгоритмы

struct sadb_supported {

 u_int16_t sadb_supported_len;      /* длина расширения и списка алгоритмов / 8 */

 u_int16_t sadb_supported_exttype;  /* SADB_EXT_SUPPORTED_{AUTH,ENCRYPT} */

 u_int32_t sadb_supported_reserved; /* зарезервировано для расширения в будущем */

};


/* далее следует список алгоритмов */

struct sadb_alg {

 u_int8_t  sadb_alg_id;       /* идентификатор алгоритма из табл. 19.5 */

 u_int8_t  sadb_alg_ivlen;    /* длина IV или нуль */

 u_int16_t sadb_alg_minbits;  /* минимальная длина ключа */

 u_int16_t sadb_alg_maxbits;  /* максимальная длина ключа */

 u_int16_t sadb_alg_reserved; /* зарезервировано для расширения в будущем */

};

После заголовка

sadb_supported
следует по одной структуре
sadb_alg
для каждого алгоритма, поддерживаемого системой. На рис. 19.1 представлен возможный ответ на сообщение, регистрирующее обработчик SA типа
SADB_SATYPE_ESP
.

Рис. 19.1. Данные, возвращаемые ядром в ответ на команду SADB_REGISTER

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

Листинг 19.9. Регистрация демона-обработчика

//key/register.c

 1 void

 2 sadb_register(int type)

 3 {

 4  int s;

 5  char buf[4096]; /* XXX */

 6  struct sadb_msg msg;

 7  int goteof;

 8  int mypid;


 9  s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);


10  mypid = getpid();


11  /* формирование и отправка запроса SADB_REGISTER */

12  bzero(&msg, sizeof(msg));

13  msg.sadb_msg_version = PF_KEY_V2;

14  msg.sadb_msg_type = SADB_REGISTER;

15  msg.sadb_msg_satype = type;

16  msg.sadb_msg_len = sizeof(msg) / 8;

17  msg.sadb_msg_pid = mypid;

18  printf("Sending register message:\n");

19  print_sadb_msg(&msg, sizeof(msg));

20  Write(s, &msg, sizeof(msg));


21  printf("\nReply returned:\n");

22  /* Чтение и вывод ответа SADB_REGISTER, игнорирование всех прочих

       сообщений */

23  for (;;) {

24   int msglen;

25   struct sadb_msg *msgp;


26   msglen = Read(s, &buf, sizeof(buf));

27   msgp = (struct sadb_msg*)&buf;

28   if (msgp->sadb_msg_pid == mypid &&

29    msgp->sadb_msg_type == SADB_REGISTER) {

30    print_sadb_msg(msgp, msglen);

31    break;

32   }

33  }

34  close(s);

35 }

Открытие сокета PF_KEY

1-9
 Мы открываем сокет PF_KEY.

Сохранение PID

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

Создание сообщения SADB_REGISTER

11-17
 Подобно
SADB_DUMP
, сообщение
SADB_REGISTER
не требует никаких расширений. Мы обнуляем сообщение, после чего заполняем интересующие нас поля структуры.

Вывод и отправка сообщения

18-20
 Мы отображаем подготовленное сообщение на экране при помощи функции
print_sadb_msg
, после чего записываем сообщение в сокет.

Ожидание ответа

23-30
 Мы считываем сообщения из сокета, ожидая ответа на наше сообщение о регистрации. Ответ адресован по идентификатору процесса и представляет собой сообщение
SADB_REGISTER
. Он содержит список поддерживаемых алгоритмов, который выводится нашей функцией
print_sadb_msg
.

Пример

Мы запускаем программу

register
в системе, поддерживающей на несколько протоколов больше, чем описано в RFC 2367.

macosx % register -t ah

Sending register message:

SADB Message Register, errno 0, satype IPsec AH, seq 0, pid 20746

Reply returned:

SADB Message Register, errno 0, satype IPsec AH, seq 0, pid 20746

Supported authentication algorithms:

HMAC-MD5 ivlen 0 bits 128-128

HMAC-SHA-1 ivlen 0 bits 160-160

Keyed MD5 ivlen 0 bits 128-128

Keyed SHA-1 ivlen 0 bits 160-160

Null ivlen 0 bits 0-2048

SHA2-256 ivlen 0 bits 256-256

SHA2-384 ivlen 0 bits 384-384

SHA2-512 ivlen 0 bits 512-512

Supported encryption algorithms:

DES-CBC ivlen 8 bits 64-64

3DES-CBC ivlen 8 bits 192-192

Null ivlen 0 bits 0-2048

Blowfish-CBC ivlen 8 bits 40-448

CAST128-CBC ivlen 8 bits 40-128

AES ivlen 16 bits 128-256

Если ядру требуется связаться с собеседником, а соответствующая политика требует наличия соглашения о безопасности, но соглашение таковое отсутствует, ядро отправляет на зарегистрировавшиеся для данного типа соглашения сокеты управления ключами сообщение

SADB_ACQUIRE
, в расширениях которого содержатся предлагаемые ядром алгоритмы и длины ключей. Предложение может представлять собой комбинацию поддерживаемых системой средств безопасности и политики, ограничивающей набор средств для конкретного собеседника. Алгоритмы, длины ключей и времена жизни объединяются в список в порядке предпочтительности использования. Когда демон-ключник получает сообщение
SADB_ACQUIRE
, он выполняет действия, необходимые для выбора ключа, удовлетворяющего одной из предложенных ядром комбинаций, и устанавливает этот ключ в ядро. Для выбора SPI из нужного диапазона демон отправляет ядру сообщение
SADB_GETSPI
. В ответ на это сообщение ядро создает соглашение о безопасности в состоянии
SADB_SASTATE_LARVAL
. Затем демон согласовывает параметры безопасности с удаленным собеседником, используя предоставленный ядром SPI, после чего отправляет ядру сообщение
SADB_UPDATE
для завершения создания соглашения и перевода его в рабочее состояние (
SADB_SASTATE_MATURE
). Динамически создаваемые соглашения обычно снабжаются гибким и жестким ограничениями на время жизни. Когда истекает один из этих сроков, ядро отправляет сообщение
SADB_EXPIRE
, в котором указывается, какое именно достигнуто ограничение. По достижении гибкого ограничения соглашение переходит в состояние
SADB_SASTATE_DYING
, в котором оно еще может использоваться, однако процессу следует получить новое соглашение. Если же достигнуто жесткое ограничение, соглашение переходит в состояние
SADB_SASTATE_DEAD
, в котором оно больше не может использоваться для обеспечения безопасности и должно быть удалено из базы данных.

19.6. Резюме

Сокеты управления ключами используются для взаимодействия с ядром, демонами-ключниками и другими обеспечивающими безопасность сущностями (такими как маршрутизирующие демоны). Соглашения о безопасности могут создаваться статически или динамически посредством протокола согласования ключей. Динамические ключи обычно характеризуются определенным временем жизни, по истечении которого (гибкое ограничение) демон-ключник получает соответствующее уведомление. Если соглашение не обновляется до достижения жесткого ограничения, оно становится недействительным.

Между процессами и ядром через сокет управления ключами могут передаваться сообщения десяти типов. Каждому типу сообщений сопоставляются обязательные и необязательные расширения. Все сообщения, отправляемые процессом, передаются на все открытые сокеты управления ключами (однако при этом из сообщений удаляются расширения, содержащие «уязвимые» данные).

Упражнения

1. Напишите программу, открывающую сокет PF_KEY и выводящую все получаемые через этот сокет сообщения.

2. Изучите сведения о новом протоколе, предложенном рабочей группой IETF по IPSec взамен IKE. Эти сведения находятся на странице

http://www.ietf.org/html.charters/ipsec-charter.html
.

Глава 20