diff options
-rw-r--r-- | paramiko/client.py | 25 | ||||
-rw-r--r-- | paramiko/kex_gss.py | 6 | ||||
-rw-r--r-- | paramiko/transport.py | 1 | ||||
-rw-r--r-- | sites/www/changelog.rst | 3 | ||||
-rw-r--r-- | tests/test_client.py | 61 |
5 files changed, 81 insertions, 15 deletions
diff --git a/paramiko/client.py b/paramiko/client.py index bff223ea..6e2e80ae 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -364,22 +364,21 @@ class SSHClient (ClosingContextManager): server_hostkey_name = "[%s]:%d" % (hostname, port) our_server_keys = None - # If GSS-API Key Exchange is performed we are not required to check the - # host key, because the host is authenticated via GSS-API / SSPI as - # well as our client. - if not self._transport.use_gss_kex: - our_server_keys = self._system_host_keys.get(server_hostkey_name) - if our_server_keys is None: - our_server_keys = self._host_keys.get(server_hostkey_name) - if our_server_keys is not None: - keytype = our_server_keys.keys()[0] - sec_opts = t.get_security_options() - other_types = [x for x in sec_opts.key_types if x != keytype] - sec_opts.key_types = [keytype] + other_types + our_server_keys = self._system_host_keys.get(server_hostkey_name) + if our_server_keys is None: + our_server_keys = self._host_keys.get(server_hostkey_name) + if our_server_keys is not None: + keytype = our_server_keys.keys()[0] + sec_opts = t.get_security_options() + other_types = [x for x in sec_opts.key_types if x != keytype] + sec_opts.key_types = [keytype] + other_types t.start_client(timeout=timeout) - if not self._transport.use_gss_kex: + # If GSS-API Key Exchange is performed we are not required to check the + # host key, because the host is authenticated via GSS-API / SSPI as + # well as our client. + if not self._transport.gss_kex_used: server_key = t.get_remote_server_key() if our_server_keys is None: # will raise exception if the key is rejected diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index 3406babb..04906abd 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -83,7 +83,6 @@ class KexGSSGroup1(object): """ Start the GSS-API / SSPI Authenticated Diffie-Hellman Key Exchange. """ - self.transport.gss_kex_used = True self._generate_x() if self.transport.server_mode: # compute f = g^x mod p, but don't send it yet @@ -216,6 +215,7 @@ class KexGSSGroup1(object): else: self.kexgss.ssh_check_mic(mic_token, self.transport.session_id) + self.transport.gss_kex_used = True self.transport._activate_outbound() def _parse_kexgss_init(self, m): @@ -258,6 +258,7 @@ class KexGSSGroup1(object): else: m.add_boolean(False) self.transport._send_message(m) + self.transport.gss_kex_used = True self.transport._activate_outbound() else: m.add_byte(c_MSG_KEXGSS_CONTINUE) @@ -325,7 +326,6 @@ class KexGSSGex(object): """ Start the GSS-API / SSPI Authenticated Diffie-Hellman Group Exchange """ - self.transport.gss_kex_used = True if self.transport.server_mode: self.transport._expect_packet(MSG_KEXGSS_GROUPREQ) return @@ -501,6 +501,7 @@ class KexGSSGex(object): else: m.add_boolean(False) self.transport._send_message(m) + self.transport.gss_kex_used = True self.transport._activate_outbound() else: m.add_byte(c_MSG_KEXGSS_CONTINUE) @@ -587,6 +588,7 @@ class KexGSSGex(object): else: self.kexgss.ssh_check_mic(mic_token, self.transport.session_id) + self.transport.gss_kex_used = True self.transport._activate_outbound() def _parse_kexgss_error(self, m): diff --git a/paramiko/transport.py b/paramiko/transport.py index df068b3c..7ed3bad6 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -1995,6 +1995,7 @@ class Transport(threading.Thread, ClosingContextManager): self.clear_to_send.clear() finally: self.clear_to_send_lock.release() + self.gss_kex_used = False self.in_kex = True if self.server_mode: mp_required_prefix = 'diffie-hellman-group-exchange-sha' diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 5728a281..c3640a38 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`1055` (also :issue:`1056`, :issue:`1057`, :issue:`1058`, :issue:`1059`) + Fix up host-key checking in our GSSAPI support, which was previously using an + incorrect API call. Thanks to Anselm Kruis for the patches. * :support:`979` Update how we use `Cryptography <https://cryptography.io>`_'s signature/verification methods so we aren't relying on a deprecated API. Thanks to Paul Kehrer for the patch. diff --git a/tests/test_client.py b/tests/test_client.py index 7ada13da..5d616fcf 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -170,6 +170,7 @@ class SSHClientTest (unittest.TestCase): self.assertTrue(self.ts.is_active()) self.assertEqual('slowdive', self.ts.get_username()) self.assertEqual(True, self.ts.is_authenticated()) + self.assertEqual(False, self.tc.get_transport().gss_kex_used) # Command execution functions? stdin, stdout, stderr = self.tc.exec_command('yes') @@ -447,6 +448,66 @@ class SSHClientTest (unittest.TestCase): auth_timeout=0.5, ) + def test_10_auth_trickledown_gsskex(self): + """ + Failed gssapi-keyex auth doesn't prevent subsequent key auth from succeeding + """ + if not paramiko.GSS_AUTH_AVAILABLE: + return # for python 2.6 lacks skipTest + kwargs = dict( + gss_kex=True, + key_filename=[test_path('test_rsa.key')], + ) + self._test_connection(**kwargs) + + def test_11_auth_trickledown_gssauth(self): + """ + Failed gssapi-with-mic auth doesn't prevent subsequent key auth from succeeding + """ + if not paramiko.GSS_AUTH_AVAILABLE: + return # for python 2.6 lacks skipTest + kwargs = dict( + gss_auth=True, + key_filename=[test_path('test_rsa.key')], + ) + self._test_connection(**kwargs) + + def test_12_reject_policy(self): + """ + verify that SSHClient's RejectPolicy works. + """ + threading.Thread(target=self._run).start() + + self.tc = paramiko.SSHClient() + self.tc.set_missing_host_key_policy(paramiko.RejectPolicy()) + self.assertEqual(0, len(self.tc.get_host_keys())) + self.assertRaises( + paramiko.SSHException, + self.tc.connect, + password='pygmalion', **self.connect_kwargs + ) + + def test_13_reject_policy_gsskex(self): + """ + verify that SSHClient's RejectPolicy works, + even if gssapi-keyex was enabled but not used. + """ + # Test for a bug present in paramiko versions released before 2017-08-01 + if not paramiko.GSS_AUTH_AVAILABLE: + return # for python 2.6 lacks skipTest + threading.Thread(target=self._run).start() + + self.tc = paramiko.SSHClient() + self.tc.set_missing_host_key_policy(paramiko.RejectPolicy()) + self.assertEqual(0, len(self.tc.get_host_keys())) + self.assertRaises( + paramiko.SSHException, + self.tc.connect, + password='pygmalion', + gss_kex=True, + **self.connect_kwargs + ) + def _client_host_key_bad(self, host_key): threading.Thread(target=self._run).start() hostname = '[%s]:%d' % (self.addr, self.port) |