- PVSM.RU - https://www.pvsm.ru -
Приветствую! Очень хочется рассказать про устройство современного стэка IPsec [1] протоколов ESPv3 [2] и IKEv2 [3]. IPsec, как мне кажется, незаслуженно обходится многими стороной и детального разбора его работы, его протоколов и возможностей я не видел на русском языке. Кроме того, сделаю странное — сравню IPsec ESPv3 [4] и IKEv2 [5] (оба 2005-го года) с современным, модным, state-of-art TLS 1.3 [6] 2018-го года.
Почему я вообще так увлечён темой IPsec — возможно самого сложного стэка протоколов для защиты сетей? Ведь сложность это главный враг надёжности и безопасности! Во-первых, чем больше узнаёшь про его протоколы, особенно IKEv2, тем больше понимаешь как много возможностей в него закладывалось и впечатляешься его продуманностью, в отличии от распространённого подхода разработчиков «костыль костылём погоняет» и решением серьёзных проблем «пока гром не грянет». Во-вторых, IPsec протоколы хорошо продуманы с криптографической точки зрения и, даже старые ESP/IKEv1, фактически являются единственными промышленными массово используемыми протоколами в которых не было сколь либо серьёзных уязвимостей. Тот же SSL (1995-ый год) стал достойно продуманным только с версии 1.3. А нелюбовь к IPsec у многих связана с монструозной сложностью IKEv1, которой больше нет в v2.
В идеале, если бы разработчики операционных систем не тормозили в своё время с реализацией и внедрением IPsec и IPv6 [7] (для доступности компьютеров, чтобы никакого NAT), то никаких SSL/TLS не должно было появится в принципе. Мир оказался не идеален, но сейчас IPsec из коробки (хотя бы SA/SP + ESP часть стэка) есть в любой хоть сколько-то распространённой ОС (лично мне известна только DragonFly BSD [8], выпилившая IPsec из-за нехватки разработчиков для его поддержки), а IPv6 в некоторых развитых странах сразу доступен преобладающему большинству людей.
IPsec это стэк протоколов, вызовов API, framework для того чтобы приложения и/или администратор могли сообщать какая им нужна безопасность при связи и она прозрачно бы обеспечивалась на сетевом уровне (IP security). Речь может идти как про IP пакеты только одного сокета (например TCP соединения), так и про трафик между целыми сетями.
Под безопасностью трафика подразумевается: обеспечение конфиденциальности данных, их аутентичности/целостности и защиты от атак перепроигрывания (replay attack). Как и практически все протоколы, IPsec имеет транспортную часть, обеспечивающую защиту IP пакетов, и часть рукопожатия, связанную с согласованием ключей, параметров, конфигурации и аутентификацией сторон.
TLS 1.3: обеспечивает только per-socket защиту данных TCP соединения. DTLS [9] может обеспечить защиту датаграмм (DTLS 1.3 стандарта ещё нет), но далеко не каждая библиотека это поддерживает.
Для IPsec транспорта используются IP протоколы:
Безопасность IP-трафика обеспечивается только транспортным уровнем. А так как речь может идти о многих миллионах пакетов в секунду, то де-факто ESP реализуется на ядерном уровне операционной системы, в её сетевом стэке, как минимум, чтобы не делать дорогущее переключение контекста между ядром и userspace (как это штатно происходит с TLS, SSH, OpenVPN, и прочими).
Подчёркиваю, что AH и ESP это протоколы IP-уровня, сетевого, а не транспортного. Почему не UDP? Контрольная сумма избыточна и сжигает CPU, а криптография и так обеспечит целостность. Но, если ваш NAT ничего не знает про ESP (а он не знает), то работать это всё за ним не будет. Позже придумали костыли в виде NAT-T (NAT traversal), когда IPsec трафик оборачивается в UDP пакет на 4500 порту и сможет проходить через NAT, но это лишний overhead и необходимость править IPsec стэк в ядре, ведь именно оно уже должно понимать эти особые UDP пакеты и доставать из них ESP для его штатной обработки.
Как ядро узнает что надо делать с IP пакетом: шифровать ли его на каком-то ключе, дешифровать ли пришедший ESP или пропускать не трогая? Для этого в ядре есть политики безопасности (Security Policies (SP)). Это правила как в firewall-е. Кроме них, в ядре присутствуют Security Associations (SA): контексты для выполнения криптографических операций (ключи, счётчики, replay окна, и т.д.). В общем случае, ни SP не являются IPsec-специфичным, ни SA — они могут использоваться и для других задач/протоколов (например для OSPF).
Настройка SP/SA может производится как через специальный API (PF_KEYv2), так и руками через какую-нибудь setkey [10] утилиту. Например, если мы хотим сообщить ядру, что все IP пакеты идущие с fc::123 адреса на fc::321 надо обезопашивать через ESP, то это легко сделать вызовом из командной строки:
$ echo "spdadd fc00::123 fc00::321 any -P out ipsec esp/transport//require;" | setkey -c
До этой команды мы видели ping-и:
IP6 fc00::123 > fc00::321: ICMP6, echo request, seq 0, length 16
IP6 fc00::321 > fc00::123: ICMP6, echo reply, seq 0, length 16
IP6 fc00::123 > fc00::321: ICMP6, echo request, seq 1, length 16
IP6 fc00::321 > fc00::123: ICMP6, echo reply, seq 1, length 16
После не увидим, так как ядро пока ещё не знает «чем» надо шифровать. Нужно добавить SA и это тоже можно сделать вручную, задав для простоты AES-GCM-16 алгоритм AEAD [11] шифрования и случайный 160-бит ключ:
echo "add fc00::123 fc00::321 esp 0xdeadbabe -E aes-gcm-16 0x0c09d1d90f804b0b4cef80e255e29c0894db1928 ;" | setkey -c
Если на удалённом хосте мы выполним те же команды (только не забыв указать -P in), то увидим:
IP6 fc00::123 > fc00::321: ESP(spi=0xdeadbabe,seq=0x1), length 52
IP6 fc00::321 > fc00::123: ICMP6, echo reply, seq 0, length 16
IP6 fc00::123 > fc00::321: ESP(spi=0xdeadbabe,seq=0x2), length 52
IP6 fc00::321 > fc00::123: ICMP6, echo reply, seq 1, length 16
Request зашифрован ESP, а reply нет. Потому что ESP работает по умолчанию в «одну сторону» и для двусторонней связи нужно зеркально добавить ещё SP/SA для противоположного направления.
0xdeadbabe в данном примере это Security Parameters Index (SPI) — уникальный идентификатор ESP «туннеля» между двумя IP адресами, по которому ядро может найти соответствующий SA контекст и взять из него ключ дешифрования. А esp/transport//require это требование использовать ESP в транспортном режиме (об этом ниже).
Схематично ESP пакет устроен так:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---
| Security Parameters Index (SPI) | ^
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | A
| Sequence Number | | u
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | t
~ IV (variable) ~ | h
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | e -----
| Payload Data (variable) | | n ^ E
~ ~ | t | n
| | | i | c
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | c | r
| | TFC Padding * (optional, variable) | | a | y
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | t | p
| | Padding (0-255 bytes) | | e | t
+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | d | e
| | Pad Length | Next Header | v v d
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---------
~ Integrity Check Value-ICV (variable) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Вся часть пакета от payload до Next Header зашифрована. Всё, кроме MAC, аутентифицировано. Длина ICV, наличие IV (initialization vector, вектора инициализации) зависит от используемых режимов/алгоритмов шифрования и аутентификации.
TLS 1.3: опциональный padding данных до заданного размера появился только в версии 1.3. В остальном, шифрование и аутентификация полностью схожи. TLS 1.3 обязывает использовать только AEAD алгоритмы, что правильно и хорошо. ESP поддерживает AEAD, но есть выбор и более архаичных решений. Полей типа SPI или SeqNum нет, так как TCP гарантирует очерёдность и доставку, кроме того, на практике и не передаётся в явном виде никакого вектора инициализации — TLS record layer пакет поэтому немного короче. DTLS уже содержит SeqNum, а также данные касающиеся фрагментации сообщения.
32-бит номер пакета на практике может оказаться слишком коротким. Это всего лишь 4+ млрд. IP пакетов, что на 10+ Mpps скоростях может пролететь за считанные минуты. Что будет когда счётчик переполнится? Он обнулится. Но, это будет означать, что значение SPI+SeqNum у нас начнёт повторяться и ранее перехваченные ESP пакеты можно будет использовать для replay атаки. Для решения этой проблемы был придуман ESN (Extended Sequence Number). Это 64-бит счётчик, но только «нижние» 32-бита которого передаются в SeqNum поле, а верхние 32-бита хранятся в памяти. Аутентифицируется полное значение ESN — поэтому стороны обязаны согласовать факт применения ESN заранее.
Как же конкретно происходит шифрование/аутентификация ESP пакета, например, при использовании AES-GCM-16? Для работы с ESP, в нём используется 64-бит вектор инициализации, находящийся в начале payload. Кроме того, используется 32-бит соль, являющаяся частью ключевого материала. В примере для setkey я предоставил не 128-бит ключ, а 128+32-бит. Могут быть ситуации, когда ключ используется повторно, а IV заполняется плохим генератором псевдослучайных чисел (PRNG), значения которого могут повториться. Соль призвана обезопасить от этого опаснейшего случая, приводящего потенциально к дешифрованию перехваченных пакетов. Само шифрование/аутентификация ESP в AES-128-GCM-16-ESP режиме происходит так:
AES-GCM(
key = 128-bit key,
plaintext = 64-bit IV || payload || TFC || pad || padLen || NH,
nonce = 32-bit salt || IV,
associated-data = SPI || {ESN или SeqNum},
) -> encrypted-payload || 128-bit ICV
ESP = SPI || SeqNum || IV || encrypted-payload || ICV
Для российских ГОСТовых алгоритмов (шифры Магма или Кузнечик) входные данные аналогичны. Оба шифра используются в режиме MGM [12] (я бы сказал, улучшенной версией GCM [13]), а также применяется регулярная ESPTREE [14] ротация ключевого материала, используя HMAC-Стрибог-256. Это уменьшает нагрузку на ключ. Главным образом, в контексте IPsec, это нужно не столько для увеличения времени его использования, сколько для уменьшения поверхности атаки по побочным каналам. Например из-за key meshing (схожей технологии постоянной ротации ключа), ГОСТ 28147-89 блочный шифр с 64-бит размером блока оказался неуязвим к SWEET32 [15] атаке.
С точки зрения безопасности, к ESP с AEAD алгоритмами нареканий нет. Но для AEAD алгоритмов IV является просто 64-бит счётчиком, явно передаваемым с каждым пакетом, тратящим место в пакете. SeqNum слишком короток, а ESN полностью не передаётся, хотя полностью подошёл бы в качестве IV. Для не AEAD алгоритмов IV может быть уже необходим и нести непредсказуемое значение, но, ни в коем случае, не счётчик. Это legacy, отъедающее драгоценное место в пакете и вес тут на надёжность не влияет.
Если бы IV для AEAD-ов мог иметь значения от 128-бит, то можно было бы использовать алгоритмы типа XSalsa20/XChaCha20 с 192-бит nonce-ом, 128-бит которого псевдослучайно генерировать при запуске, а оставшиеся 64-бит использовать для счётчика. Это могло бы быть спасением для систем которые потеряли своё состояние счётчиков, но хотят продолжать использовать уже имеющиеся ключи.
TLS 1.3: в качестве nonce используется XOR между счётчиком сообщений и вектором инициализации, выработанным вместе с ключом. Так как, ни счётчик, ни IV не передаются в явном виде, то TLS 1.3 немного компактнее. Если в ESP используются не AEAD алгоритмы, то они могут потребовать генерирования непредсказуемого IV, что может быть ощутимо ресурсоёмко для CPU.
Что попадает в payload пакета? Это зависит от того, в каком режиме работает ESP: транспортном или туннельном. Транспортный режим заменяет payload передаваемого IP пакета на ESP с этим payload-ом. То есть, было:
---------------------------------------
| orig IP hdr |[ext hdrs]| TCP | Data |
---------------------------------------
стало:
---------------------------------------------------------
| orig |hop-by-hop,dest*,| |dest| | | ESP | ESP|
|IP hdr|routing,fragment.|ESP|opt*|TCP|Data|Trailer| ICV|
---------------------------------------------------------
|<--- encryption ---->|
|<---- authenticity ----->|
В туннельном режиме весь IP пакет полностью оборачивается в ESP и формируется новый IP пакет, как правило, с новыми заголовками и адресами SrcIP/DstIP. Этот режим используется для туннелирования пакетов между сетями.
----------------------------------------------------------
| new* |new ext| | orig*|orig ext| | | ESP | ESP|
|IP hdr| hdrs* |ESP|IP hdr| hdrs * |TCP|Data|Trailer| ICV|
----------------------------------------------------------
|<--------- encryption --------->|
|<---------- authenticity ---------->|
Например, через setkey я могу указать, что все пакеты между 2001:ac::/64 и 2001:dc::/64 сетями должны проходить в зашифрованном виде через два endpoint-а туннеля с адресами 2001::123, 2001::321.
spdadd 2001:ac::/64 2001:dc::/64 any -P out ipsec esp/tunnel/2001::123-2001::321/require ;
spdadd 2001:dc::/64 2001:ac::/64 any -P in ipsec esp/tunnel/2001::321-2001::123/require ;
Транспортный режим часто называют host-to-host подключением. Если для туннелирования используется какой-нибудь GRE или IPv*-over-IPv* протокол, уже работающий между двумя endpoint-ами, то смысла, в данном случае, применять режим туннелирования на уровне IPsec уже нет. Однако, транспортный режим не аутентифицирует IP-заголовок. Как правило это не важно и не критично, но если хочется убедиться что никакие расширенные заголовки IPv6 или flow label пакетов не изменены, то тогда стоит применять туннельный режим, пускай даже между двумя хостами, ценой overhead-а.
Что будет если я перезагружу компьютеры, у них из памяти пропадёт SA со всеми значениями счётчика, а я снова руками загружу прежние SP/SA команды? Во-первых, пакеты, у которых совпадёт IV, можно будет дешифровать, так как это равносильно двойному использованию [16] шифроблокнота. Во-вторых, раз SPI/salt/ESN/SeqNum совпадают, то все ранее перехваченные пакеты будут валидно аутентифицированы и можно сделать их replay. Повторное использование таких setkey-установленных SA катастрофично для безопасности. В-третьих, особенно если не используется ESN (например, в FreeBSD на момент написания, ещё не поддерживается), при долгой работе SA можно не заметить что счётчик «израсходован».
Всё это значит, что нам нужно регулярно менять ESP ключи. А ещё договариваться об алгоритме шифрования, наличии ESN, TFC, транспортном/туннельном режиме, значениях SPI. Де-факто для этого используется ISAKMP протокол (Internet Security Association and Key Management Protocol). Хотя, с лёгкостью можно прикрутить какой-нибудь IM с OTR/PGP/OMEMO аутентифицированным шифрованием и просто отсылать shell скриптом setkey команды на сервер, в которых ключи генерируются читая /dev/urandom. Ядру не имеет значения как что было согласовано. Как в OpenVPN: аутентификация X.509 сертификатами и согласование ключей происходит вообще по TLS, а сам транспортный VPN протокол уже свой.
В «чистом» виде ISAKMP не применяется, так как в нём нет никакой криптографии. Для аутентификации собеседников и выработки ключевого материала применяется сторонний протокол, внутри себя инкапсулирующий ISAKMP. Мне известны:
IKE протоколы очень расширяемы за счёт большого количества разных типов payload-ов. IKEv1 имеет большое количество опций для конфигурации одного только туннеля для своей работы. Не один десяток RFC описывающий в целом всю связку ISAKMP и IKEv1 с распространёнными payload-ами. Пугающая сложность. Плюс возможность легко напортачить в не fool-proof конфигурации и известный, отчасти заслуженно верный, миф что гарантированно IKEv1 будет работать только если, чуть ли не полностью, скопировать конфигурационный файл.
Благо, появился IKEv2: одна удобная RFC (для большинства features), существенно упрощённый протокол согласования параметров и, соответственно, его конфигурации. Как правило, в нём меньше round-trip-ов на весь процесс рукопожатия и согласования ключей чем в IKEv1. Поэтому рассматриваться будет только он, так как смысла в IKEv1 уже нет (но и гнаться заменять уже запущенные и работающие instance тоже вряд ли стоит, раз работают). IKEv2, в отличии от IKEv1, для шифрования собственных сообщений использует абсолютно аналогичные алгоритмы и подходы как и ESP. Также в нём появилась EAP-аутентификация и возможность каждой стороны аутентифицироваться разными методами (например клиент использует PSK, а сервер X.509 сертификаты).
+-------------+
|Демон ключей|
+-------------+
| |
| |
| | Приложения/userspace
=====[PF_KEY]====[PF_INET]====================
| | Ядро ОС
+-----------+ +-------------+
|База данных| |TCP/IP, |
| SA и SP |---|включая IPsec|
+-----------+ +-------------+
|
+-----------+
| Сетевой |
| интерфейс |
+-----------+
Эта часть стэка IPsec уже работает, как правило, в userspace. Во-первых, это не сильно нагруженные демоны: между собой они могут вообще хоть раз в сутки связываться, а начальный handshake занимает считанные round-trip-ы по UDP. Во-вторых, количество возможностей ISAKMP/IKE такое, что и кода в сотни раз больше чем в полной реализации SA/SP/ESP. Реализаций ISAKMP демонов масса: strongSwan [18] (IKEv1/v2) (а также Openswan [19], Libreswan [20]), isakmpd [21] (IKEv1), OpenIKED [22] (IKEv2), racoon [23] (IKEv1), racoon2 [24] (IKEv1/v2, KINK) и другие.
Заметка: правильнее бы писать и говорить «деймоны» (daemons [25]), как это я встречал в переводах художественной литературы. Но в технических русскоязычных кругах уже прижились «демоны».
TLS 1.3: в общем случае, весь стэк TLS является библиотечными функциями работающими в каждом отдельном приложении и его же памяти хранящими ключевой материал. Вся криптография при этом выполняется с переключением в userspace, что огромный overhead. Однако, как минимум, в FreeBSD и Linux уже есть и ядерные offload реализации TLS, когда, аналогично IPsec, транспортная часть обрабатывается полностью в ядре, а рукопожатие происходит в userspace.
IKEv2 работает поверх UDP, по умолчанию на 500-ом порту (isakmp сервис). Демоны создают безопасный канал, аутентифицируют друг друга, согласуют/создают/удаляют ESP SA/SP, обновляют ключи, делают heartbeat (Dead Peer Detection (DPD)) и многое другое. Всё общение демонов между собой происходит в виде обмена парой request/response сообщений. На любой запрос должен быть ответ. Раз это UDP, то что делать при пропаже пакета? Учитывать это в своём state, перепосылать запросы после timeout на которые не получены ответы, перепосылать ответы на повторные запросы, игнорировать повторные ответы. Пакеты могут приходить в хаотичном порядке, могут непредсказуемо пропадать — многое учтено в IKEv2 стандарте и описано как себя надо вести при различных race condition-ах.
TLS 1.3: TCP-природа TLS берёт на себя все заботы о порядке и доставке сообщений. Но TCP занимает ощутимые ресурсы в ядре ОС и огромное количество TCP сессий может стать проблемой (в отличии от UDP). Но в DTLS все схожие проблемы аналогично возникнут как и в IKE, плюс добавится геморрой с обработкой фрагментированных сообщений. Смена IP адресов endpoint-ов для UDP не является проблемой. IKE соединения, как правило, очень долгоживущие (IKE state небольшой и хранится только в памяти userspace демона) и поэтому реже требуют делать рукопожатие, тогда как в TLS после потери TCP соединения придётся проделывать (хотя есть и ускоренные методы продолжения сессий, если state не был потерян, например при перезапуске программы). Так как IKE демон один на всю систему (как правило), то если какое-то приложение захочет безопасной связи с тем, с кем уже имеется IKE соединение, то он его или сразу же может использовать или демон, одним round-trip-ом, создаст дополнительный ESP SA для приложения.
Первым обменом (request-response) демонов будет IKE_SA_INIT, создающий IKE SA для дальнейшего безопасного общения. Замечу, что ESP SA «хранится» в ядре, а IKE SA в userspace демоне. Затем идёт IKE_AUTH обмен, где происходит аутентификация сторон. В этом же обмене происходит и создание дочернего SA (Child SA), использующийся для ESP SA. В общем случае, достаточно этих двух обменов чтобы аутентифицировать стороны и согласовать ESP SA параметры с ключами и дальше уже гонять шифрованный ESP трафик между компьютерами. Между демонами при этом на долгое время остаётся работающий IKE SA. Дальше, в любое время, они могут произвести CREATE_CHILD_SA обмен, для создания ещё дочерних SA, а также INFORMATIONAL обмен (самые разные цели).
Заголовок всех IKEv2 сообщений имеет следующую структуру:
1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IKE SA Initiator's SPI |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IKE SA Responder's SPI |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Next Payload | Version | Exchange Type | Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SPIi+SPIr занимают 128-бит. Зачем так много, когда в ESP всего 32-бита отводится? Во-первых, так как они не согласуются, а, псевдослучайно генерируются, то 64-бит для одной стороны будет достаточно чтобы не было коллизий. Во-вторых, ESP также привязан и к IP адресам, а IKE сессия в общем случае нет — стороны спокойно могут менять свои IP адреса (мобильный клиент) и продолжать общение.
TLS 1.3: смена IP адреса приведёт к разрыву соединения. Нужно будет делать rehandshake, даже с iPSK, экономя на ресурсах для асимметричной криптографии, это 1.5 round-trip плюс round-trip-ы для установки TCP соединения. Создание дочерних ESP SA на новых IP адресах, в уже установленном IKE соединении (не имеющим привязки к адресам), займёт всего один round-trip (+round-trip на удаление старых, но это уже произойдёт в фоне нового работающего ESP SA).
После IKE заголовка идёт одна или более полезных нагрузок (payload-ов). Каждый payload имеет заголовок общего формата, а дальше специфичное для его типа содержимое. Содержимое выровнено по 32-бит границе. Общий для всех заголовок:
1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Next Payload |C| RESERVED | Payload Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Таким образом, IKE сообщения состоят из IKE заголовка и связанных между собой в цепочку payload-ов. Не критичные и неизвестные payload-ы демоны могут проигнорировать. Содержимое payload-а типа Nonce (после заголовка) это просто случайный набор данных, не фиксированного размера. Но есть и гораздо более сложные структуры. В IKE стандартах приняты короткие обозначения типов payload-ов (например N* для nonce сообщений, где "*" это или «i» (initiator) или «r» (responder)).
С криптографической точки зрения, IKEv1/IKEv2 относятся к STS, ISO/IEC IS 9798-3 и SIGMA (SIGn-and-MAc) классу протоколов аутентифицированного обмена ключами. Это очень хорошо изученные и математически верифицированные (SIGMA) решения. В своей «P2P F2F E2EE IM за один вечер» [26] статье я уже описывал принцип работы и реализацию SIGMA-I протокола. IKEv2 полностью аналогичен. Когда мы обсуждаем безопасность протокола рукопожатия, то что ожидаем?
После такого IKE_SA_INIT обмена у демонов есть адреса друг друга, SPIi+SPIr значение IKE сессии, согласованные SA алгоритмы (в случае IKE-а это алгоритмы согласования ключей (DH), шифрования/аутентификации сообщений (ENCR), выработки ключей (PRF)), публичный ключ (DH) противоположной стороны. Этого достаточно чтобы сохранить в памяти state и выполнить согласование ключей (Диффи-Хельман, ГОСТ Р 34.10-VKO, curve25519 и тому подобные), выработав симметричный ключ для шифрования полезной нагрузки последующих IKE сообщений.
TLS 1.3: формат сообщений рукопожатия сильно отличается, есть много legacy, но принципиально ничего выделяющегося. Вместо nonce используется поле random. Вместо payload-ов многочисленные extensions. Вместо сложной SA proposal структуры, используются идентификаторы шифронаборов (ciphersuites), что компактнее и проще. На мой взгляд, гибкость SA proposals избыточна, но в IKEv2 это всё равно не проблема и в конфигурационном файле прописываются похожие на ciphersuite значения. Только с TLS 1.3 версии становится обязательным DH обмен.
После IKE_SA_INIT вырабатывается SKEYSEED:
SKEYSEED = PRF(Ni[:8] || Nr[:8], DH-KEY)
PRF алгоритм выбирается в IKE SA. Например для ГОСТ IKEv2 это функция HMAC-Стрибог-512. Ключом PRF является 64-бит кусок от каждого из nonce-ов.
Выглядит несерьёзно, ведь nonce-ы передаются в открытую, а значит и ключ для данного PRF известен любому кто перехватил трафик. Но PRF тут используется исключительно для выработки ключа из, уже неизвестного злоумышленнику DH-KEY, результата вычисления DH. Результатом DH функции может быть огромное и неравномерно содержащее энтропию значение, может быть точка на эллиптической кривой — всё это нельзя использовать в качестве короткого высокоэнтропийного симметричного ключа. Поэтому нужно сделать «выжимку» (extract) энтропии из DH-KEY (это как-раз SKEYSEED), а дальше «расширить» (expand) её до нужного количества ключей:
PRF+(SKEYSEED, Ni || Nr || SPIi || SPIr) ->
SK_d || SK_ai || SK_ar || SK_ei || SK_er || SK_pi || SK_pr
PRF+(K,S) = T1 || T2 || T3 || T4 || ...
T1 = PRF(K, S || 0x01)
T2 = PRF(K, T1 || S || 0x02)
T3 = PRF(K, T2 || S || 0x03)
T4 = PRF(K, T3 || S || 0x04)
Всё это классическая операция выработки ключей с extract/expand стадиями, аналогичная HKDF [28] функции. Но если HKDF предполагает использование хэш-функций, то данная PRF/PRF+ конструкция может использоваться и просто с симметричными шифрами — в случае с распространённым AES-GCM + AES-XCBC-PRF у нас вообще нигде не будет использоваться хэш-функция, а малое количество используемых примитивов всегда хорошо.
Вырабатываются следующие ключи:
TLS 1.3: имеет значительно более сложное ключевое расписание (key scheduling). Выжимка энтропии производится сразу из целых сообщений рукопожатия, а не отдельных полей. Генерируемая расширенная последовательность не просто режется на ряд ключей (+соль для них, когда требуется), но и сопровождается HMAC преобразованиями с метками (label) для каждого контекста применения этих ключей или генерируемых IV. Использование текстовых label/application/context для любого рода вырабатываемых значений это хорошая современная практика, которую проще всегда делать, чем раздумывать нужна ли она. Хэширование вообще всего что попадается — тоже очень хорошая практика, «хуже не будет». Однако, это всё не означает что безопасность IKEv2 хуже или что можно легко придумать хотя бы отдалённо теоретическую ситуацию когда отсутствие label может быть на руку злоумышленнику. В IKEv2 подход минималистичности, а у TLS 1.3 «лучше перебдеть» (ибо сколько косяков или сложностей было понаделано в предыдущих версиях протокола!). IKEv2 всё равно использует проверенные подходы и примитивы, аутентифицирует всё что надо, выжимает/учитывает всю передаваемую энтропию, для каждой стороны и задачи применяет отличающиеся ключи.
Дальше производится IKE_AUTH обмен, аутентифицирующий обе стороны и согласующий ESP SA:
SK{IDi, [CERT, ...], [CERTREQ], [IDr], AUTH, SAi2, TSi, TSr} -->
<-- SK{IDr, [CERT, ...], AUTH, SAr2, TSi, TSr}
Теперь более подробно об этих payload-ах:
TBSi = Msg0 || Nr || PRF(SK_pi, IDi)
TBSr = Msg1 || Ni || PRF(SK_pr, IDr)
Инициатор явно аутентифицирует полностью своё первое сообщение (Msg0), nonce противоположной стороны (Nr), и свой идентификатор (IDi), «связывая» вместе контекст использования всего этого вместе. Кроме того, он подтверждает факт выработки одинакового SK_pi (соответственно и всего ключевого материала). Ответчик аутентифицирует всё «зеркально».
Аутентификация это самая ответственная часть протоколов рукопожатия и ошибки/проблемы в ней сводят к нулю безопасность вообще всего. В доморощенных протоколах рукопожатия недостатки чаще всего с аутентификацией. Крайне важно связать сообщения обмена вместе (связать Ni и Nr), которые уже образуют некую сессию рукопожатия. А также крайне важно связать свой идентификатор с сессией, явно сказав что да, именно я участвую в этой сессии.
Многие протоколы не делают явного подтверждения корректности согласования симметричных ключей, которые будут использованы для транспорта. Если ключ не был корректно согласован (значения сторон расходятся), то первое же транспортное сообщение не будет аутентифицировано и сессию можно завершить. Во многих случаях это не является проблемой, ведь всё равно будет рано или поздно понятно что что-то не так. Многие не делают явного подтверждения знания ключа чтобы сэкономить round-trip-ы. SIGMA-протоколы делают явное подтверждение, как и IKEv2, чтобы не устанавливать в ядро ESP SA, которые ещё не известно согласован ли корректно. Некоторые протоколы посылают хэш от ключа, что плохо, так как хэш это тоже знание о ключе. SIGMA используют MAC c выработанным симметричным ключом (достаточно подтвердить корректность выработки любого из SK_*). IKEv2 использует PRF, что равносильно. Также замечу, что PRF(ID*) в открытом виде нигде не передаётся и не присутствует, поэтому его не получится использовать для оценки успешности brute-force атак (как хэш от ключа) на выработанный ключ.
При использовании PSK, аутентификатор вычисляется так:
AUTHi = PRF(PRF(PSK, "Key Pad for IKEv2"), TBSi)
AUTHr = PRF(PRF(PSK, "Key Pad for IKEv2"), TBSr)
Зачем нужен PRF(PSK) и почему не просто использовать PSK в качестве ключа? Потому что PSK может быть отличной длины от той что нужно выбранной PRF функции. PSK может быть вообще парольной фразой, не подходящей для прямого использования в функциях шифрования/аутентификации. PRF() это «выжимка» энтропии. Значение PRF(PSK) можно хранить в БД PSK вместо оригинального PSK значения, чтобы не иметь его в открытом виде (но для паролей всё равно лучше бы применять Argon2 [29], Balloon [30] и подобные средства усиления пароля).
TSi = ((proto=17, port=100, fc::123 - fc::123),
(proto=17, port=200, fc::123 - fc::123))
TSr = ((proto=17, port=300, :: - ffff:..:ffff),
(proto=17, port=400, :: - ffff:..:ffff))
означают, что инициатор будет посылать UDP пакеты (идентификатор протокола = 17), с 100-го или 200-го порта fc::123 адреса, и отсылать на любой адрес с целевыми UDP портами 300 или 400. Если идентификатор протокола нулевой, значит любой IP пакет попадает под выбор. Порты, соответственно, можно указать только для некоторых IP протоколов, где есть это понятие (как исключение, тип ICMP сообщения тоже можно указать в виде порта). Нулевое значение порта означает, что любые разрешены.
В данном примере возможны все комбинации между этими четырьмя UDP портами. Если же хочется указать жёстко, что возможна передача, например, только с 100 на 300-ый, то для этого необходимо создать отдельный ESP SA со своими селекторами трафика.
Ответная сторона присылает свой подтверждённый выбор селекторов, который или совпадает или может иметь более узкие диапазоны выбора.
Все эти payload-ы зашифрованы на выработанном IKE SA ключе после первого обмена сообщениями. Шифрование необходимо для скрытия в открытую передаваемых идентификаторов сторон, их сертификаты и прочую приватную информацию. Однако активный злоумышленник может вклиниться в первый IKE_SA_INIT обмен и увидеть эту информацию, хотя продолжить сессию уже не в состоянии.
TLS 1.3:
Шифрование SK payload-а AEAD шифрами не хитрое и полностью схоже с ESP, например для AES-GCM (в котором, аналогично AES-GCM-ESP, соль является частью ключевого материала):
AES-GCM(
key = SK_*e,
plaintext = 64-bit IV || payloads || pad || 8-bit padLen,
nonce = 32-bit salt || IV,
associated-data = IKEHdr || unencrypted payloads
) -> ciphertext
А если какая либо сторона захочет аутентифицировать подписью и X.509 сертификатами? Для этого, уже в IKE_SA_INIT может посылаться CERTREQ payload, запрашивающий противоположную сторону предоставить сертификат в виде CERT payload-ов. CERT и CERTREQ содержат идентификатор формата сертификатов и специфичное для формата содержимое. Как правило, сертификаты могут быть представлены в виде ASN.1 DER или в виде SHA1 хэша сертификата + URL откуда его можно скачать. Так как размер UDP ограничен MTU, а размеры сертификатов могут быть гораздо бОльшими, то hash+URL вариант тут спасителен (хотя его можно считать и костылём).
В одном только IKEv2 RFC перечислены, кроме DER закодированных X.509 сертификатов и SHA1+URL: PKCS #7 wrapped X.509 certificate, PGP certificate, DNS signed key, SPKI certificate, X.509 Attribute certificate, «сырые» публичные ключи. Если хочется использовать IPsec аналогично самому частому use-case TLS: аутентифицированный по X.509 сертификату сервер и анонимный клиент, то в IKEv2 нет возможности не производить аутентификацию одной из сторон. Но RFC 5386 [35] описывает Better-Than-Nothing-Security подход, при котором «клиент» может использовать голый публичный ключ и сервер сможет считать его за анонимного.
Кроме того, штатно поддерживается EAP аутентификация, добавляющая round-trip-ы в IKE_AUTH обмен. EAP может как сказать аутентифицирована сторона или нет, так ещё и выработать ключ, который IKEv2 будет учтён и использован. Покажу только схему как может работать EAP:
SAi1, KEi, Ni -->
<-- SAr1, KEr, Nr
SK{IDi, [IDr], SAi2, TSi, TSr} -->
<-- SK{IDr, AUTH, EAP}
SK{EAP} -->
<-- SK{EAP(success)}
SK{AUTH} -->
<-- SK{AUTH, SAr2, TSi, TSr}
TLS 1.3: в нём подпись (или MAC в Finished сообщении) ставится над хэшом от всех увиденных сообщений участвовавших в рукопожатии. Тоже простой и надёжный хороший подход. Разнообразия методов аутентификации нет. А ведь хотелось бы видеть какой-нибудь сильный протокол аутентифицированного согласования ключей по паролю (PAKE), типа российского SESPAKE [36] или OPAQUE [37].
Итак, мы проверили аутентификацию, подтвердили корректность согласования ключей, согласовали ESP SA и селекторы трафика. Остаётся выработать симметричные ключи для ESP и нужные SA/SP можно установить в ядро:
PRF+(SK_d, Ni || Nr) -> KEYMAT0 || KEYMAT1
Для двусторонней связи нужно по два ESP SA, поэтому IKEv2 вырабатывает сразу два ключевых материала, которые уже передаются напрямую в ядро в соответствующий SA. Длина материала зависит от используемого алгоритма ESP (так например AES-GCM-ESP требует, кроме ключа, ещё и 32-бит соль). В качестве SPI используется SPI значение указанное каждой стороной в ESP SA предложениях.
Что делать если нам надо согласовать несколько ESP SA/SP, например, потому что в единичной паре TSi/TSr не все пожелания можно указать? Для этого используются CREATE_CHILD_SA обмены, идущие в любое время после IKE_AUTH. Создание дочернего SA происходит следующим обменом:
SK{SA, Ni, [KEi], TSi, TSr} --> <-- SK{SA, Nr, [KEr], TSi, TSr}
Делается SA предложение, отправляются nonce-ы, селекторы трафика. Всё как и прежде. Ключевой материал вырабатывается уже с использованием этих новых nonce-ов. Опционально можно задействовать key exchange payload-ы, добавляющие энтропию и вынуждающие стороны использовать ещё больше асимметричной криптографии. Понадобится это может для постоянного соблюдения PFS свойства (в OTR [38] протоколе вообще с каждым сообщением отправляются эфемерные DH ключи). Ключевой материал при этом будет выработан так:
PRF+(SK_d, DH-KEY || Ni || Nr) -> KEYMAT0 || KEYMAT1
А если мы хотим обновить ключ IKE SA соединения? Делаем следующий CREATE_CHILD_SA обмен:
SK{SA, Ni, KEi} --> <-- SK{SA, Nr, KEr}
где SA будет содержать уже IKE SA предложения и будет выработан новый SKEYSEED:
PRF(SK_d_old, DH-KEY || Ni || Nr) -> SKEYSEED
Обновление ключа ESP SA производится либо через создание нового ESP SA (с другим SPI) и удалением старого, либо посылая особое оповещение (о нём ниже). Переключение трафика на использование новых ESP SA произойдёт прозрачно и без потерь. Какое-то непродолжительное время стороны будут иметь по два активных ESP SA, что позволит обрабатывать трафик ещё находящийся в пути на каналах связи.
Удаление ESP SA делается отсылкой DELETE payload-а в последующих INFORMATIONAL обменах, в котором перечисляются SPI требующие удаления. Так как все ESP SA существуют попарно (для двусторонней связи), то каждая сторона посылает значения SPI только ESP SA отвечающих за исходящий трафик. В ответ получаются SPI значения ESP SA для входящего трафика.
SK{D(SPIi)} --> <-- SK{D(SPIr)}
Удаление IKE SA делается также посредством DELETE, но уже с IKE SPI и приёмом пустого аутентифицированного ответа:
SK{D} --> <-- SK{}
TLS 1.3: есть механизм ротации ключей через KeyUpdate сообщения, но возможности внесения дополнительной энтропии или выполнения DH при этом нет. TLS явно не предназначен для очень долгоживущих соединений. В лучшем случае можно будет только разорвать сессию и продолжить/создать новую с iPSK-ECDHE рукопожатием.
IKEv1 имеет как отдельную процедуру обновления ключа IKE, так и отдельную для повторной аутентификации. В IKEv2 повторной аутентификации нет. Для этого просто с нуля создаётся новый IKE SA, старый удаляется через DELETE.
TLS 1.3: имеет post-handshake возможность аутентификации клиента, когда в любой момент после рукопожатия (Finished сообщений от обеих сторон) сервер может прислать запрос на аутентификацию клиента по X.509 сертификату. Например клиент, бродя по сайту, перешёл на страницу личного кабинета. В IKEv2 такой возможности нет — аутентификация проводится только в момент рукопожатия.
А как же согласовываются туннельный/транспортный режимы, TFC? Для этого в запрос добавляются payload-ы «оповещений» NOTIFY (N). Видов оповещений в одном только IKEv2 RFC не один десяток. Оповещения используются для сигнализации об ошибках, о проблемах согласования SA предложений, селекторов трафика и т.д…
Чтобы сигнализировать о желании использовать транспортный режим в согласуемом ESP SA, добавляется N(USE_TRANSPORT_MODE) оповещение как инициатором, так и ответчиком, для подтверждения согласования режима. N(ESP_TFC_PADDING_NOT_SUPPORTED) оповещение сигнализирует что TFC не поддерживается. А N(HTTP_CERT_LOOKUP_SUPPORTED) сигнализирует что поддерживается скачивание сертификата по URL.
Возможность обновления ключа ESP SA, без создания новых ESP SA, производится аналогично процедуре создания дочернего ESP SA, но инициатором добавляется N(REKEY_SA) оповещение, содержащее SPI текущего ESP SA:
SK{N(REKEY_SA), SA, Ni, [KEi], TSi, TSr} --> <-- SK{ SA, Nr, [KEr], TSi, TSr}
INFORMATIONAL обмен с пустым SK используется для dead peer detection (DPD), в качестве heartbeat-а между демонами. Если IKE демон долго недоступен, то, скорее всего, он и потерял своё состояние и поэтому за ESP SA на противоположной стороне никто не следит или они уже и не активны. Поэтому, когда ясно что удалённая сторона недоступна, имеет смысл удалить все относящиеся к ней ESP/IKE SA. Пустой SK означает что в нём нет payload-а, но у него есть аутентифицированные данные (как минимум, IKE заголовки с счётчиками), поэтому аутентификация такого пакета это доверенный признак жизни.
SK{} --> <-- SK{}
А что будет если одна сторона быстро перезапустилась, потеряв состояние, и начала устанавливать IKE соединение с нуля? Противоположная сторона могла даже и не заметить недоступность другой и она будет считать, что та решила произвести или процедуру повторной аутентификации или создания новых дочерних SA в другом IKE соединении. Ничего катастрофичного то не будет, но старые ESP SA ещё приличное время могут жить. Инициатор может поместить N(INITIAL_CONTACT) оповещение в свой IKE_AUTH обмен, сигнализируя что для него это единственное известное IKE соединение с этой стороной. Увидев такое аутентифицированное оповещение, можно с чистой совестью удалить все старые IKE/ESP SA.
Уже в самом начале IKE_SA_INIT посылается KEi payload с эфемерным публичным ключом DH. Но ведь инициатор ещё не обменивался IKE SA и откуда ему знать какой алгоритм поддерживает принимающая сторона? Он может только сделать предположение или запомнить в долговременной памяти что использовалось прежде при связи с этим адресом. Если ответчик не поддерживает алгоритм, то пошлёт N(INVALID_KEY_PAYLOAD) оповещение, в котором будет указан идентификатор предпочитаемого алгоритма DH. Инициатор вынужден будет повторить свой запрос, но с новым KEi.
TLS 1.3: может послать сразу несколько эфемерных публичных ключей на разных алгоритмах, авось какой-нибудь да подойдёт. Но это ресурсы и трафик. Он может вообще не посылать публичный ключ и сервер ему ответит HelloRetryRequest со своими предпочтениями — плюсом будет то, что дорогая асимметричная криптография вообще не задействуется, пока точно не станут известны предпочитаемые алгоритмы сервера, но ценой лишнего round-trip. Если клиент изначально предоставил не подходящий алгоритм публичного ключа, то, как и в IKEv2, получит HelloRetryRequest с алгоритмами на выбор.
А что будет если посылать один и тот же начальный пакет от инициатора? Возможно каждый раз генерировать там новый SPIi. Ответчик будет, как минимум, честно производить DH вычисления и отвечать IKE_AUTH. DH это очень ресурсоёмкая операция, сжигающая CPU и источник энтропии — поэтому ответчика можно будет вывести из строя.
В IKEv2 (но не IKEv1) имеется защита от этого, в виде ответа N(COOKIE) оповещением, содержащим строку-cookie, после которого инициатор должен повторить свой запрос, но добавив к нему этот N(COOKIE) payload:
SAi1, KEi, Ni -->
<-- N(COOKIE)
SAi1, KEi, Ni, N(COOKIE) -->
<-- SAr1, KEr, Nr, [CERTREQ]
Запрос должен иметь идентичные SPI/Ni что и первый. Его достаточно просто дополнить payload-ом. Ответчик может сохранить state о связи запроса и отосланной ему cookie и только после их совпадения, после выполнения этой работы по добавлению cookie к запросу инициатором, ответчик может штатно продолжить IKE_AUTH обмен.
Но можно состояние сохранить прямо внутри cookie, делая её «самоаутентифицирующейся». Она может нести факт того, что ответчик видел запрос от инициатора (видел его Ni и SPIi, как минимум):
Cookie = MAC(some-secret, Ni || SPIi || timestamp)
Таким образом, любителю DoS придётся хранить state и перерабатывать свои повторные сообщения, что делает атаку значительно дороже. Cookie-защиту имеет смысл включать только когда есть подозрения на DoS атаку, чтобы не заставлять всех выполнять лишний round-trip.
TLS 1.3: имеет аналогичную опциональную защиту. Сервер может ответить HelloRetryRequest сообщением, содержащим Cookie расширение, которое клиент должен вставить в свой повторный ClientHello2.
IKEv2 позволяет согласовать конфигурацию IP-сетей/адресов. Configuration payload (CP) позволяет сделать запрос на получение конфигурации (CFG_REQUEST/CFG_REPLY типы пакетов) и установку конфигурации противоположной стороне (CFG_SET/CFG_ACK типы). Запрос конфигурации содержит атрибуты которые сторона хочет узнать/установить. Атрибутами могут быть: «внутренний» адрес, адрес DNS, DHCP, знание о подсети, либо другие типы, описанные в смежных RFC. Например инициатор в IKE_AUTH обмене может сделать запрос на выдачу ему внутрисетевого адреса (подключаясь к сети компании) и DNS сервера:
SK{IDi, [IDr], AUTH, CP(CFG_REQUEST), SAi2, TSi, TSr} -->
<-- SK{IDr, AUTH, CP(CFG_REPLY), SAr2, TSi, TSr}
CP(CFG_REQUEST) =
INTERNAL_IP6_ADDRESS()
INTERNAL_IP6_DNS()
TSi = (proto=0, port=0-65535, :: - ffff:...:ffff)
TSr = (proto=0, port=0-65535, :: - ffff:...:ffff)
CP(CFG_REPLY) =
INTERNAL_IP6_ADDRESS(2001:db8::5/64)
INTERNAL_IP6_DNS(2001:db8::1)
INTERNAL_IP6_SUBNET(2001:db8:abcd::/64)
TSi = (proto=0, port=0-65535, 2001:db8::5 - 2001:db8::5)
TSr = (proto=0, port=0-65535, 2001:db8::0 - 2001:db8::ffff:ffff:ffff:ffff)
Для проверки современных отечественных реализаций IPsec стэка с ГОСТ алгоритмами решили написать совершенно независимую (от Linux, FreeBSD, strongSwan и прочих стэков) реализацию. А для скорости и простоты разработки на Go [39] языке, с уже существующей реализацией ГОСТ алгоритмов GoGOST [40] библиотекой. Прежде уже был опыт интеграции [41] ГОСТ в TLS 1.3 реализацию crypto/tls и crypto/x509 Go библиотек.
Проект gostipsec [42] это свободное ПО, состоящее из двух демонов: ESPER (ESPv3) и IKER (IKEv2):
┌──────┐ ┌────┐ ┌─────┐ ┌────┐
│remote│ │iker│ │esper│ │ipfw│
└──┬───┘ └─┬──┘ └──┬──┘ └─┬──┘
│ │ │ │
╔══════╤═════╪════════════════╪════════════╗ │ │
║ UDP │ │ │ ║ │ │
╟──────┘ │ IKEv2... │ ║ │ │
║ │ <─────────────── ║ │ │
║ │ │ ║ │ │
║ │ IKEv2... │ ║ │ │
║ │ ───────────────> ║ │ │
╚════════════╪════════════════╪════════════╝ │ │
│ │ │ │
│ │ │ │
│ ╔═══════════╪══╤═════════════╪════════════╗ │
│ ║ UNIX-SOCKET │ │ ║ │
│ ╟─────────────setkey-commands│ ║ │
│ ║ │ ───────────────> ║ │
│ ╚═══════════╪════════════════╪════════════╝ │
│ │ │ │
│ │ │ │
│ │ ╔════════════╪═══╤═══════════╪════════════╗
│ │ ║ DIVERT-SOCKET │ │ ║
│ │ ╟──────────────encrypted ESP │ ║
│ │ ║ │ <────────────── ║
│ │ ║ │ │ ║
│ │ ║ │ decrypted ESP │ ║
│ │ ║ │ ──────────────> ║
│ │ ║ │ │ ║
│ │ ║ │ unencrypted IP│ ║
│ │ ║ │ <────────────── ║
│ │ ║ │ │ ║
│ │ ║ │ encrypted IP │ ║
│ │ ║ │ ──────────────> ║
│ │ ╚════════════╪═══════════════╪════════════╝
│ │ │ │
На данный момент, ESPER работает только с DIVERT [43] сокетами (ничего настолько же простого под Linux я не нашёл), поэтому поддерживается только на FreeBSD (возможно OpenBSD, не проверял) ОС. ESPER, как и IKER, в качестве интерфейса между ESP<->IKE связкой, использует не PF_KEYv2, который бы потребовал C-bindings, а текстовый setkey-like интерфейс, уже упоминаемый в начале статьи. IKER поэтому можно использовать и для согласования ключей ядерной ESP реализации, вызывая настоящую setkey команду. Выглядят эти команды для ESPER так:
add fc00::ac fc00::dc esp 0x12345678 -u 123 -E aes-gcm-16 0xd3537e657fde5599a2804fbb52d1aaed94b65d3e ;
add fc00::dc fc00::ac esp 0x12345679 -u 234 -E aes-gcm-16 0x9a2dae68e475eacb39d41f23c3cbef890e9f6276 tfc:1320 ;
spdadd fc00::ac/128 fc00::dc/128 all -P in ipsec esp/transport//unique:123 ;
spdadd fc00::dc/128 fc00::ac/128 all -P out ipsec esp/transport//unique:234 ;
ESPER поддерживает: AES-128/256-GCM-16, Магма/Кузнечик-MGM, ESN, TFC, транспортный/туннельный режимы, IPv6/IPv4 (поддержка последнего, куда более сложного, не так тщательно тестировалась, да и кому нужен IPv4 [7] для новых проектов?), защиту от атак перепроигрывания. IKER же позволяет согласовать: AES-128/256-GCM-16 + AES-XCBC + curve25519, Магма/Кузнечик-MGM + HMAC-Стрибог-512 + ГОСТ Р 34.10-2012-VKO-256/512, ESN/TFC/транспортный/туннельный-режимы, аутентифицировать по PSK и X.509 цифровым подписям (ECDSA, ГОСТ Р 34.10-2012). Конфигурируется единственным Hjson [44] файлом:
{
IKEAlgos: [
gost128-vko512
aes256gcm16-aesxcbc-curve25519
aes128gcm16-aesxcbc-curve25519
]
ESPAlgos: [
gost128-esn
gost64-esn
aes256gcm16-esn
aes256gcm16-noesn
aes128gcm16-esn
aes128gcm16-noesn
]
SigHashes: [
streebog512
streebog256
sha512
sha256
]
DPDTimeout: 300
Peers: [
{
Autostart: true
OurIP: fc00::dc
TheirIP: fc00::ac
OurId: our.company.net
TheirId: CN=example.com
OurTSS: [
fc00::dc/128[tcp]
fc00::dc/128[udp/53]
]
TheirTSS: [
fc00::ac/128
]
Mode: transport
# Won't be used, because of X.509 signature authentication
PSK: DEADBABE
TheirCertHash: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
OurCert: our.company.net.cer.pem
OurPrvKey: our.company.net.key.pem
TFC: 1200
}
]
}
В этом примере мы задали единственного нам известного участника:
IKER пока не поддерживает полного набора всех фич IKEv2 (CREATE_CHILD_SA, rekeying), не блюдёт пропажу пакетов и плевать хотел на DON'T PANIC принцип. Поэтому пока не может рассматриваться как кандидат для «промышленного» использования.
Tarball [45] gostipsec уже содержит все зависимости, собранную .info документацию и цели для redo сборочной системы [46], хотя сборка исполняемых файлов легко делается и штатным go build вызовом.
Holywar-ная тема, но всё же выскажу своё фи:
[[foo.bar]]
baz = 123
[[foo.bar]]
abc = 123
</pre>
превращается в:
<pre>
{
"foo": {
"bar": [
{"baz": 123 },
{"abc": 123 }
]
}
}
и когда есть возможность «дополнять» словари/массивы в любом месте конфигурационного файла, то симпатия у меня прошла. А увидев, после перевода на TOML, во что превращаются конфиги NNCP [52] с многочисленными словарями и массивами, я зарёкся его использовать в своих проектах. Нет, это не плохой формат, но с сложными структурами он уже не так легко читается людьми.
В общем случае, если реализовывать подмножество возможностей аналогичных TLS 1.3 (аутентификация только по PSK и X.509 сертификатам, никакого серьёзного rekeying), то ESPv3 с IKEv2 и IPv6 (с ним куда проще работать!) будет, с точки зрения программиста, незначительно сложнее в реализации. RFC даже не обязывает поддерживать CREATE_CHILD_SA обмены. Безопасность будет на превосходном уровне, без спорных и опасных возможных режимов работы TLS 1.3. Производительность IPsec решения будет, как правило, выше, за счёт транспорта на ядерном уровне и долгоживущих IKE сессий.
Видно что в IPsec всё заточено под защиту колоссального количества трафика между целыми сетями, но BTNS [53] (better than nothing security) рабочая группа IETF написала несколько RFC демонстрирующих что IPsec без проблем можно использовать и для per-socket соединений, где одна из сторон (клиент) анонимна, тем самым полностью ставя под сомнение целесообразность использования TLS. Connection latching, в данном случае, позволил бы любому сетевому приложению, сделав тривиальный системный вызов типа setsockopt, указать что ему нужен ESP до FQDN=банк.com адреса, представившись X.509 сертификатом (или оставаясь анонимным), а дальше прозрачно, быстро и безопасно работая с этим банк.com, без костылей в виде userspace транспортных per-application библиотек.
Сергей Матвеев [54], шифропанк [55], Python/Go/C-разработчик, главный специалист ФГУП «НТЦ „Атлас“.
Автор: stargrave2
Источник [56]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/freebsd/356747
Ссылки в тексте:
[1] IPsec: https://ru.wikipedia.org/wiki/IPsec
[2] ESPv3: https://ru.wikipedia.org/wiki/Encapsulating_Security_Payload#Encapsulating_Security_Payload
[3] IKEv2: https://ru.wikipedia.org/wiki/IKE
[4] ESPv3: https://tools.ietf.org/html/rfc4304
[5] IKEv2: https://tools.ietf.org/html/rfc5996
[6] TLS 1.3: https://tools.ietf.org/html/rfc8446
[7] IPv6: https://habr.com/ru/post/490378/
[8] DragonFly BSD: https://www.dragonflybsd.org/
[9] DTLS: https://ru.wikipedia.org/wiki/DTLS
[10] setkey: https://www.freebsd.org/cgi/man.cgi?query=setkey
[11] AEAD: https://ru.wikipedia.org/wiki/AEAD-%D1%80%D0%B5%D0%B6%D0%B8%D0%BC_%D0%B1%D0%BB%D0%BE%D1%87%D0%BD%D0%BE%D0%B3%D0%BE_%D1%88%D0%B8%D1%84%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F
[12] MGM: https://datatracker.ietf.org/doc/draft-smyshlyaev-mgm/
[13] GCM: https://en.wikipedia.org/wiki/Galois/Counter_Mode
[14] ESPTREE: https://tools.ietf.org/html/rfc8645
[15] SWEET32: https://sweet32.info/
[16] двойному использованию: https://en.wikipedia.org/wiki/One-time_pad#Problems
[17] Kerberized Internet Negotiation of Keys: https://en.wikipedia.org/wiki/Kerberized_Internet_Negotiation_of_Keys
[18] strongSwan: https://strongswan.org/
[19] Openswan: https://www.openswan.org/
[20] Libreswan: https://libreswan.org/
[21] isakmpd: https://man.openbsd.org/isakmpd.8
[22] OpenIKED: http://www.openiked.org/
[23] racoon: http://ipsec-tools.sourceforge.net/
[24] racoon2: https://github.com/zoulasc/racoon2
[25] daemons: https://en.wikipedia.org/wiki/Daemon_(classical_mythology)
[26] «P2P F2F E2EE IM за один вечер»: https://habr.com/ru/post/452200/
[27] perfect forward secrecy: https://ru.wikipedia.org/wiki/Perfect_forward_secrecy
[28] HKDF: https://en.wikipedia.org/wiki/HKDF
[29] Argon2: https://ru.wikipedia.org/wiki/Argon2
[30] Balloon: https://crypto.stanford.edu/balloon/
[31] ESNI: https://en.wikipedia.org/wiki/ESNI#Encrypted_Client_Hello
[32] уже блокируемая DPI: https://www.opennet.ru/opennews/art.shtml?num=53520
[33] EAP: https://ru.wikipedia.org/wiki/EAP
[34] RFC 5723: https://tools.ietf.org/html/rfc5723
[35] RFC 5386: https://tools.ietf.org/html/rfc5386
[36] SESPAKE: https://habr.com/ru/post/282043/
[37] OPAQUE: https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/
[38] OTR: https://ru.wikipedia.org/wiki/Off-the-Record_Messaging
[39] Go: https://golang.org/
[40] GoGOST: http://www.gogost.cypherpunks.ru/
[41] был опыт интеграции: http://www.gostls13.cypherpunks.ru/
[42] gostipsec: http://www.gostipsec.cypherpunks.ru/
[43] DIVERT: https://www.freebsd.org/cgi/man.cgi?query=divert
[44] Hjson: https://hjson.org/
[45] Tarball: http://www.gostipsec.cypherpunks.ru/Installation.html
[46] redo сборочной системы: https://habr.com/ru/post/517490/
[47] INI: https://en.wikipedia.org/wiki/.ini
[48] capabilities database: https://www.freebsd.org/cgi/man.cgi?query=getcap
[49] YAML: https://yaml.org/
[50] проблемы с Норвегией: https://hitchdev.com/strictyaml/why/implicit-typing-removed/
[51] TOML: https://toml.io/en/
[52] конфиги NNCP: http://www.nncpgo.org/Configuration.html
[53] BTNS: https://datatracker.ietf.org/wg/btns/about/
[54] Сергей Матвеев: http://www.stargrave.org/
[55] шифропанк: http://www.cypherpunks.ru/
[56] Источник: https://habr.com/ru/post/518116/?utm_source=habrahabr&utm_medium=rss&utm_campaign=518116
Нажмите здесь для печати.