
strongSwan — опенсорсная имплементация IPsec, фреймворка VPN. Несмотря на двадцатилетний стаж, проект продолжает развиваться: последняя на сегодня версия приложения вышла в декабре. У него подробная документация, есть блог с CVE и публичная база тестов. По полезной пропускной способности, задержке и утилизации CPU strongSwan превосходит Wireguard, но остаётся в тени — из-за сложности и малой пригодности для обхода блокировок. Зато перед теми, кто не ленится, он открывает широкий простор для экспериментов.
strongSwan щедр на плагины и криптографические алгоритмы. В версии 6.0.0 к ним добавили ML-KEM — механизм инкапсуляции ключа с постквантовой стойкостью, почитать о котором можно тут. «Постквантовой» предполагает, что воспроизвести ключ ML-KEM для расшифровки трафика не получится даже на квантовом компьютере — даже потом. Пока от таких атак защищаются эфемерными ключами на эллиптических кривых, но однажды уязвимы, вслед за RSA, станут и они. В статье я расскажу, как подготовиться к этому, настроив strongSwan с ML-KEM и постквантовым предопределённым ключом (PPK).
Мои вводные: сервер на Debian 12 и сотрудница с Arch, которую надо подключить к удалённому рабочему месту в подсети 10.0.0.0/22 и панели администратора в облаке. Адрес сервера — 1.2.3.4, админки — 5.6.7.8, доступ из офиса в интернет ограничен. Аутентифицировать друг друга сервер и клиент будут по сертификатам ECDSA. Сертификаты — ещё одна сильная сторона strongSwan: приложение среди прочего поддерживает взаимную аутентификацию по TLS 1.3, чем я и воспользуюсь.
В репозиториях большинства дистрибутивов, когда я приступал к задаче, нужная сборка 6.0.0 отсутствовала: у Arch — совсем, в нестабильной ветке Debian был отключён ML-KEM, а в официальных образах Docker (раз, два) устарела библиотека. Поэтому я скомпилировал файлы из исходников: взял скрипт для 5.9.14 и добавил флаг --enable-ml. Перед компиляцией убедился, что IPv6 включён, и установил следующие пакеты:
python3-setuptools ruby3.3-dev libcurl4-gnutls-dev systemd-dev libsystemd-dev libnm-dev libgmp3-dev libssl-dev libwolfssl-dev libbotan-2-dev libgcrypt20-dev libpam0g-dev libip4tc-dev libcap-dev
Сервер
strongSwan не создаёт виртуальный интерфейс в системе, ядро которой поддерживает IPsec, но может добавлять адрес туннеля к физическому, а трафиком управляет на основе политик — определить их надо на фаерволе сервера. Я возьму для этого встроенный скрипт _updown: открою порты NAT-T и IKEv2, разрешу ESP и создам таблицы nat и mangle. nat будет заменять адрес клиента на публичный адрес сервера для доступа к облаку, mangle — ограничивать MSS: в зависимости от алгоритмов IPsec по ESP может добавлять к заголовку пакета около ста байт — обычно это приводит к фрагментации, но если на пути попадутся узлы, которые отбрасывают ICMP 3 (Destination unreachable), фрагментация сломается и TCP-сессия клиента завершится по таймауту. Уменьшение MSS помогает это предотвратить. Ещё я форсирую адаптивный MTU, заодно включу форвардинг пакетов:
bob ~ sudo cat << EOF >> /etc/sysctl.d/99-sysctl.conf net.ipv4.ip_forward = 1 net.ipv4.ip_no_pmtu_disc = 1 EOF bob ~ sudo sysctl -qp
Скопирую /usr/lib/strongswan/_updown в /etc/swanctl и в копии допишу:
up-client:) iptables -I INPUT 1 -p udp --dport 500 --j ACCEPT iptables -I INPUT 2 -p udp --dport 4500 --j ACCEPT iptables -I FORWARD 1 -m policy --pol ipsec --dir in -p esp -s 10.0.0.0/22 -j ACCEPT iptables -I FORWARD 2 -m policy --pol ipsec --dir out -p esp -d 10.0.0.0/22 -j ACCEPT iptables -t nat -I POSTROUTING 1 -m policy --pol ipsec --dir out -j ACCEPT iptables -t nat -I POSTROUTING 2 -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -m policy --pol ipsec --dir out -j ACCEPT iptables -t nat -I POSTROUTING 3 -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -j MASQUERADE iptables -t mangle -I FORWARD 1 -m policy --pol ipsec --dir in -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360 iptables -t mangle -I FORWARD 2 -m policy --pol ipsec --dir out -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360 ;; down-client:) iptables -D INPUT -p udp --dport 500 --j ACCEPT iptables -D INPUT -p udp --dport 4500 --j ACCEPT iptables -D FORWARD -m policy --pol ipsec --dir in -p esp -s 10.0.0.0/22 -j ACCEPT iptables -D FORWARD -m policy --pol ipsec --dir out -p esp -d 10.0.0.0/22 -j ACCEPT iptables -t nat -D POSTROUTING -m policy --pol ipsec --dir out -j ACCEPT iptables -t nat -D POSTROUTING -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -m policy --pol ipsec --dir out -j ACCEPT iptables -t nat -D POSTROUTING -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -j MASQUERADE iptables -t mangle -D FORWARD -m policy --pol ipsec --dir in -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360 iptables -t mangle -D FORWARD -m policy --pol ipsec --dir out -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360 ;;
Настрою PKI: с помощью одноимённой утилиты выпущу ключ и сертификат удостоверяющего центра и подпишу сертификат сервера.
bob ~ sudo pki --gen --type ecdsa --size 521 \ --outform pem > /etc/swanctl/private/ca-key.pem bob ~ sudo pki --self --ca --lifetime 1825 \ --in /etc/swanctl/private/ca-key.pem \ --type ecdsa --dn "C=RU, O=Bob LLC, CN=strongSwan CA" \ --outform pem > /etc/swanctl/x509ca/ca-cert.pem bob ~ sudo pki --gen --type ecdsa --size 521 \ --outform pem > /etc/swanctl/private/bob-key.pem bob ~ sudo pki --pub --in /etc/swanctl/private/bob-key.pem \ --type ecdsa | pki --issue --lifetime 912 \ --cacert /etc/swanctl/x509ca/ca-cert.pem \ --cakey /etc/swanctl/private/ca-key.pem \ --dn "С=RU, O=Bob LLC, CN=bob.com" --san bob.com \ --flag serverAuth --flag ikeIntermediate \ --outform pem > /etc/swanctl/x509/bob-cert.pem
Значения --dn и --san сервера важны. Если его FQDN не резолвится в публичный адрес, подойдёт сам адрес. Сертификат клиента выпускается так же, но его алиас может быть любым, а --serverAuth и --ikeIntermediate не требуются. Будь у сотрудницы клиент на Windows, macOS, Android или iOS, её ключ и сертификат пришлось бы ещё поместить в контейнер PKCS#12.
Теперь сертификат клиента:
bob ~ sudo pki --gen --type ecdsa --size 521 \ --outform pem > /etc/swanctl/private/alice-key.pem bob ~ sudo pki --pub --in /etc/swanctl/private/alice-key.pem \ --type ecdsa | pki --issue --lifetime 912 \ --cacert /etc/swanctl/x509ca/ca-cert.pem \ --cakey /etc/swanctl/private/ca-key.pem \ --dn "C=RU, O=Bob LLC, CN=alice@bob.com" --san "alice@bob.com" \ --outform pem > /etc/swanctl/x509/alice-cert.pem
Закончив с PKI, уберу с сервера ключ удостоверяющего центра — выпускать новые сертификаты станет неудобно, зато уменьшу риск компрометации инфраструктуры. Ключ и сертификат сотрудницы удалю после передачи.
Сертификаты и ключ, которые остаются на сервере:
bob ~ ( cd /etc/swanctl && ls private x509 x509ca ) private/: bob-key.pem x509/: bob-cert.pem x509ca/: ca-cert.pem
Сгенерирую PPK — строку из 64 случайных символов ASCII c энтропией 330 бит: при обмене ключом IKEv2 смешает её идентификатор с результатом ML-KEM. Сочетать последний с PPK — скорее перебор: в усилении классических криптосистем эти инструменты, хоть и не равноценны, самодостаточны, но мой наниматель — пессимист. PPKs появились в strongSwan раньше — в версии 5.7.0, поэтому в отсутствие ML-KEM можно взять их и обменяться ключом с помощью X25519.
bob ~ echo 0x$( dd if=/dev/urandom count=32 bs=1 status=none | xxd -p -c 32 ) 0xba8bd35f2ae876d60a781ff19b46580ac0b31c77ae27487324a131dc690a836e
Перехожу к настройке приложения. Меня интересуют два файла: /etc/strongswan.conf и /etc/swanctl/swanctl.conf.
Указывать префикс ppk в secrets последнего обязательно, а id должен соответствовать --san сертификата. Селекторы трафика local_ts и remote_ts в директиве children отвечают за маршрутизацию: сервер ожидает из туннеля пакеты, адресат которых входит в его local_ts — у клиента те же подсети будут в remote_ts. Конфиг ниже выдаёт клиенту адрес из 10.0.2.0/23 и туннелирует трафик к 10.0.0.0/22 и 5.6.7.8/32.
bob ~ sudo mv /etc/swanctl/swanctl.conf{,.bak} 2> /dev/null bob ~ sudo sed '1d;s/^ //' <<< " connections { remote-access { version = 2 send_certreq = no pools = home-office ppk_required = yes proposals = camellia256ctr-sha512-mlkem768 local { auth = eap-tls id = bob.com certs = bob-cert.pem } remote { auth = eap-tls } children { office { local_ts = 10.0.0.0/22, 5.6.7.8/32 esp_proposals = chacha20poly1305-mlkem768 updown = _updown iptables } } } } pools { home-office { addrs = 10.0.2.0/23 } } secrets { ppk-alice { id = alice@bob.com secret = 0xba8bd35f2ae876d60a781ff19b46580ac0b31c77ae27487324a131dc690a836e } }" > /etc/swanctl/swanctl.conf
proposals и esp_proposals позволяют захардкодить набор алгоритмов шифрования, аутентификации, проверки целостности и обмена ключами. Хардкод — палка о двух концах: обе опции можно опустить, но возникает риск того, что сервер согласится на слабый алгоритм клиента. С другой стороны, если набор слишком строгий и короткий, клиенту, который его не поддерживает, вернётся ошибка аутентификации. На деле чем разнообразнее клиенты, тем разнообразнее должен быть набор — но я не оставляю пирам выбора: обменяться ключом им придётся по mlkem768.
Очередь /etc/strongswan.conf:
bob ~ sudo mv /etc/strongswan.conf{,.bak} 2> /dev/null bob ~ sudo sed '1d;s/^ //' <<< " charon { load_modular = yes install_routes = no plugins { socket-default { use_ipv6 = no } include strongswan.d/charon/*.conf } } libtls { version_max = 1.3 } include strongswan.d/*.conf" > /etc/strongswan.conf
Запускаю службу и добавляю её в автозагрузку. strongswan-starter — артефакт метапакета strongswan на Debian, который задействует легаси-интерфейс приложения.
bob ~ sudo systemctl disable --now strongswan-starter 2> /dev/null; \ sudo systemctl enable --now strongswan
Клиент
Одноразовой ссылкой передаю сотруднице корневой сертификат, сертификат и ключ клиента, клиентские swanctl.conf и strongswan.conf:
alice ~ cat /etc/swanctl/swanctl.conf connections { remote-access { remote_addrs = 1.2.3.4 vips = 10.0.2.0/23 send_certreq = no ppk_required = yes ppk_id = alice@bob.com proposals = camellia256ctr-sha512-mlkem768 local { auth = eap-tls certs = alice-cert.pem } remote { auth = eap-tls id = bob.com } children { office { remote_ts = 10.0.0.0/22, 5.6.7.8/32 start_action = start esp_proposals = chacha20poly1305-mlkem768 } } } } secrets { ppk-alice { id = alice@bob.com secret = 0xba8bd35f2ae876d60a781ff19b46580ac0b31c77ae27487324a131dc690a836e } } alice ~ cat /etc/strongswan.conf charon { load_modular = yes half_open_timeout = 30 plugins { include strongswan.d/charon/*.conf } } libtls { version_max = 1.3 } include strongswan.d/*.conf
В strongswan.conf клиента отсутствует install_routes = no, поэтому демон выделит свои маршруты в локальную таблицу 220. На сервере я отказался от неё ради производительности.
Сертификаты и ключ на клиенте:
alice ~ ( cd /etc/swanctl && ls private x509 x509ca ) private/: alice-key.pem x509/: alice-cert.pem x509ca/: ca-cert.pem
Сотрудница подтверждает, что на домашнем роутере разрешён IPsec, и запускает strongswan.service — на Arch служба одна. В логе сервера:
bob ~ journalctl -fu strongswan | grep -E '(select|establish)' Feb 26 08:16:38 bob.com charon-systemd[693470]: selected proposal: IKE:CAMELLIA_CTR_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ML_KEM_768 Feb 26 08:16:38 bob.com charon-systemd[693470]: selected peer config 'remote-access' Feb 26 08:16:38 bob.com charon-systemd[693470]: IKE_SA remote-access[1] established between 1.2.3.4[С=RU, O=Bob LLC, CN=bob.com]...9.10.11.12[С=RU, O=Bob LLC, CN=alice@bob.com] Feb 26 08:16:38 bob.com charon-systemd[693470]: selected proposal: ESP:CHACHA20_POLY1305/NO_EXT_SEQ Feb 26 08:16:38 bob.com charon-systemd[693470]: CHILD_SA remote-access{1} established with SPIs c04da127_i cad1369a_o and TS 10.0.0.0/22, 5.6.7.8/32 === 10.0.2.1/32
Статус физического интерфейса и таблицы 220 на клиенте:
alice ~ ip -4 -br a s dev wlo0 && ip r l t 220 wlo0 UP 192.168.0.2/24 metric 20 10.0.2.1/32 10.0.0.0/22 via 192.168.0.1 dev wlo0 proto static src 10.0.2.1 5.6.7.8 via 192.168.0.1 dev wlo0 proto static src 10.0.2.1 throw 192.168.0.0/24 proto static throw 192.168.0.1 proto static
Готово. Осталось автообновление сертификатов — но об этом пусть заботится будущий, постквантовый я.
