diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-10-07 20:04:21 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-10-07 20:04:21 +0200 |
| commit | 58ccdee2d255f4e952322467902f7d69b4f828fa (patch) | |
| tree | 3b0a15a5241c413bad09e702ddc705bc9bf008f9 | |
| parent | fff4e828d2a7ec1549ca20db30906ff47396e374 (diff) | |
| parent | 8d99a2f6f81b225014d246dcf6c7bb03c641d4da (diff) | |
| download | vyos-1x-58ccdee2d255f4e952322467902f7d69b4f828fa.tar.gz vyos-1x-58ccdee2d255f4e952322467902f7d69b4f828fa.zip | |
Merge pull request #5523 from mandelbitdev/T9370-openvpn-chacha20poly1305
openvpn: T9370: add chacha20poly1305 data cipher
| -rw-r--r-- | interface-definitions/interfaces_openvpn.xml.in | 16 | ||||
| -rwxr-xr-x | python/vyos/template.py | 3 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_interfaces_openvpn.py | 70 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces_openvpn.py | 5 | ||||
| -rwxr-xr-x | src/op_mode/generate_ovpn_client_file.py | 3 | ||||
| -rw-r--r-- | src/tests/test_template.py | 12 |
6 files changed, 100 insertions, 9 deletions
diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in index 26b004520..ff16a5610 100644 --- a/interface-definitions/interfaces_openvpn.xml.in +++ b/interface-definitions/interfaces_openvpn.xml.in @@ -91,7 +91,7 @@ <properties> <help>Cipher negotiation list for use in server or client mode</help> <completionHelp> - <list>none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list> + <list>none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm chacha20poly1305</list> </completionHelp> <valueHelp> <format>none</format> @@ -125,8 +125,12 @@ <format>aes256gcm</format> <description>AES algorithm with 256-bit key GCM</description> </valueHelp> + <valueHelp> + <format>chacha20poly1305</format> + <description>ChaCha20 stream cipher with Poly1305 authenticator</description> + </valueHelp> <constraint> - <regex>(none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex> + <regex>(none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm|chacha20poly1305)</regex> </constraint> <multi/> </properties> @@ -135,7 +139,7 @@ <properties> <help>Fallback cipher to use for site-to-site tunnels</help> <completionHelp> - <list>none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list> + <list>none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm chacha20poly1305</list> </completionHelp> <valueHelp> <format>none</format> @@ -169,8 +173,12 @@ <format>aes256gcm</format> <description>AES algorithm with 256-bit key GCM</description> </valueHelp> + <valueHelp> + <format>chacha20poly1305</format> + <description>ChaCha20 stream cipher with Poly1305 authenticator</description> + </valueHelp> <constraint> - <regex>(none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex> + <regex>(none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm|chacha20poly1305)</regex> </constraint> </properties> </leafNode> diff --git a/python/vyos/template.py b/python/vyos/template.py index c35a25d21..2c07d79da 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -610,7 +610,8 @@ openvpn_translate = { 'aes192gcm': 'aes-192-gcm', 'aes192': 'aes-192-cbc', 'aes256gcm': 'aes-256-gcm', - 'aes256': 'aes-256-cbc' + 'aes256': 'aes-256-cbc', + 'chacha20poly1305': 'chacha20-poly1305', } @register_filter('openvpn_cipher') diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index 93c79e646..476fcbdf4 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -329,6 +329,76 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): f'{self._openvpn_log(client)}', ) + def test_openvpn_dco_chacha20poly1305(self): + # ChaCha20-Poly1305 is offloaded just like AES-GCM. A peer only shows + # up in the Kernel once its key got installed, so a client and a server + # offering nothing else prove the module took that very cipher. + server = 'vtun5110' + client = 'vtun5111' + port = '1196' + + path = base_path + [server] + self.cli_set(path + ['mode', 'server']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['server', 'subnet', '10.98.0.0/24']) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) + self.cli_set(path + ['encryption', 'data-ciphers', 'chacha20poly1305']) + self.cli_set(path + ['offload', 'dco']) + self.cli_commit() + + config = read_file(f'/run/openvpn/{server}.conf') + self.assertIn('data-ciphers CHACHA20-POLY1305', config) + self.assertDcoDataPath(server) + + path = base_path + [client] + self.cli_set(path + ['mode', 'client']) + self.cli_set(path + ['remote-host', '127.0.0.1']) + self.cli_set(path + ['remote-port', port]) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['encryption', 'data-ciphers', 'chacha20poly1305']) + self.cli_set(path + ['offload', 'dco']) + self.cli_commit() + + self.assertDcoDataPath(client, multipoint=False) + self.assertGreater( + self._peers(server), + 0, + f'the Kernel holds no peer for {server}\n{self._openvpn_log(server)}', + ) + self.assertGreater( + self._peers(client), + 0, + f'the Kernel holds no peer for {client}\n{self._openvpn_log(client)}', + ) + + # and it is the cipher the two of them settled on + log = cmdl( + ['sudo', 'journalctl', '-u', f'openvpn@{client}.service', '-o', 'cat'] + ) + self.assertIn("Data Channel: cipher 'CHACHA20-POLY1305'", log) + + def test_openvpn_site2site_dco_chacha20poly1305(self): + # the fallback cipher decides the offload in site-to-site mode + interface = 'vtun5000' + path = base_path + [interface] + + self.cli_set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['local-address', '10.0.0.1']) + self.cli_set(path + ['remote-address', '192.168.0.1']) + self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'certificate', 'ovpn_test']) + self.cli_set(path + ['tls', 'role', 'active']) + self.cli_set(path + ['encryption', 'data-ciphers-fallback', 'chacha20poly1305']) + self.cli_set(path + ['offload', 'dco']) + self.cli_commit() + + config = read_file(f'/run/openvpn/{interface}.conf') + self.assertIn('data-ciphers-fallback CHACHA20-POLY1305', config) + self.assertDcoDataPath(interface, multipoint=False) + # OVPN_CMD_PEER_GET carries GENL_ADMIN_PERM, hence the detour through # sudo. The probe swallows its own errors so a missing interface or a # refused dump reads as "no peer" with the reason attached, instead of diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py index 109e0f401..911b0f467 100755 --- a/src/conf_mode/interfaces_openvpn.py +++ b/src/conf_mode/interfaces_openvpn.py @@ -80,9 +80,8 @@ group = 'openvpn' cfg_dir = '/run/openvpn' cfg_file = '/run/openvpn/{ifname}.conf' # Ciphers implemented by the in-tree "ovpn" Kernel module. Any other cipher -# must be handled in userspace and thus rules out DCO. The module also does -# ChaCha20-Poly1305, which the CLI does not offer. -dco_ciphers = ['aes128gcm', 'aes192gcm', 'aes256gcm'] +# must be handled in userspace and thus rules out DCO. +dco_ciphers = ['aes128gcm', 'aes192gcm', 'aes256gcm', 'chacha20poly1305'] # Raw options that make OpenVPN fall back to the userspace data path, taken # from dco_check_option() and dco_check_option_ce() dco_incompatible_options = [ diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py index a55f2e3c5..21c933932 100755 --- a/src/op_mode/generate_ovpn_client_file.py +++ b/src/op_mode/generate_ovpn_client_file.py @@ -47,7 +47,8 @@ verb 3 'aes192gcm': 'AES-192-GCM', 'aes192': 'AES-192-CBC', 'aes256gcm': 'AES-256-GCM', - 'aes256': 'AES-256-CBC' + 'aes256': 'AES-256-CBC', + 'chacha20poly1305': 'CHACHA20-POLY1305' } %} {% if encryption is defined and encryption is not none %} diff --git a/src/tests/test_template.py b/src/tests/test_template.py index 7ac396b68..65266584e 100644 --- a/src/tests/test_template.py +++ b/src/tests/test_template.py @@ -208,3 +208,15 @@ class TestVyOSTemplate(TestCase): internal_ports['certbot_haproxy']) self.assertEqual(vyos.template.nft_accept_invalid('arp'), 'ct state invalid ether type arp counter accept') + + def test_openvpn_ciphers(self): + # CLI values map onto the names OpenVPN expects + self.assertEqual( + vyos.template.get_openvpn_data_ciphers(['chacha20poly1305', 'aes256gcm']), + 'CHACHA20-POLY1305:AES-256-GCM', + ) + self.assertEqual( + vyos.template.get_openvpn_data_ciphers_fallback('chacha20poly1305'), + 'CHACHA20-POLY1305', + ) + self.assertEqual(vyos.template.get_openvpn_cipher('aes128'), 'AES-128-CBC') |
