Skip to content

[BUG] - paramiko ignores SecurityOptions.key_types when hostkey option is set (and accepts signature algorithms not listed) #2280

@micromaomao

Description

@micromaomao

Are you using paramiko as a client or server?

Client

What feature(s) aren't working right?

Keys/auth

What version(s) of paramiko are you using?

3.3.1

What version(s) of Python are you using?

3.8.10

What operating system and version are you using?

WSL on Windows 11 and Linux

If you're connecting as a client, which SSH server are you connecting to?

Linux VM

If you're using paramiko as part of another tool, which tool/version?

No response

Expected/desired behavior

Hi - the background for this issue is that in our use case we needed to explicitly limit the set of allowed algorithms to a subset of the default, for example not allowing ssh-rsa because it uses sha1 which is insecure (rsa-sha2-256 is fine). For testing we've set up a test VM with ssh options set so that the only signature algorithm it will offer is sha1.

The following should fail, whether we include the hostkey argument in t.connect or not:

import paramiko
k = paramiko.RSAKey.from_private_key_file("tw4-peri-azure-store-ssh-id.key")
t = paramiko.Transport("10.216.0.10")
secopts = t.get_security_options()
secopts.key_types = ['rsa-sha2-256', 'ecdsa-sha2-nistp256']
t.connect(username="azureuser", pkey=k)

Exception (client): Incompatible ssh peer (no acceptable host key)
Traceback (most recent call last):
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 2159, in run
    self._handler_table[ptype](m)
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 2279, in _negotiate_keys
    self._parse_kex_init(m)
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 2495, in _parse_kex_init
    raise IncompatiblePeer(
paramiko.ssh_exception.IncompatiblePeer: Incompatible ssh peer (no acceptable host key)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 1369, in connect
    self.start_client()
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 722, in start_client
    raise e
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 2159, in run
    self._handler_table[ptype](m)
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 2279, in _negotiate_keys
    self._parse_kex_init(m)
  File "/home/developer/.local/lib/python3.8/site-packages/paramiko/transport.py", line 2495, in _parse_kex_init
    raise IncompatiblePeer(
paramiko.ssh_exception.IncompatiblePeer: Incompatible ssh peer (no acceptable host key)

However, if we specify a host key to verify against, it connects successfully:

import base64
hk = paramiko.PKey.from_type_string("ssh-rsa", base64.b64decode("..."))
  # The `ssh-rsa` here is just the key type - this key will still work with rsa-sha2-* (if the server accepts it)
>>> t = paramiko.Transport("10.216.0.10")
secopts = t.get_security_options()
secopts.key_types = ['rsa-sha2-256', 'ecdsa-sha2-nistp256']
t.connect(username="azureuser", pkey=k, hostkey=hk)
>>> t
<paramiko.Transport at 0xdc47f490 (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>
>>> cl = paramiko.SFTPClient.from_transport(t)
>>> cl.listdir("/")
['opt', 'var', 'tmp', 'mnt', 'etc', 'sbin', 'sys', 'boot', 'srv', 'run', 'media', 'lib64', 'dev', 'lib32', 'proc', 'home', 'lib', 'root', 'libx32', 'lost+found', 'usr', 'bin']

Actual behavior

Connection should fail even when a host key is specified, as we did not include ssh-rsa (i.e. the sha1 version of rsa-sha2-*) in the security options.

How to reproduce

See above log. The server setup is basically adding the following line to sshd_config:

HostKeyAlgorithms ssh-rsa

Anything else?

I think the likely reason for this issue is in this bit of code:

paramiko/paramiko/transport.py

Lines 1354 to 1361 in d5117fc

if isinstance(hostkey, RSAKey):
self._preferred_keys = [
"rsa-sha2-512",
"rsa-sha2-256",
"ssh-rsa",
]
else:
self._preferred_keys = [hostkey.get_name()]

It should not override _preferred_keys completely, but instead choose a subset of the existing _preferred_keys previously set by SecurityOptions

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions