VYPR
Low severity3.4NVD Advisory· Published May 6, 2026· Updated May 7, 2026

CVE-2026-44405

CVE-2026-44405

Description

In Paramiko through 4.0.0 before a448945, rsakey.py allows the SHA-1 algorithm.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
paramikoPyPI
<= 4.0.0

Affected products

1

Patches

1
a4489456b6f6

Remove SHA1 support from RSA key handling

https://github.com/paramiko/paramikoJeff ForcierFeb 15, 2026via ghsa
12 files changed · +143 136
  • paramiko/auth_handler.py+6 0 modified
    @@ -614,7 +614,13 @@ def _parse_userauth_request(self, m):
                 # NOTE: server never wants to guess a client's algo, they're
                 # telling us directly. No need for _finalize_pubkey_algorithm
                 # anywhere in this flow.
    +            # TODO: ok is this a spot where it can say a SHA2 dealie in some
    +            # fields but still ssh-rsa within the pubkey blob part?
    +            # TODO: ok so this would be rsa-sha2-256 or w/e, if this field says
    +            # ssh-rsa the request can get stuffed.
                 algorithm = m.get_text()
    +            # TODO: This part would, if deconstructed, still be allowed to have
    +            # "ssh-rsa" in its first field.
                 keyblob = m.get_binary()
                 try:
                     key = self._generate_key_from_request(algorithm, keyblob)
    
  • paramiko/client.py+26 0 modified
    @@ -439,10 +439,32 @@ def connect(
     
             our_server_keys = self._system_host_keys.get(server_hostkey_name)
             if our_server_keys is None:
    +            # TODO: this is getting us the test suite _test_connection host key
    +            # setup by virtue of that code doing tc.get_host_keys().add() (that
    +            # method wraps self._host_keys)
    +            # TODO: it should be analogous to running paramiko w/ a
    +            # ~/.ssh/known_hosts file
                 our_server_keys = self._host_keys.get(server_hostkey_name)
             if our_server_keys is not None:
    +            # TODO: keytype needs to turn into one of the rsa-sha2 keys if it's
    +            # an RSAKey.
    +            # TODO: where is the equivalent on our server-side? hopefully the
    +            # tests exercise that lol
    +            # TODO: also, is it just me or does this [0] mean we literally do
    +            # not actually implement real HostKeyAlgorithms agreement like
    +            # ssh.c does?! eesh
                 keytype = our_server_keys.keys()[0]
                 sec_opts = t.get_security_options()
    +            # TODO: clean this up a bit, but it does help some tests pass!
    +            if keytype == "ssh-rsa":
    +                if "rsa-sha2-512" in sec_opts.key_types:
    +                    keytype = "rsa-sha2-512"
    +                elif "rsa-sha2-256" in sec_opts.key_types:
    +                    keytype = "rsa-sha2-256"
    +                else:
    +                    raise Exception(
    +                        "TODO: REPLACEME with appropriate exception for 'what even is this key type in your known_hosts files?'"
    +                    )
                 other_types = [x for x in sec_opts.key_types if x != keytype]
                 sec_opts.key_types = [keytype] + other_types
     
    @@ -459,6 +481,10 @@ def connect(
                         self, server_hostkey_name, server_key
                     )
                 else:
    +                # TODO: this should 'just work' but dblcheck (HostKeys will be
    +                # offering an ssh-rsa-via-sha2 key as "ssh-rsa" still, and the
    +                # key would be RSAKey whose .get_name would still say
    +                # "ssh-rsa")
                     our_key = our_server_keys.get(server_key.get_name())
                     if our_key != server_key:
                         if our_key is None:
    
  • paramiko/config.py+1 0 modified
    @@ -445,6 +445,7 @@ def _tokenize(self, config, target_hostname, key, value):
             # The actual tokens!
             replacements = {
                 # TODO: %%???
    +            # TODO: sha1 bad / this is offspec from rfc/openssh
                 "%C": sha1(tohash.encode()).hexdigest(),
                 "%d": homedir,
                 "%h": configured_hostname,
    
  • paramiko/rsakey.py+10 5 modified
    @@ -38,8 +38,6 @@ class RSAKey(PKey):
     
         name = "ssh-rsa"
         HASHES = {
    -        "ssh-rsa": hashes.SHA1,
    -        "ssh-rsa-cert-v01@openssh.com": hashes.SHA1,
             "rsa-sha2-256": hashes.SHA256,
             "rsa-sha2-256-cert-v01@openssh.com": hashes.SHA256,
             "rsa-sha2-512": hashes.SHA512,
    @@ -81,7 +79,14 @@ def __init__(
     
         @classmethod
         def identifiers(cls):
    -        return list(cls.HASHES.keys())
    +        # NOTE: we no longer want to have ssh-rsa+SHA1 in HASHES but we still
    +        # need to advertise we can be used to read ssh-rsa keys (w/ assumption
    +        # other parts of system will enforce the use of SHA2 signing algos).
    +        # Thus, just say so here.
    +        return list(cls.HASHES.keys()) + [
    +            "ssh-rsa",
    +            "ssh-rsa-cert-v01@openssh.com",
    +        ]
     
         @property
         def size(self):
    @@ -126,8 +131,8 @@ def sign_ssh_data(self, data, algorithm=None):
             sig = self.key.sign(
                 data,
                 padding=padding.PKCS1v15(),
    -            # HASHES being just a map from long identifier to either SHA1 or
    -            # SHA256 - cert'ness is not truly relevant.
    +            # HASHES being just a map from long identifier to algo; cert'ness
    +            # is not truly relevant.
                 algorithm=self.HASHES[algorithm](),
             )
             m = Message()
    
  • paramiko/sftp_server.py+4 0 modified
    @@ -79,6 +79,7 @@
     from paramiko.sftp_si import SFTPServerInterface
     from paramiko.util import b
     
    +# TODO: nuke or update w/ newer algs, see below
     _hash_class = {"sha1": sha1, "md5": md5}
     
     
    @@ -302,6 +303,9 @@ def _check_file(self, request_number, msg):
                 return
             f = self.file_table[handle]
             for x in alg_list:
    +            # TODO: this only contains sha1 and md5 so uh, is this extension
    +            # actually supported anymore? do we need to update the map for
    +            # newer algos instead? other?
                 if x in _hash_class:
                     algname = x
                     alg = _hash_class[x]
    
  • paramiko/transport.py+25 9 modified
    @@ -204,7 +204,6 @@ class Transport(threading.Thread, ClosingContextManager):
             "ecdsa-sha2-nistp521",
             "rsa-sha2-512",
             "rsa-sha2-256",
    -        "ssh-rsa",
         )
         # ~= PubkeyAcceptedAlgorithms
         _preferred_pubkeys = (
    @@ -214,7 +213,6 @@ class Transport(threading.Thread, ClosingContextManager):
             "ecdsa-sha2-nistp521",
             "rsa-sha2-512",
             "rsa-sha2-256",
    -        "ssh-rsa",
         )
         _preferred_kex = (
             "ecdh-sha2-nistp256",
    @@ -307,12 +305,18 @@ class Transport(threading.Thread, ClosingContextManager):
         }
     
         _key_info = {
    -        # TODO: at some point we will want to drop this as it's no longer
    -        # considered secure due to using SHA-1 for signatures. OpenSSH 8.8 no
    -        # longer supports it. Question becomes at what point do we want to
    -        # prevent users with older setups from using this?
    -        "ssh-rsa": RSAKey,
    -        "ssh-rsa-cert-v01@openssh.com": RSAKey,
    +        # TODO: do some downstream uses of this need to be able to 'see'
    +        # ssh-rsa in not-using-SHA1 contexts?
    +        # TODO: NO!!! good.
    +        # TODO: it's used in:
    +        # - Transport._verify_key - verification - do not want ssh-rsa
    +        # - SecurityOptions - only really uses this as a filter for what's
    +        # allowed to be overwritten into its .key_types (which ==
    +        # transport._preferred_keys), and since the latter doesn't want ssh-rsa
    +        # in it, this use case doesn't require that string in here either.
    +        # - AuthHandler._generate_key_from_request - server-side auth
    +        # support - is looking at the 'algorithm' field in the request when it
    +        # references this structure, so yup, do not want ssh-rsa
             "rsa-sha2-256": RSAKey,
             "rsa-sha2-256-cert-v01@openssh.com": RSAKey,
             "rsa-sha2-512": RSAKey,
    @@ -1396,11 +1400,13 @@ def connect(
                 # TODO: a more robust implementation would be to ask each key class
                 # for its nameS plural, and just use that.
                 # TODO: that could be used in a bunch of other spots too
    +            # TODO: don't we have that now, lol
    +            # TODO: either way this is ~= like using SecurityOptions.key_types
    +            # = xxx, but different, which sucks sigh
                 if isinstance(hostkey, RSAKey):
                     self._preferred_keys = [
                         "rsa-sha2-512",
                         "rsa-sha2-256",
    -                    "ssh-rsa",
                     ]
                 else:
                     self._preferred_keys = [hostkey.get_name()]
    @@ -2002,6 +2008,8 @@ def _verify_key(self, host_key, sig):
             key: PKey = self._key_info[self.host_key_type](Message(host_key))
             if key is None:
                 raise SSHException("Unknown host key type")
    +        # TODO: like, here, can a host offer "ssh-rsa" but request SHA2, or are
    +        # those baked in?
             if not key.verify_ssh_sig(self.H, Message(sig)):
                 raise SSHException(
                     "Signature verification ({}) failed.".format(
    @@ -3232,6 +3240,14 @@ def key_types(self):
     
         @key_types.setter
         def key_types(self, x):
    +        # TODO: so this reads Transport._key_info.keys(), yells if any values
    +        # in `x` /aren't/ in that list, then overwrites
    +        # Transport._preferred_keys with `x`...
    +        # TODO: so you can read this pretty simply as "replace
    +        # transport._preferred_keys with x".
    +        # TODO: which is...bad...in cases where SSHClient is trying to simply
    +        # load up known_hosts or system known hosts, and use those to determine
    +        # which hostkey /algorithms/ it is willing to accept
             self._set("_preferred_keys", "_key_info", x)
     
         @property
    
  • sites/www/changelog.rst+12 0 modified
    @@ -2,6 +2,18 @@
     Changelog
     =========
     
    +- :support:`-` Removed support for verifying/signing with RSA keys using SHA-1
    +  hashing. Generally, this means most cases where ``"ssh-rsa"`` was used as an
    +  algorithm identifier (as opposed to a key material identifier) will no longer
    +  accept that string as valid, and the relevant code that actually used eg
    +  `hashes.SHA1` no longer does.
    +
    +  .. warning::
    +    This change is backwards incompatible if you are stuck supporting legacy
    +    systems with Paramiko that are unable to use SHA2-based signatures with RSA
    +    keys (or other workarounds, such as switching from RSA keys to Ed25519
    +    ones).
    +
     - :bug:`- major` Added a ``password`` kwarg to `PKey.from_type_string
       <paramiko.pkey.PKey.from_type_string>` so it can handle encrypted keys like
       most other PKey constructors already could.
    
  • tests/auth.py+25 16 modified
    @@ -31,11 +31,7 @@
     )
     
     from ._util import (
    -    _disable_sha1_pubkey,
    -    _disable_sha2,
    -    _disable_sha2_pubkey,
         _support,
    -    requires_sha1_signing,
         server,
         unicodey,
     )
    @@ -134,8 +130,9 @@ def auth_exception_when_disconnected(self):
             verify that we catch a server disconnecting during auth, and report
             it as an auth failure.
             """
    -        with server(defer=True, skip_verify=True) as (tc, ts), raises(
    -            AuthenticationException
    +        with (
    +            server(defer=True, skip_verify=True) as (tc, ts),
    +            raises(AuthenticationException),
             ):
                 tc.auth_password("bad-server", "hello")
     
    @@ -144,9 +141,10 @@ def non_responsive_triggers_auth_exception(self):
             verify that authentication times out if server takes to long to
             respond (or never responds).
             """
    -        with server(defer=True, skip_verify=True) as (tc, ts), raises(
    -            AuthenticationException
    -        ) as info:
    +        with (
    +            server(defer=True, skip_verify=True) as (tc, ts),
    +            raises(AuthenticationException) as info,
    +        ):
                 tc.auth_timeout = 1  # 1 second, to speed up test
                 tc.auth_password("unresponsive-server", "hello")
                 assert "Authentication timeout" in str(info.value)
    @@ -158,7 +156,6 @@ def _server(self, *args, **kwargs):
             return server(*args, **kwargs)
     
         class fallback_pubkey_algorithm:
    -        @requires_sha1_signing
             def key_type_algo_selected_when_no_server_sig_algs(self):
                 privkey = RSAKey.from_private_key_file(_support("rsa.key"))
                 # Server pretending to be an apparently common setup:
    @@ -167,6 +164,10 @@ def key_type_algo_selected_when_no_server_sig_algs(self):
                 # This is the scenario in which Paramiko has to guess-the-algo, and
                 # where servers that don't support sha2 or server-sig-algs can give
                 # us trouble.
    +            # TODO: ok maybe I need to reinstate _disable_sha2_pubkey, but it
    +            # _might_ make sense to choose some other key type to disable, if
    +            # it's more sensible now - surely the modern version of this
    +            # scenario would be disabling like, ecdsa or something?
                 server_init = dict(_disable_sha2_pubkey, server_sig_algs=False)
                 with self._server(
                     pubkeys=[privkey],
    @@ -177,9 +178,11 @@ def key_type_algo_selected_when_no_server_sig_algs(self):
                     # Auth did work
                     assert tc.is_authenticated()
                     # Selected ssh-rsa, instead of first-in-the-list (rsa-sha2-512)
    +                # TODO: this needs to be selecting some other fallback now,
    +                # presumably whatever is now first in one of our attribute
    +                # lists
                     assert tc._agreed_pubkey_algorithm == "ssh-rsa"
     
    -        @requires_sha1_signing
             def key_type_algo_selection_is_cert_suffix_aware(self):
                 # This key has a cert next to it, which should trigger cert-aware
                 # loading within key classes.
    @@ -197,10 +200,10 @@ def key_type_algo_selection_is_cert_suffix_aware(self):
                     # Selected expected cert type
                     assert (
                         tc._agreed_pubkey_algorithm
    +                    # TODO: this needs to be choosing a sha2 or w/e now
                         == "ssh-rsa-cert-v01@openssh.com"
                     )
     
    -        @requires_sha1_signing
             def uses_first_preferred_algo_if_key_type_not_in_list(self):
                 # This is functionally the same as legacy AuthHandler, just
                 # arriving at the same place in a different manner.
    @@ -210,6 +213,8 @@ def uses_first_preferred_algo_if_key_type_not_in_list(self):
                     pubkeys=[privkey],
                     connect=dict(pkey=privkey),
                     server_init=server_init,
    +                # TODO: this is outdated now, disable something else, probably
    +                # whatever is /now/ first in the list instead of ssh-rsa
                     client_init=_disable_sha1_pubkey,  # no ssh-rsa
                     catch_error=True,
                 ) as (tc, ts, err):
    @@ -226,7 +231,7 @@ def pubkey_auth_honors_disabled_algorithms(self):
                 connect=dict(pkey=privkey),
                 init=dict(
                     disabled_algorithms=dict(
    -                    pubkeys=["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512"]
    +                    pubkeys=["rsa-sha2-256", "rsa-sha2-512"]
                     )
                 ),
                 catch_error=True,
    @@ -239,6 +244,7 @@ def client_sha2_disabled_server_sha1_disabled_no_match(self):
             with server(
                 pubkeys=[privkey],
                 connect=dict(pkey=privkey),
    +            # TODO: update or maybe just nuke this test now
                 client_init=_disable_sha2_pubkey,
                 server_init=_disable_sha1_pubkey,
                 catch_error=True,
    @@ -250,24 +256,27 @@ def client_sha1_disabled_server_sha2_disabled_no_match(self):
             with server(
                 pubkeys=[privkey],
                 connect=dict(pkey=privkey),
    +            # TODO: update or maybe just nuke this test now
                 client_init=_disable_sha1_pubkey,
                 server_init=_disable_sha2_pubkey,
                 catch_error=True,
             ) as (tc, ts, err):
                 assert isinstance(err, AuthenticationException)
     
    -    @requires_sha1_signing
         def ssh_rsa_still_used_when_sha2_disabled(self):
             privkey = RSAKey.from_private_key_file(_support("rsa.key"))
             # NOTE: this works because key obj comparison uses public bytes
             # TODO: would be nice for PKey to grow a legit "give me another obj of
             # same class but just the public bits" using asbytes()
             with server(
    -            pubkeys=[privkey], connect=dict(pkey=privkey), init=_disable_sha2
    +            # TODO: update
    +            pubkeys=[privkey],
    +            connect=dict(pkey=privkey),
    +            init=_disable_sha2,
             ) as (tc, _):
                 assert tc.is_authenticated()
     
    -    @requires_sha1_signing
    +    # TODO: isn't this duplicating some of the earlier tests? if not, update it
         def first_client_preferred_algo_used_when_no_server_sig_algs(self):
             privkey = RSAKey.from_private_key_file(_support("rsa.key"))
             # Server pretending to be an apparently common setup:
    
  • tests/test_client.py+20 45 modified
    @@ -40,13 +40,15 @@
     from paramiko.pkey import PublicBlob
     from paramiko.ssh_exception import AuthenticationException, SSHException
     
    -from ._util import _support, requires_sha1_signing, slow
    +from ._util import _support, slow
     
     requires_gss_auth = unittest.skipUnless(
         paramiko.GSS_AUTH_AVAILABLE, "GSS auth not available"
     )
     
     FINGERPRINTS = {
    +    # TODO: this should still be ok as it's specifically re: key
    +    # type/identity.
         "ssh-rsa": b"\x60\x73\x38\x44\xcb\x51\x86\x65\x7f\xde\xda\xa2\x2b\x5a\x57\xd5",  # noqa
         "ecdsa-sha2-nistp256": b"\x25\x19\xeb\x55\xe6\xa1\x47\xff\x4f\x38\xd2\x75\x6f\xa5\xd5\x60",  # noqa
         "ssh-ed25519": b'\xb3\xd5"\xaa\xf9u^\xe8\xcd\x0e\xea\x02\xb9)\xa2\x80',
    @@ -198,7 +200,13 @@ def _test_connection(self, **kwargs):
             self.tc = SSHClient()
             # Pretend we have a known_hosts file or similar
             self.tc.get_host_keys().add(
    -            f"[{self.addr}]:{self.port}", "ssh-rsa", public_host_key
    +            f"[{self.addr}]:{self.port}",
    +            # TODO: so, this is 'keytype', which is still allowed to be
    +            # ssh-rsa, as it "comes from the key itself" per `man sshd`.
    +            # TODO: but q is, is part of our pipeline still conflating keytype
    +            # with signature algorithm? seems likely?
    +            "ssh-rsa",
    +            public_host_key,
             )
     
             # Actual connection
    @@ -240,38 +248,34 @@ def _test_connection(self, **kwargs):
     
     
     class SSHClientTest(ClientTest):
    -    @requires_sha1_signing
         def test_client(self):
             """
             verify that the SSHClient stuff works too.
             """
             self._test_connection(password="pygmalion")
     
    -    @requires_sha1_signing
         def test_client_rsa(self):
             """
             verify that SSHClient works with an RSA key.
             """
             self._test_connection(key_filename=_support("rsa.key"))
     
    -    @requires_sha1_signing
         def test_client_ecdsa(self):
             """
             verify that SSHClient works with an ECDSA key.
             """
             self._test_connection(key_filename=_support("ecdsa-256.key"))
     
    -    @requires_sha1_signing
         def test_client_ed25519(self):
             self._test_connection(key_filename=_support("ed25519.key"))
     
    -    @requires_sha1_signing
         def test_multiple_key_files(self):
             """
             verify that SSHClient accepts and tries multiple key files.
             """
             # This is dumb :(
             types_ = {
    +            # TODO: this is just about key identity so should be ok
                 "rsa": "ssh-rsa",
                 "ed25519": "ssh-ed25519",
                 "ecdsa": "ecdsa-sha2-nistp256",
    @@ -307,7 +311,6 @@ def test_multiple_key_files(self):
                     self.tearDown()
                     self.setUp()
     
    -    @requires_sha1_signing
         def test_multiple_key_files_failure(self):
             """
             Expect failure when multiple keys in play and none are accepted
    @@ -321,7 +324,6 @@ def test_multiple_key_files_failure(self):
                 allowed_keys=["ecdsa-sha2-nistp256"],
             )
     
    -    @requires_sha1_signing
         def test_certs_allowed_as_key_filename_values(self):
             # NOTE: giving cert path here, not key path. (Key path test is below.
             # They're similar except for which path is given; the expected auth and
    @@ -334,7 +336,6 @@ def test_certs_allowed_as_key_filename_values(self):
                     public_blob=PublicBlob.from_file(f"{key_path}-cert.pub"),
                 )
     
    -    @requires_sha1_signing
         def test_certs_implicitly_loaded_alongside_key_filename_keys(self):
             # NOTE: a regular test_connection() w/ rsa.key would incidentally
             # test this (because test_xxx.key-cert.pub exists) but incidental tests
    @@ -349,29 +350,6 @@ def test_certs_implicitly_loaded_alongside_key_filename_keys(self):
                     public_blob=PublicBlob.from_file(f"{key_path}-cert.pub"),
                 )
     
    -    def _cert_algo_test(self, ver, alg):
    -        # Issue #2017; see auth_handler.py
    -        self.connect_kwargs["username"] = "somecertuser"  # neuter pw auth
    -        self._test_connection(
    -            # NOTE: SSHClient is able to take either the key or the cert & will
    -            # set up its internals as needed
    -            key_filename=_support("rsa.key-cert.pub"),
    -            server_name="SSH-2.0-OpenSSH_{}".format(ver),
    -        )
    -        assert (
    -            self.tc._transport._agreed_pubkey_algorithm
    -            == "{}-cert-v01@openssh.com".format(alg)
    -        )
    -
    -    @requires_sha1_signing
    -    def test_old_openssh_needs_ssh_rsa_for_certs_not_rsa_sha2(self):
    -        self._cert_algo_test(ver="7.7", alg="ssh-rsa")
    -
    -    @requires_sha1_signing
    -    def test_newer_openssh_uses_rsa_sha2_for_certs_not_ssh_rsa(self):
    -        # NOTE: 512 happens to be first in our list and is thus chosen
    -        self._cert_algo_test(ver="7.8", alg="rsa-sha2-512")
    -
         def test_default_key_locations_trigger_cert_loads_if_found(self):
             # TODO: what it says on the tin: ~/.ssh/id_rsa tries to load
             # ~/.ssh/id_rsa-cert.pub. Right now no other tests actually test that
    @@ -417,6 +395,8 @@ def test_save_host_keys(self):
     
             host_id = f"[{self.addr}]:{self.port}"
     
    +        # TODO: this should still be okay, host keys -> keytype -> is not
    +        # directly tied to algo -> but need to see if guts do that
             client.get_host_keys().add(host_id, "ssh-rsa", public_host_key)
             assert len(client.get_host_keys()) == 1
             assert public_host_key == client.get_host_keys()[host_id]["ssh-rsa"]
    @@ -513,13 +493,15 @@ def test_banner_timeout(self):
     
             self.tc = SSHClient()
             self.tc.get_host_keys().add(
    -            f"[{self.addr}]:{self.port}", "ssh-rsa", public_host_key
    +            f"[{self.addr}]:{self.port}",
    +            # TODO: hostkey keytype == ok-ish
    +            "ssh-rsa",
    +            public_host_key,
             )
             # Connect with a half second banner timeout.
             kwargs = dict(self.connect_kwargs, banner_timeout=0.5)
             self.assertRaises(paramiko.SSHException, self.tc.connect, **kwargs)
     
    -    @requires_sha1_signing
         def test_auth_trickledown(self):
             """
             Failed key auth doesn't prevent subsequent pw auth from succeeding
    @@ -540,7 +522,6 @@ def test_auth_trickledown(self):
             )
             self._test_connection(**kwargs)
     
    -    @requires_sha1_signing
         @slow
         def test_auth_timeout(self):
             """
    @@ -667,15 +648,14 @@ def test_host_key_negotiation_1(self):
             host_key = paramiko.ECDSAKey.generate()
             self._client_host_key_bad(host_key)
     
    -    @requires_sha1_signing
         def test_host_key_negotiation_2(self):
    +        # TODO: this may now be too small for audit recco, dlbcheck
             host_key = paramiko.RSAKey.generate(2048)
             self._client_host_key_bad(host_key)
     
         def test_host_key_negotiation_3(self):
             self._client_host_key_good(paramiko.ECDSAKey, "ecdsa-256.key")
     
    -    @requires_sha1_signing
         def test_host_key_negotiation_4(self):
             self._client_host_key_good(paramiko.RSAKey, "rsa.key")
     
    @@ -747,10 +727,10 @@ def test_disabled_algorithms_passed_directly_if_given(self, Transport):
                 "host",
                 sock=Mock(),
                 password="no",
    -            disabled_algorithms={"keys": ["ssh-rsa"]},
    +            disabled_algorithms={"keys": ["ed25519"]},
             )
             call_arg = Transport.call_args[1]["disabled_algorithms"]
    -        assert call_arg == {"keys": ["ssh-rsa"]}
    +        assert call_arg == {"keys": ["ed25519"]}
     
         @patch("paramiko.client.Transport")
         def test_transport_factory_defaults_to_Transport(self, Transport):
    @@ -793,28 +773,24 @@ class PasswordPassphraseTests(ClientTest):
         # instead of suffering a real connection cycle.
         # TODO: in that case, move the below to be part of an integration suite?
     
    -    @requires_sha1_signing
         def test_password_kwarg_works_for_password_auth(self):
             # Straightforward / duplicate of earlier basic password test.
             self._test_connection(password="pygmalion")
     
         # TODO: more granular exception pending #387; should be signaling "no auth
         # methods available" because no key and no password
         @raises(SSHException)
    -    @requires_sha1_signing
         def test_passphrase_kwarg_not_used_for_password_auth(self):
             # Using the "right" password in the "wrong" field shouldn't work.
             self._test_connection(passphrase="pygmalion")
     
    -    @requires_sha1_signing
         def test_passphrase_kwarg_used_for_key_passphrase(self):
             # Straightforward again, with new passphrase kwarg.
             self._test_connection(
                 key_filename=_support("test_rsa_password.key"),
                 passphrase="television",
             )
     
    -    @requires_sha1_signing
         def test_password_kwarg_used_for_passphrase_when_no_passphrase_kwarg_given(
             self,
         ):  # noqa
    @@ -825,7 +801,6 @@ def test_password_kwarg_used_for_passphrase_when_no_passphrase_kwarg_given(
             )
     
         @raises(AuthenticationException)  # TODO: more granular
    -    @requires_sha1_signing
         def test_password_kwarg_not_used_for_passphrase_when_passphrase_kwarg_given(  # noqa
             self,
         ):
    
  • tests/test_pkey.py+2 6 modified
    @@ -44,7 +44,7 @@
     from paramiko.common import byte_chr, o600
     from paramiko.util import b
     
    -from ._util import _support, is_low_entropy, requires_sha1_signing
    +from ._util import _support, is_low_entropy
     
     # from openssh's ssh-keygen
     PUB_RSA = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c="  # noqa
    @@ -198,18 +198,14 @@ def _sign_and_verify_rsa(self, algorithm, saved_sig):
             pub = RSAKey(data=key.asbytes())
             self.assertTrue(pub.verify_ssh_sig(b"ice weasels", msg))
     
    -    @requires_sha1_signing
    -    def test_sign_and_verify_ssh_rsa(self):
    -        self._sign_and_verify_rsa("ssh-rsa", SIGNED_RSA)
    -
         def test_sign_and_verify_rsa_sha2_512(self):
             self._sign_and_verify_rsa("rsa-sha2-512", SIGNED_RSA_512)
     
         def test_sign_and_verify_rsa_sha2_256(self):
             self._sign_and_verify_rsa("rsa-sha2-256", SIGNED_RSA_256)
     
    -    @requires_sha1_signing
         def test_generate_rsa(self):
    +        # TODO: this probs needs to be larger number now
             key = RSAKey.generate(1024)
             msg = key.sign_ssh_data(b"jerri blank")
             msg.rewind()
    
  • tests/test_transport.py+9 19 modified
    @@ -69,11 +69,8 @@
         TestServer as NullServer,
     )
     from ._util import (
    -    _disable_sha1,
    -    _disable_sha2,
         _support,
         needs_builtin,
    -    requires_sha1_signing,
         server,
         slow,
     )
    @@ -1172,14 +1169,6 @@ class TestSHA2SignatureKeyExchange(unittest.TestCase):
         # are new tests in test_pkey.py which use known signature blobs to prove
         # the SHA2 family was in fact used!
     
    -    @requires_sha1_signing
    -    def test_base_case_ssh_rsa_still_used_as_fallback(self):
    -        # Prove that ssh-rsa is used if either, or both, participants have SHA2
    -        # algorithms disabled
    -        for which in ("init", "client_init", "server_init"):
    -            with server(**{which: _disable_sha2}) as (tc, _):
    -                assert tc.host_key_type == "ssh-rsa"
    -
         def test_kex_with_sha2_512(self):
             # It's the default!
             with server() as (tc, _):
    @@ -1211,15 +1200,16 @@ def _incompatible_peers(self, client_init, server_init):
                 else:
                     raise err
     
    -    def test_client_sha2_disabled_server_sha1_disabled_no_match(self):
    -        self._incompatible_peers(
    -            client_init=_disable_sha2, server_init=_disable_sha1
    -        )
    +    # TODO: update these couple the same way we did under test_client...
    +    # def test_client_sha2_disabled_server_sha1_disabled_no_match(self):
    +    #    self._incompatible_peers(
    +    #        client_init=_disable_sha2, server_init=_disable_sha1
    +    #    )
     
    -    def test_client_sha1_disabled_server_sha2_disabled_no_match(self):
    -        self._incompatible_peers(
    -            client_init=_disable_sha1, server_init=_disable_sha2
    -        )
    +    # def test_client_sha1_disabled_server_sha2_disabled_no_match(self):
    +    #    self._incompatible_peers(
    +    #        client_init=_disable_sha1, server_init=_disable_sha2
    +    #    )
     
         def test_explicit_client_hostkey_not_limited(self):
             # Be very explicit about the hostkey on BOTH ends,
    
  • tests/_util.py+3 36 modified
    @@ -167,42 +167,9 @@ def is_low_entropy():
         return is_32bit and os.environ.get("PYTHONHASHSEED", None) == "0"
     
     
    -def sha1_signing_unsupported():
    -    """
    -    This is used to skip tests in environments where SHA-1 signing is
    -    not supported by the backend.
    -    """
    -    private_key = rsa.generate_private_key(
    -        public_exponent=65537, key_size=2048, backend=default_backend()
    -    )
    -    message = b"Some dummy text"
    -    try:
    -        private_key.sign(
    -            message,
    -            padding.PSS(
    -                mgf=padding.MGF1(hashes.SHA1()),
    -                salt_length=padding.PSS.MAX_LENGTH,
    -            ),
    -            hashes.SHA1(),
    -        )
    -        return False
    -    except UnsupportedAlgorithm as e:
    -        return e._reason == _Reasons.UNSUPPORTED_HASH
    -
    -
    -requires_sha1_signing = unittest.skipIf(
    -    sha1_signing_unsupported(), "SHA-1 signing not supported"
    -)
    -
    -_disable_sha2 = dict(
    -    disabled_algorithms=dict(keys=["rsa-sha2-256", "rsa-sha2-512"])
    -)
    -_disable_sha1 = dict(disabled_algorithms=dict(keys=["ssh-rsa"]))
    -_disable_sha2_pubkey = dict(
    -    disabled_algorithms=dict(pubkeys=["rsa-sha2-256", "rsa-sha2-512"])
    -)
    -_disable_sha1_pubkey = dict(disabled_algorithms=dict(pubkeys=["ssh-rsa"]))
    -
    +# TODO: doublecheck where _disable_xxx helpers were in use; disabling sha1 no
    +# longer makes any sense, and presumably neither does en/dis abling sha2 but
    +# that's the question innit
     
     unicodey = "\u2022"
     
    

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

4

News mentions

0

No linked articles in our index yet.