Потроха IPsec, меримся с TLS 1.3, ГОСТ и Go
Приветствую! Очень хочется рассказать про устройство современного стэка IPsec протоколов ESPv3 и IKEv2. IPsec, как мне кажется, незаслуженно обходится многими стороной и детального разбора его работы, его протоколов и возможностей я не видел на русском языке. Кроме того, сделаю странное — сравню IPsec ESPv3 и IKEv2 (оба 2005-го года) с современным, модным, state-of-art TLS 1.3 2018-го года.
Почему я вообще так увлечён темой IPsec — возможно самого сложного стэка протоколов для защиты сетей? Ведь сложность это главный враг надёжности и безопасности! Во-первых, чем больше узнаёшь про его протоколы, особенно IKEv2, тем больше понимаешь как много возможностей в него закладывалось и впечатляешься его продуманностью, в отличии от распространённого подхода разработчиков «костыль костылём погоняет» и решением серьёзных проблем «пока гром не грянет». Во-вторых, IPsec протоколы хорошо продуманы с криптографической точки зрения и, даже старые ESP/IKEv1, фактически являются единственными промышленными массово используемыми протоколами в которых не было сколь либо серьёзных уязвимостей. Тот же SSL (1995-ый год) стал достойно продуманным только с версии 1.3. А нелюбовь к IPsec у многих связана с монструозной сложностью IKEv1, которой больше нет в v2.
В идеале, если бы разработчики операционных систем не тормозили в своё время с реализацией и внедрением IPsec и IPv6 (для доступности компьютеров, чтобы никакого NAT), то никаких SSL/TLS не должно было появится в принципе. Мир оказался не идеален, но сейчас IPsec из коробки (хотя бы SA/SP + ESP часть стэка) есть в любой хоть сколько-то распространённой ОС (лично мне известна только DragonFly BSD, выпилившая IPsec из-за нехватки разработчиков для его поддержки), а IPv6 в некоторых развитых странах сразу доступен преобладающему большинству людей.
IPsec это стэк протоколов, вызовов API, framework для того чтобы приложения и/или администратор могли сообщать какая им нужна безопасность при связи и она прозрачно бы обеспечивалась на сетевом уровне (IP security). Речь может идти как про IP пакеты только одного сокета (например TCP соединения), так и про трафик между целыми сетями.
Под безопасностью трафика подразумевается: обеспечение конфиденциальности данных, их аутентичности/целостности и защиты от атак перепроигрывания (replay attack). Как и практически все протоколы, IPsec имеет транспортную часть, обеспечивающую защиту IP пакетов, и часть рукопожатия, связанную с согласованием ключей, параметров, конфигурации и аутентификацией сторон.
TLS 1.3: обеспечивает только per-socket защиту данных TCP соединения. DTLS может обеспечить защиту датаграмм (DTLS 1.3 стандарта ещё нет), но далеко не каждая библиотека это поддерживает.
Транспортные протоколы
Для IPsec транспорта используются IP протоколы:
- AH (authentication headers). Про AH я дальше говорить не буду, так как он не обеспечивает конфиденциальности данных и, насколько слышал, его сделали исключительно чтобы как-то «мириться» с законами некоторых стран в 1990-х об ограничениях использования шифрования. Шифрование настолько легковесно относительно всего остального, что не имеет смысла им жертвовать. Но почти везде, где упоминается ESP, также подразумевается и AH.
- ESP (encapsulating security payloads). ESP со временем немного эволюционировал и сейчас используется его ESPv3 версия, которая часто обратно совместима и не отличается от прошлой версии.
Безопасность 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 для его штатной обработки.
SP, SA, SPI и наше первое IPsec шифрование
Как ядро узнает что надо делать с IP пакетом: шифровать ли его на каком-то ключе, дешифровать ли пришедший ESP или пропускать не трогая? Для этого в ядре есть политики безопасности (Security Policies (SP)). Это правила как в firewall-е. Кроме них, в ядре присутствуют Security Associations (SA): контексты для выполнения криптографических операций (ключи, счётчики, replay окна, и т.д.). В общем случае, ни SP не являются IPsec-специфичным, ни SA — они могут использоваться и для других задач/протоколов (например для OSPF).
Настройка SP/SA может производится как через специальный API (PF_KEYv2), так и руками через какую-нибудь setkey утилиту. Например, если мы хотим сообщить ядру, что все 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 шифрования и случайный 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
Схематично 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) ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- SPI — 32-бит уникальный идентификатор сессии/туннеля/соединения ESP между IP адресами. Как правило, по {SrcIP, DstIP, SPI} находится SA и криптографический контекст.
- SeqNum — 32-бит последовательный номер пакета. Увеличивается с каждым отправляемым пакетом. Нужен для понимания не является ли пакет повтором, для защиты от replay attack.
- payload — полезная нагрузка ESP, варьируемый размер.
- TFC padding — опциональные данные (Traffic Flow Confidentiality), используемые исключительно для того, чтобы дополнить размер пакета до какого-то заданного размера, скрывая настоящий размер передаваемых данных. Длина TFC нигде в явном виде не задана, поэтому его можно использовать только тогда, когда payload может понять свои границы, свой размер. Например, если payload это IP пакет, содержащий длину. Зачастую поддержка TFC не требует какого-либо изменения в ядре, которое просто отбросит лишнее.
- Padding — ESP требует чтобы payload был выровнен по 32-бит границе, для удобства работы. Кроме того, некоторые режимы шифрования (например CBC) требуют кратность открытого текста размеру шифроблока. Это поле используется для дополнения размера под эти требования. Может быть нулевой длины.
- Pad Length — 8-бит длина Padding поля.
- Next Header — 8-бит идентификатор IP протокола находящегося в payload. Есть особый идентификатор «no next header», обозначающий ESP-пакет пустышку — это можно использовать для скрытия фактов отсутствия полезного трафика, для генерирования трафика константной скорости. Вместе с TFC это позволяет полностью скрыть факты наличия полезных пакетов и их размеров — мало кто может предложить такой уровень безопасности метаданных.
- ICV — Integrity Check Value, содержащий аутентификационные данные (MAC).
Вся часть пакета от 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
Как же конкретно происходит шифрование/аутентификация 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 (я бы сказал, улучшенной версией GCM), а также применяется регулярная ESPTREE ротация ключевого материала, используя HMAC-Стрибог-256. Это уменьшает нагрузку на ключ. Главным образом, в контексте IPsec, это нужно не столько для увеличения времени его использования, сколько для уменьшения поверхности атаки по побочным каналам. Например из-за key meshing (схожей технологии постоянной ротации ключа), ГОСТ 28147–89 блочный шифр с 64-бит размером блока оказался неуязвим к SWEET32 атаке.
С точки зрения безопасности, к 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-а.
ISAKMP
Что будет если я перезагружу компьютеры, у них из памяти пропадёт SA со всеми значениями счётчика, а я снова руками загружу прежние SP/SA команды? Во-первых, пакеты, у которых совпадёт IV, можно будет дешифровать, так как это равносильно двойному использованию шифроблокнота. Во-вторых, раз 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. Мне известны:
- KINK — Kerberized Internet Negotiation of Keys, где для аутентификации и согласования используется третья доверенная Kerberos KDC сторона. Кроме описания из Wikipedia я больше про KINK ничего не знаю и не видел в живую.
- IKE(v1) — Internet Key Exchange. Возможно до сих пор самый популярный протокол, хоть и создан аж в 1998-ом году.
- IKEv2 — вторая версия IKE, 2005-го года, про которую я и буду рассказывать.
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 сертификаты).
IKE демон
+-------------+ |Демон ключей| +-------------+ | | | | | | Приложения/userspace =====[PF_KEY]====[PF_INET]==================== | | Ядро ОС +-----------+ +-------------+ |База данных| |TCP/IP, | | SA и SP |---|включая IPsec| +-----------+ +-------------+ | +-----------+ | Сетевой | | интерфейс | +-----------+
Эта часть стэка IPsec уже работает, как правило, в userspace. Во-первых, это не сильно нагруженные демоны: между собой они могут вообще хоть раз в сутки связываться, а начальный handshake занимает считанные round-trip-ы по UDP. Во-вторых, количество возможностей ISAKMP/IKE такое, что и кода в сотни раз больше чем в полной реализации SA/SP/ESP. Реализаций ISAKMP демонов масса: strongSwan (IKEv1/v2) (а также Openswan, Libreswan), isakmpd (IKEv1), OpenIKED (IKEv2), racoon (IKEv1), racoon2 (IKEv1/v2, KINK) и другие.
Заметка: правильнее бы писать и говорить «деймоны» (daemons), как это я встречал в переводах художественной литературы. Но в технических русскоязычных кругах уже прижились «демоны».
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 для приложения.
Потроха IKE
Первым обменом (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 — 64-бит IKE SA Initiator SPI. Случайно сгенерированный инициатором IKE сессии идентификатор.
- SPIr — 64-бит IKE SA Responder SPI. Аналогично, но только SPI со стороны ответчика. В самом первом сообщении от инициатора это поле заполнено нулевыми байтами.
- NP — 8-бит Next payload. Идентификатор полезной нагрузки идущей после заголовка.
- Version — 8-бит версия IKE протокола.
- ExchType — 8-бит тип IKE обмена: IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA или INFORMATIONAL.
- Flags — 8-бит разных флагов. Единственным интересным флагом является указание является ли сообщение исходящим от инициатора или же это ответ.
- MsgID — 32-бит порядковый номер сообщения. Используется для обнаружения дубляжа пакетов, их пропажи, replay-атак. Инкрементируется при каждом новом обмене — пара request/response будет иметь один и тот же MsgID. Повторяемые сообщения от инициатора обязаны нести тот же номер, чтобы ответчик мог понять что это идёт повтор.
- Len — 32-бит длина всего сообщения (заголовок + полезная нагрузка).
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 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- Next Payload — 8-бит указывающие какой тип полезной нагрузки пойдёт после текущей. Все payload-ы связаны в цепочку и каждый предыдущий указывает кто пойдёт после него. Нулевое значение означает что это последний payload. Указатель типа первого payload находится в IKE заголовке. Единственное исключение это Encrypted Payload, который обязан быть последним payload-ом и его NP равен не нулю (хоть это и последний payload в контексте IKE сообщения), а типу первого payload-а внутри шифротекста.
- C — флаг «критичности» payload. Если IKEv2 демон не знает и не умеет обрабатывать payload и он помечен как критичный, то IKE сессию придётся прекратить. Не критичный payload можно проигнорировать. В IKE есть много vendor-specific расширений, не представляющих интереса для стороннего ПО.
- Len — 16-бит полная длина payload (заголовок + содержимое).
Таким образом, IKE сообщения состоят из IKE заголовка и связанных между собой в цепочку payload-ов. Не критичные и неизвестные payload-ы демоны могут проигнорировать. Содержимое payload-а типа Nonce (после заголовка) это просто случайный набор данных, не фиксированного размера. Но есть и гораздо более сложные структуры. В IKE стандартах приняты короткие обозначения типов payload-ов (например N* для nonce сообщений, где »*» это или «i» (initiator) или «r» (responder)).
SIGMA
С криптографической точки зрения, IKEv1/IKEv2 относятся к STS, ISO/IEC IS 9798–3 и SIGMA (SIGn-and-MAc) классу протоколов аутентифицированного обмена ключами. Это очень хорошо изученные и математически верифицированные (SIGMA) решения. В своей «P2P F2F E2EE IM за один вечер» статье я уже описывал принцип работы и реализацию SIGMA-I протокола. IKEv2 полностью аналогичен. Когда мы обсуждаем безопасность протокола рукопожатия, то что ожидаем?
- конфиденциальность передаваемых сообщений;
- аутентичность и целостность передаваемых сообщений — их изменение должно быть обнаружено;
- защиту от атак перепроигрывания (replay attack) — факт пропажи или повтора сообщений должен быть обнаружен;
- двустороннюю идентификацию и аутентификацию собеседников;
наличие perfect forward secrecy свойства (PFS) — компрометация нашего долгоживущего PSK или ключа подписи не должна приводить к возможности чтения предыдущих сообщений (а IKE сообщения могут содержать ключевой материал ESP SA). Запись перехваченного трафика становится бесполезной;
действительность/валидность сообщений (транспортных и рукопожатия) только в пределах одной IKE сессии. Вставка корректно подписанных/аутентифицированных сообщений из другой сессии (даже с этим же собеседником) не должна быть возможной;
пассивный наблюдатель не должен видеть ни идентификаторов сторон, ни передаваемых долгоживущих публичных ключей, ни хэшей от них. Некая анонимность от пассивного наблюдателя.
После такого 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 ключевой материал
После 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 функции. Но если HKDF предполагает использование хэш-функций, то данная PRF/PRF+ конструкция может использоваться и просто с симметричными шифрами — в случае с распространённым AES-GCM + AES-XCBC-PRF у нас вообще нигде не будет использоваться хэш-функция, а малое количество используемым примитивов всегда хорошо.
Вырабатываются следующие ключи:
- SK_d ключ для выработки ключей дочерних ESP SA.
- SK_a[ir] ключи для аутентификации IKE сообщений. Не вырабатывается/не используется если согласован AEAD алгоритм (AES-GCM, Кузнечик/Магма-MGM, ChaCha20-Poly1305, и т.д.).
- SK_e[ir] ключи шифрования IKE сообщений.
- SK_p[ir] ключи используемые при вычислении аутентификаторов AUTH.
TLS 1.3: имеет значительно более сложное ключевое расписание (key scheduling). Выжимка энтропии производится сразу из целых сообщений рукопожатия, а не отдельных полей. Генерируемая расширенная последовательность не просто режется на ряд ключей (+соль для них, когда требуется), но и сопровождается HMAC преобразованиями с метками (label) для каждого контекста применения этих ключей или генерируемых IV. Использование текстовых label/application/context для любого рода вырабатываемых значений это хорошая современная практика, которую проще всегда делать, чем раздумывать нужна ли она. Хэширование вообще всего что попадается — тоже очень хорошая практика, «хуже не будет». Однако, это всё не означает что безопасность IKEv2 хуже или что можно легко придумать хотя бы отдалённо теоретическую ситуацию когда отсутствие label может быть на руку злоумышленнику. В IKEv2 подход минималистичности, а у TLS 1.3 «лучше перебдеть» (ибо сколько косяков или сложностей было понаделано в предыдущих версиях протокола!). IKEv2 всё равно использует проверенные подходы и примитивы, аутентифицирует всё что надо, выжимает/учитывает всю передаваемую энтропию, для каждой стороны и задачи применяет отличающиеся ключи.
IKE_AUTH
Дальше производится IKE_AUTH обмен, аутентифицирующий обе стороны и согласующий ESP SA:
SK{IDi, [CERT, ...], [CERTREQ], [IDr], AUTH, SAi2, TSi, TSr} --> <-- SK{IDr, [CERT, ...], AUTH, SAr2, TSi, TSr}
- IKE сообщения содержат зашифрованный (SK) payload, внутри которого находятся все остальные.
- Инициатор предоставляет свой идентификатор (IDi), аутентификатор (AUTH), SA предложение по ESP (SAi2) и пару initiator/responder, так называемых, селекторов трафика (traffic selectors (TS*)). Он также может опционально послать и ожидаемый идентификатор ответчика, что можно считать неким аналогом SNI из TLS.
- В ответ он получает идентификатор ответной стороны, согласованное предложение по SA ESP, подтверждённые селекторы трафика и аутентификатор.
- После чего, обе стороны считают друг друга аутентифицированными, имеют договорённость об ESP SA, трафике который должен относится к этому ESP и могут в ядро уже отдать команду на создание SA и, возможно, SP (есть демоны вообще не занимающиеся SP).
Теперь более подробно об этих payload-ах:
- ID — идентификатор стороны. Содержит тип идентификации и специфичные для неё данные. Идентифицироваться стороны могут уймой способов: IPv4/IPv6 адресу, FQDN (fully qualified domain name, просто строка, самый популярный способ), RFC822 email адресу, ASN.1 DER Distinguished Name (самый распространённый способ при использовании X.509 сертификатов) или General Name, а также vendor-specific.
- AUTH — тип аутентификации и специфичный для неё аутентификатор. На практике это либо значение PRF функции (используемой как функция MAC-а), ключом которой является заранее распределённый ключ (pre-shared key (PSK)), либо цифровая подпись. Аутентифицируются следующие данные (TBS*):
TBSi = Msg0 || Nr || PRF(SK_pi, IDi) TBSr = Msg1 || Ni || PRF(SK_pr, IDr)
Инициатор явно аутентифицирует полностью своё первое сообщение (Msg0), nonce противоположной стороны (Nr), и свой идентификатор (IDi), «связывая» вместе контекст использования