From 633e1f2fbd0a6f7deaad60c131d16d5cc451dffe Mon Sep 17 00:00:00 2001 From: miika Date: Thu, 25 Apr 2024 21:34:13 +0300 Subject: [PATCH 1/7] Add ability to disable algorithms --- src/SSHLibrary/abstractclient.py | 13 +++++++------ src/SSHLibrary/library.py | 9 +++++---- src/SSHLibrary/pythonclient.py | 8 ++++++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/SSHLibrary/abstractclient.py b/src/SSHLibrary/abstractclient.py index 9fa28362f..4ee021135 100644 --- a/src/SSHLibrary/abstractclient.py +++ b/src/SSHLibrary/abstractclient.py @@ -162,7 +162,7 @@ def close(self): pass def login(self, username=None, password=None, allow_agent=False, look_for_keys=False, delay=None, proxy_cmd=None, - read_config=False, jumphost_connection=None, keep_alive_interval='0 seconds'): + read_config=False, jumphost_connection=None, keep_alive_interval='0 seconds', disabled_algorithms=None): """Logs into the remote host using password authentication. This method reads the output from the remote host after logging in, @@ -208,7 +208,7 @@ def login(self, username=None, password=None, allow_agent=False, look_for_keys=F password = self._encode(password) try: self._login(username, password, allow_agent, look_for_keys, proxy_cmd, read_config, - jumphost_connection, keep_alive_interval) + jumphost_connection, keep_alive_interval, disabled_algorithms) except SSHClientException: self.client.close() raise SSHClientException("Authentication failed for user '%s'." @@ -226,7 +226,7 @@ def _decode(self, bytes): return bytes.decode(self.config.encoding, self.config.encoding_errors) def _login(self, username, password, allow_agent, look_for_keys, proxy_cmd, read_config, - jumphost_connection, keep_alive_interval): + jumphost_connection, keep_alive_interval, disabled_algorithms): raise NotImplementedError def _read_login_output(self, delay): @@ -238,7 +238,8 @@ def _read_login_output(self, delay): def login_with_public_key(self, username, keyfile, password, allow_agent=False, look_for_keys=False, delay=None, proxy_cmd=None, - jumphost_connection=None, read_config=False, keep_alive_interval='0 seconds'): + jumphost_connection=None, read_config=False, keep_alive_interval='0 seconds', + disabled_algorithms=None): """Logs into the remote host using the public key authentication. This method reads the output from the remote host after logging in, @@ -286,7 +287,7 @@ def login_with_public_key(self, username, keyfile, password, allow_agent=False, self._login_with_public_key(username, keyfile, password, allow_agent, look_for_keys, proxy_cmd, jumphost_connection, - read_config, keep_alive_interval) + read_config, keep_alive_interval, disabled_algorithms) except SSHClientException: self.client.close() raise SSHClientException("Login with public key failed for user " @@ -304,7 +305,7 @@ def _verify_key_file(self, keyfile): def _login_with_public_key(self, username, keyfile, password, allow_agent, look_for_keys, proxy_cmd, - jumphost_index_or_alias, read_config, keep_alive_interval): + jumphost_index_or_alias, read_config, keep_alive_interval, disabled_algorithms): raise NotImplementedError @staticmethod diff --git a/src/SSHLibrary/library.py b/src/SSHLibrary/library.py index 403807c66..331ec3e37 100644 --- a/src/SSHLibrary/library.py +++ b/src/SSHLibrary/library.py @@ -937,7 +937,8 @@ def get_connections(self): return configs def login(self, username=None, password=None, allow_agent=False, look_for_keys=False, delay='0.5 seconds', - proxy_cmd=None, read_config=False, jumphost_index_or_alias=None, keep_alive_interval='0 seconds'): + proxy_cmd=None, read_config=False, jumphost_index_or_alias=None, keep_alive_interval='0 seconds', + disabled_algorithms=None): """Logs into the SSH server with the given ``username`` and ``password``. Connection must be opened before using this keyword. @@ -1006,13 +1007,13 @@ def login(self, username=None, password=None, allow_agent=False, look_for_keys=F return self._login(self.current.login, username, password, is_truthy(allow_agent), is_truthy(look_for_keys), delay, proxy_cmd, is_truthy(read_config), - jumphost_connection, keep_alive_interval) + jumphost_connection, keep_alive_interval, disabled_algorithms) def login_with_public_key(self, username=None, keyfile=None, password='', allow_agent=False, look_for_keys=False, delay='0.5 seconds', proxy_cmd=None, jumphost_index_or_alias=None, - read_config=False, keep_alive_interval='0 seconds'): + read_config=False, keep_alive_interval='0 seconds', disabled_algorithms=None): """Logs into the SSH server using key-based authentication. Connection must be opened before using this keyword. @@ -1077,7 +1078,7 @@ def login_with_public_key(self, username=None, keyfile=None, password='', return self._login(self.current.login_with_public_key, username, keyfile, password, is_truthy(allow_agent), is_truthy(look_for_keys), delay, proxy_cmd, - jumphost_connection, is_truthy(read_config), keep_alive_interval) + jumphost_connection, is_truthy(read_config), keep_alive_interval, disabled_algorithms) def _login(self, login_method, username, *args): self._log("Logging into '%s:%s' as '%s'." diff --git a/src/SSHLibrary/pythonclient.py b/src/SSHLibrary/pythonclient.py index 19bffee4d..758bdecad 100644 --- a/src/SSHLibrary/pythonclient.py +++ b/src/SSHLibrary/pythonclient.py @@ -151,7 +151,7 @@ def _get_jumphost_tunnel(self, jumphost_connection): return jumphost_transport.open_channel("direct-tcpip", dest_addr, jump_addr) def _login(self, username, password, allow_agent=False, look_for_keys=False, proxy_cmd=None, - read_config=False, jumphost_connection=None, keep_alive_interval=None): + read_config=False, jumphost_connection=None, keep_alive_interval=None, disabled_algorithms=None): if read_config: hostname = self.config.host self.config.host, username, self.config.port, proxy_cmd = \ @@ -172,6 +172,7 @@ def _login(self, username, password, allow_agent=False, look_for_keys=False, pro self.client.connect(self.config.host, self.config.port, username, password, look_for_keys=look_for_keys, allow_agent=allow_agent, + disabled_algorithms=disabled_algorithms, timeout=float(self.config.timeout), sock=sock_tunnel) except paramiko.SSHException: pass @@ -183,6 +184,7 @@ def _login(self, username, password, allow_agent=False, look_for_keys=False, pro self.client.connect(self.config.host, self.config.port, username, password, look_for_keys=look_for_keys, allow_agent=allow_agent, + disabled_algorithms=disabled_algorithms, timeout=float(self.config.timeout), sock=sock_tunnel) transport = self.client.get_transport() transport.set_keepalive(keep_alive_interval) @@ -201,7 +203,8 @@ def _login(self, username, password, allow_agent=False, look_for_keys=False, pro raise SSHClientException def _login_with_public_key(self, username, key_file, password, allow_agent, look_for_keys, proxy_cmd=None, - jumphost_connection=None, read_config=False, keep_alive_interval=None): + jumphost_connection=None, read_config=False, keep_alive_interval=None, + disabled_algorithms=None): if read_config: hostname = self.config.host self.config.host, username, self.config.port, key_file, proxy_cmd = \ @@ -230,6 +233,7 @@ def _login_with_public_key(self, username, key_file, password, allow_agent, look password, key_filename=key_file, allow_agent=allow_agent, look_for_keys=look_for_keys, + disabled_algorithms=disabled_algorithms, timeout=float(self.config.timeout), sock=sock_tunnel) transport = self.client.get_transport() From 927bdd2889f2335f5b8b4002fb3dda6d85f60ad3 Mon Sep 17 00:00:00 2001 From: miika Date: Wed, 28 Aug 2024 18:01:56 +0300 Subject: [PATCH 2/7] Add disabled algorithms support for client --- src/SSHLibrary/client.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/SSHLibrary/client.py b/src/SSHLibrary/client.py index 74c97e864..0f074b8b9 100644 --- a/src/SSHLibrary/client.py +++ b/src/SSHLibrary/client.py @@ -182,7 +182,7 @@ def close(self): pass def login(self, username=None, password=None, allow_agent=False, look_for_keys=False, delay=None, proxy_cmd=None, - read_config=False, jumphost_connection=None, keep_alive_interval='0 seconds'): + read_config=False, jumphost_connection=None, keep_alive_interval='0 seconds', disabled_algorithms=None): """Logs into the remote host using password authentication. This method reads the output from the remote host after logging in, @@ -224,7 +224,7 @@ def login(self, username=None, password=None, allow_agent=False, look_for_keys=F password = self._encode(password) try: self._login(username, password, allow_agent, look_for_keys, proxy_cmd, read_config, - jumphost_connection, keep_alive_interval) + jumphost_connection, keep_alive_interval, disabled_algorithms) except SSHClientException: self.client.close() raise SSHClientException(f"Authentication failed for user '{self._decode(username)}'.") @@ -249,7 +249,8 @@ def _read_login_output(self, delay): def login_with_public_key(self, username, keyfile, password, allow_agent=False, look_for_keys=False, delay=None, proxy_cmd=None, - jumphost_connection=None, read_config=False, keep_alive_interval='0 seconds'): + jumphost_connection=None, read_config=False, keep_alive_interval='0 seconds', + disabled_algorithms=None): """Logs into the remote host using the public key authentication. This method reads the output from the remote host after logging in, @@ -295,7 +296,8 @@ def login_with_public_key(self, username, keyfile, password, allow_agent=False, self._login_with_public_key(username, keyfile, password, allow_agent, look_for_keys, proxy_cmd, jumphost_connection, - read_config, keep_alive_interval) + read_config, keep_alive_interval, + disabled_algorithms) except SSHClientException: self.client.close() raise SSHClientException(f"Login with public key failed for user '{self._decode(username)}'.") @@ -836,7 +838,7 @@ def _get_jumphost_tunnel(self, jumphost_connection): return jumphost_transport.open_channel("direct-tcpip", dest_addr, jump_addr) def _login(self, username, password, allow_agent=False, look_for_keys=False, proxy_cmd=None, - read_config=False, jumphost_connection=None, keep_alive_interval=None): + read_config=False, jumphost_connection=None, keep_alive_interval=None, disabled_algorithms=None): if read_config: hostname = self.config.host self.config.host, username, self.config.port, proxy_cmd = \ @@ -857,7 +859,8 @@ def _login(self, username, password, allow_agent=False, look_for_keys=False, pro self.client.connect(self.config.host, self.config.port, username, password, look_for_keys=look_for_keys, allow_agent=allow_agent, - timeout=float(self.config.timeout), sock=sock_tunnel) + timeout=float(self.config.timeout), sock=sock_tunnel, + disabled_algorithms=disabled_algorithms) except paramiko.SSHException: pass transport = self.client.get_transport() @@ -868,7 +871,8 @@ def _login(self, username, password, allow_agent=False, look_for_keys=False, pro self.client.connect(self.config.host, self.config.port, username, password, look_for_keys=look_for_keys, allow_agent=allow_agent, - timeout=float(self.config.timeout), sock=sock_tunnel) + timeout=float(self.config.timeout), sock=sock_tunnel, + disabled_algorithms=disabled_algorithms) transport = self.client.get_transport() transport.set_keepalive(keep_alive_interval) except paramiko.AuthenticationException: @@ -886,7 +890,7 @@ def _login(self, username, password, allow_agent=False, look_for_keys=False, pro raise SSHClientException def _login_with_public_key(self, username, key_file, password, allow_agent, look_for_keys, proxy_cmd=None, - jumphost_connection=None, read_config=False, keep_alive_interval=None): + jumphost_connection=None, read_config=False, keep_alive_interval=None, disabled_algorithms=None): if read_config: hostname = self.config.host self.config.host, username, self.config.port, key_file, proxy_cmd = \ @@ -915,7 +919,8 @@ def _login_with_public_key(self, username, key_file, password, allow_agent, look allow_agent=allow_agent, look_for_keys=look_for_keys, timeout=float(self.config.timeout), - sock=sock_tunnel) + sock=sock_tunnel, + disabled_algorithms=disabled_algorithms) transport = self.client.get_transport() transport.set_keepalive(keep_alive_interval) except paramiko.AuthenticationException: From ad1e5353147a64a9212933ced41a9bc8136aa556 Mon Sep 17 00:00:00 2001 From: miika Date: Fri, 30 Aug 2024 22:42:05 +0300 Subject: [PATCH 3/7] Add test cases using disabled algorithms --- atest/login.robot | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/atest/login.robot b/atest/login.robot index 45cbe9572..1b536b4d7 100644 --- a/atest/login.robot +++ b/atest/login.robot @@ -85,3 +85,15 @@ Login Using Config File Proxy Command ${output}= Login password=test read_config=True Should Contain ${output} test@ +Login With Disabled Algorithms + [Setup] Open Connection ${HOST} prompt=${PROMPT} + VAR @{pubkeys} rsa-sha2-512 rsa-sha2-256 + VAR &{disabled_algorithms} pubkeys=${pubkeys} + Login ${USERNAME} ${PASSWORD} disabled_algorithms=${disabled_algorithms} + +Login With Disabled Algorithms And Public Key + [Setup] Open Connection ${HOST} prompt=${PROMPT} + VAR @{pubkeys} rsa-sha2-512 rsa-sha2-256 + VAR &{disabled_algorithms} pubkeys=${pubkeys} + Login With Public Key ${KEY USERNAME} ${KEY} disabled_algorithms=${disabled_algorithms} + From cae605d33f28484d69ffec1197384660c1ef5bda Mon Sep 17 00:00:00 2001 From: miika Date: Fri, 30 Aug 2024 23:02:35 +0300 Subject: [PATCH 4/7] Add documentation for disabled_algorithms in login and login with public key keywords --- src/SSHLibrary/library.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/SSHLibrary/library.py b/src/SSHLibrary/library.py index d02b592f1..673bc8e53 100644 --- a/src/SSHLibrary/library.py +++ b/src/SSHLibrary/library.py @@ -958,6 +958,11 @@ def login(self, username=None, password=None, allow_agent=False, look_for_keys=F ``keep_alive_interval`` is new in SSHLibrary 3.7.0. + ``disabled_algorithms`` is a list of algorithms that should be disabled. + For example, if you need to disable diffie-hellman-group16-sha512 key exchange + (perhaps because your code talks to a server which implements it differently from Paramiko), + specify disabled_algorithms={"kex": ["diffie-hellman-group16-sha512"]} + Example that logs in and returns the output: | `Open Connection` | linux.server.com | @@ -980,6 +985,12 @@ def login(self, username=None, password=None, allow_agent=False, look_for_keys=F First, add the key to the authentication agent with: ``ssh-add /path/to/keyfile``. | `Open Connection` | linux.server.com | | `Login` | johndoe | allow_agent=True | + + Example login with disabled algorithms: + | `Open Connection` | linux.server.com | + | `VAR` | @{pubkeys} | rsa-sha2-512 rsa-sha2-256 | + | `VAR` | &{disabled_algorithms} | pubkeys=${pubkeys} | + | `Login` | username=johndoe | disabled_algorithms=${disabled_algorithms} | """ jumphost_connection_conf = self.get_connection(index_or_alias=jumphost_index_or_alias) \ if jumphost_index_or_alias else None @@ -1048,6 +1059,17 @@ def login_with_public_key(self, username=None, keyfile=None, password='', set to ``0``, which means sending the ``keepalive`` packet is disabled. ``keep_alive_interval`` is new in SSHLibrary 3.7.0. + + ``disabled_algorithms`` is a list of algorithms that should be disabled. + For example, if you need to disable diffie-hellman-group16-sha512 key exchange + (perhaps because your code talks to a server which implements it differently from Paramiko), + specify disabled_algorithms={"kex": ["diffie-hellman-group16-sha512"]} + + Example login with disabled algorithms: + | `Open Connection` | linux.server.com | + | `VAR` | @{pubkeys} | rsa-sha2-512 rsa-sha2-256 | + | `VAR` | &{disabled_algorithms} | pubkeys=${pubkeys} | + | `Login With Public Key` | username=johndoe | keyfile=key | disabled_algorithms=${disabled_algorithms} | """ if proxy_cmd and jumphost_index_or_alias: raise ValueError("`proxy_cmd` and `jumphost_connection` are mutually exclusive SSH features.") From 5422cefa5920ee258391c5afadbd7f25f0506258 Mon Sep 17 00:00:00 2001 From: miikaoskari Date: Sat, 31 Aug 2024 17:35:41 +0300 Subject: [PATCH 5/7] Change open connection hostname to testkey_hostname --- atest/login.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/login.robot b/atest/login.robot index 7b6909280..46888b6ad 100644 --- a/atest/login.robot +++ b/atest/login.robot @@ -95,7 +95,7 @@ Login With Disabled Algorithms Login ${USERNAME} ${PASSWORD} disabled_algorithms=${disabled_algorithms} Login With Disabled Algorithms And Public Key - [Setup] Open Connection ${HOST} prompt=${PROMPT} + [Setup] Open Connection ${TESTKEY_HOSTNAME} prompt=${PROMPT} VAR @{pubkeys} rsa-sha2-512 rsa-sha2-256 VAR &{disabled_algorithms} pubkeys=${pubkeys} Login With Public Key ${KEY USERNAME} ${KEY} disabled_algorithms=${disabled_algorithms} From 7714ea3805962e5f3c85a5270094a9f5ce0d7922 Mon Sep 17 00:00:00 2001 From: miikaoskari Date: Sat, 31 Aug 2024 17:47:26 +0300 Subject: [PATCH 6/7] Move public key login test under other key logins --- atest/login.robot | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/atest/login.robot b/atest/login.robot index 46888b6ad..0e7126885 100644 --- a/atest/login.robot +++ b/atest/login.robot @@ -43,6 +43,11 @@ Login With Public Key When Non-Existing Key Run Keyword And Expect Error Given key file 'not_existing_key' does not exist. ... Login With Public Key ${KEY USERNAME} not_existing_key +Login With Public Key And Disabled Algorithms + VAR @{pubkeys} rsa-sha2-512 rsa-sha2-256 + VAR &{disabled_algorithms} pubkeys=${pubkeys} + Login With Public Key ${KEY USERNAME} ${KEY} disabled_algorithms=${disabled_algorithms} + Logging In Returns Server Output [Setup] Open Connection ${HOST} ${output}= Login ${USERNAME} ${PASSWORD} @@ -94,8 +99,3 @@ Login With Disabled Algorithms VAR &{disabled_algorithms} pubkeys=${pubkeys} Login ${USERNAME} ${PASSWORD} disabled_algorithms=${disabled_algorithms} -Login With Disabled Algorithms And Public Key - [Setup] Open Connection ${TESTKEY_HOSTNAME} prompt=${PROMPT} - VAR @{pubkeys} rsa-sha2-512 rsa-sha2-256 - VAR &{disabled_algorithms} pubkeys=${pubkeys} - Login With Public Key ${KEY USERNAME} ${KEY} disabled_algorithms=${disabled_algorithms} From 19857a516e54fd06b7fe9ffb16fa9bfee2d88d7c Mon Sep 17 00:00:00 2001 From: miikaoskari Date: Sat, 31 Aug 2024 18:05:06 +0300 Subject: [PATCH 7/7] Test with different key algorithms --- atest/login.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/login.robot b/atest/login.robot index 0e7126885..cf8bf22ec 100644 --- a/atest/login.robot +++ b/atest/login.robot @@ -44,7 +44,7 @@ Login With Public Key When Non-Existing Key ... Login With Public Key ${KEY USERNAME} not_existing_key Login With Public Key And Disabled Algorithms - VAR @{pubkeys} rsa-sha2-512 rsa-sha2-256 + VAR @{pubkeys} diffie-hellman-group16-sha512 VAR &{disabled_algorithms} pubkeys=${pubkeys} Login With Public Key ${KEY USERNAME} ${KEY} disabled_algorithms=${disabled_algorithms}