diff --git a/paramiko/_version.py b/paramiko/_version.py index eaabe9995..c21caba34 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,3 +1,3 @@ # last version component is odd for pre-release development, even for stable release -__version_info__ = (2, 8, 7) +__version_info__ = (2, 8, 8) __version__ = '.'.join(map(str, __version_info__)) diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 218c400d5..1eb57c9c5 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -152,6 +152,7 @@ def __delitem__(self, key): for e in list(self._entries): if e.key.get_name() == key: self._entries.remove(e) + break else: raise KeyError(key) diff --git a/paramiko/pkey.py b/paramiko/pkey.py index 0cfa69e18..137e58f1c 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -209,26 +209,20 @@ def asbytes(self): def __str__(self): return self.asbytes() - # noinspection PyUnresolvedReferences - # TODO: The comparison functions should be removed as per: - # https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons def __cmp__(self, other): + # python-2 only, same purpose as __eq__() + return cmp(self.asbytes(), other.asbytes()) # noqa + + def __eq__(self, other): """ - Compare this key to another. Returns 0 if this key is equivalent to - the given key, or non-0 if they are different. Only the public parts + Compare this key to another. Returns True if this key is equivalent to + the given key, or False if they are different. Only the public parts of the key are compared, so a public key will compare equal to its corresponding private key. :param .PKey other: key to compare to. """ - hs = hash(self) - ho = hash(other) - if hs != ho: - return cmp(hs, ho) # noqa - return cmp(self.asbytes(), other.asbytes()) # noqa - - def __eq__(self, other): - return hash(self) == hash(other) + return self.asbytes() == other.asbytes() def get_name(self): """ diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py index f7e40b645..212434046 100644 --- a/paramiko/rsakey.py +++ b/paramiko/rsakey.py @@ -128,10 +128,15 @@ def verify_ssh_sig(self, data, msg): if isinstance(key, rsa.RSAPrivateKey): key = key.public_key() + # pad received signature with leading zeros, key.verify() expects + # a signature of key_size bits (e.g. PuTTY doesn't pad) + sign = msg.get_binary() + diff = key.key_size - len(sign) * 8 + if diff > 0: + sign = b"\x00" * ((diff + 7) // 8) + sign + try: - key.verify( - msg.get_binary(), data, padding.PKCS1v15(), hashes.SHA1() - ) + key.verify(sign, data, padding.PKCS1v15(), hashes.SHA1()) except InvalidSignature: return False else: diff --git a/tests/test_client.py b/tests/test_client.py index ec3c59e97..86bfe1870 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -409,15 +409,17 @@ def test_cleanup(self): self.tc.close() del self.tc - # force a collection to see whether the SSHClient object is deallocated - # 2 GCs are needed on PyPy, time is needed for Python 3 + # GC is unpredictable, depending on python version and implementation time.sleep(0.1) gc.collect() + time.sleep(0.2) + gc.collect() time.sleep(0.1) gc.collect() + time.sleep(0.2) + gc.collect() time.sleep(0.1) gc.collect() - self.assertTrue(p() is None) def test_client_can_be_used_as_context_manager(self): diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py index 4955095e8..e37415d7c 100644 --- a/tests/test_hostkeys.py +++ b/tests/test_hostkeys.py @@ -128,3 +128,20 @@ def test_delitem(self): pass # Good else: assert False, "Entry was not deleted from HostKeys on delitem!" + + def test_entry_delitem(self): + hostdict = paramiko.HostKeys('hostfile.temp') + target = 'happy.example.com' + entry = hostdict[target] + key_type_list = [key_type for key_type in entry] + for key_type in key_type_list: + del entry[key_type] + + # will KeyError if not present + for key_type in key_type_list: + try: + del entry[key_type] + except KeyError: + pass # Good + else: + assert False, "Key was not deleted from Entry on delitem!" diff --git a/tests/test_pkey.py b/tests/test_pkey.py index fd0f29395..4e25d2429 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -612,6 +612,25 @@ def test_certificates(self): _support('test_rsa.key-cert.pub'), ) + def keys_loaded_twice(self): + for key_class, filename in [ + (RSAKey, "test_rsa.key"), + (DSSKey, "test_dss.key"), + (ECDSAKey, "test_ecdsa_256.key"), + (Ed25519Key, "test_ed25519.key"), + ]: + key1 = key_class.from_private_key_file(_support(filename)) + key2 = key_class.from_private_key_file(_support(filename)) + yield key1, key2 + + def test_keys_are_comparable(self): + for key1, key2 in self.keys_loaded_twice(): + assert key1 == key2 + + def test_keys_are_hashable(self): + for key1, key2 in self.keys_loaded_twice(): + assert hash(key1) == hash(key2) + def test_autodetect_ed25519(self): key = load_private_key_file(_support("test_ed25519.key")) self.assertIsInstance(key, Ed25519Key)