Харденинг strongSwan на всякий постквантовый

в 12:15, , рубрики: mlkem, ppk, strongswan, криптографические алгоритмы
Харденинг strongSwan на всякий постквантовый - 1

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. Но мне это не подходит: сейчас ML-KEM в TLS выступает довеском к эллиптическим кривым, как элемент одной из гибридных схем, которых в strongSwan нет: его ML-KEM совместим только с протоколом обмена ключами IKEv2, штатным для IPsec.

В репозиториях большинства дистрибутивов, когда я приступал к задаче, нужная сборка 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/:
alice-cert.pem  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.

Отсутствие опции auth в директиве remote последнего означает, что пиры аутентифицируют друг друга дефолтным методом pubkey. Указывать префикс 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 {
        	id = bob.com
        	certs = bob-cert.pem
    	}
    	remote {}
    	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
	}
}

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 {
        	certs = alice-cert.pem
    	}
    	remote {
        	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
	}
}

include strongswan.d/*.conf

В strongswan.conf клиента отсутствует install_routes = no, поэтому демон выделит свои маршруты в локальную таблицу 220. На сервере я отказался от неё ради производительности.

Сертификаты и ключ на клиенте:

alice ~ ( cd /etc/swanctl && ls private x509 )
private/:
alice-key.pem

x509/:
alice-cert.pem  bob-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

Готово. Осталось автообновление сертификатов — но об этом пусть заботится будущий, постквантовый я.

Автор: jhoag

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js