summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/client.py25
-rw-r--r--paramiko/kex_gss.py6
-rw-r--r--paramiko/transport.py1
-rw-r--r--sites/www/changelog.rst3
-rw-r--r--tests/test_client.py61
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)