summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--paramiko/auth_handler.py151
-rw-r--r--paramiko/transport.py2
-rw-r--r--tests/test_ssh_gss.py45
3 files changed, 145 insertions, 53 deletions
diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py
index 33f01da6..13c41c9b 100644
--- a/paramiko/auth_handler.py
+++ b/paramiko/auth_handler.py
@@ -503,52 +503,13 @@ class AuthHandler (object):
supported_mech = sshgss.ssh_gss_oids("server")
# RFC 4462 says we are not required to implement GSS-API error
# messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt
- while True:
- m = Message()
- m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE)
- m.add_bytes(supported_mech)
- self.transport._send_message(m)
- ptype, m = self.transport.packetizer.read_message()
- if ptype == MSG_USERAUTH_GSSAPI_TOKEN:
- client_token = m.get_string()
- # use the client token as input to establish a secure
- # context.
- try:
- token = sshgss.ssh_accept_sec_context(self.gss_host,
- client_token,
- username)
- except Exception:
- result = AUTH_FAILED
- self._send_auth_result(username, method, result)
- raise
- if token is not None:
- m = Message()
- m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
- m.add_string(token)
- self.transport._send_message(m)
- else:
- result = AUTH_FAILED
- self._send_auth_result(username, method, result)
- return
- # check MIC
- ptype, m = self.transport.packetizer.read_message()
- if ptype == MSG_USERAUTH_GSSAPI_MIC:
- break
- mic_token = m.get_string()
- try:
- sshgss.ssh_check_mic(mic_token,
- self.transport.session_id,
- username)
- except Exception:
- result = AUTH_FAILED
- self._send_auth_result(username, method, result)
- raise
- # TODO: Implement client credential saving.
- # The OpenSSH server is able to create a TGT with the delegated
- # client credentials, but this is not supported by GSS-API.
- result = AUTH_SUCCESSFUL
- self.transport.server_object.check_auth_gssapi_with_mic(
- username, result)
+ m = Message()
+ m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE)
+ m.add_bytes(supported_mech)
+ self.transport.auth_handler = GssapiWithMicAuthHandler(self, sshgss)
+ self.transport._expected_packet = (MSG_USERAUTH_GSSAPI_TOKEN, MSG_USERAUTH_REQUEST, MSG_SERVICE_REQUEST)
+ self.transport._send_message(m)
+ return
elif method == "gssapi-keyex" and gss_auth:
mic_token = m.get_string()
sshgss = self.transport.kexgss_ctxt
@@ -658,3 +619,101 @@ class AuthHandler (object):
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
}
+
+
+class GssapiWithMicAuthHandler(object):
+ """A specialized Auth handler for gssapi-with-mic
+
+ During the GSSAPI token exchange we need a modified dispatch table,
+ because the packet type numbers are not unique.
+ """
+
+ method = "gssapi-with-mic"
+
+ def __init__(self, delegate, sshgss):
+ self._delegate = delegate
+ self.sshgss = sshgss
+
+ def abort(self):
+ self._restore_delegate_auth_handler()
+ return self._delegate.abort()
+
+ @property
+ def transport(self):
+ return self._delegate.transport
+
+ @property
+ def _send_auth_result(self):
+ return self._delegate._send_auth_result
+
+ @property
+ def auth_username(self):
+ return self._delegate.auth_username
+
+ @property
+ def gss_host(self):
+ return self._delegate.gss_host
+
+ def _restore_delegate_auth_handler(self):
+ self.transport.auth_handler = self._delegate
+
+ def _parse_userauth_gssapi_token(self, m):
+ client_token = m.get_string()
+ # use the client token as input to establish a secure
+ # context.
+ sshgss = self.sshgss
+ try:
+ token = sshgss.ssh_accept_sec_context(self.gss_host,
+ client_token,
+ self.auth_username)
+ except Exception as e:
+ self.transport.saved_exception = e
+ result = AUTH_FAILED
+ self._restore_delegate_auth_handler()
+ self._send_auth_result(self.auth_username, self.method, result)
+ raise
+ if token is not None:
+ m = Message()
+ m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
+ m.add_string(token)
+ self.transport._expected_packet = (MSG_USERAUTH_GSSAPI_TOKEN,
+ MSG_USERAUTH_GSSAPI_MIC,
+ MSG_USERAUTH_REQUEST)
+ self.transport._send_message(m)
+
+ def _parse_userauth_gssapi_mic(self, m):
+ mic_token = m.get_string()
+ sshgss = self.sshgss
+ username = self.auth_username
+ self._restore_delegate_auth_handler()
+ try:
+ sshgss.ssh_check_mic(mic_token,
+ self.transport.session_id,
+ username)
+ except Exception as e:
+ self.transport.saved_exception = e
+ result = AUTH_FAILED
+ self._send_auth_result(username, self.method, result)
+ raise
+ # TODO: Implement client credential saving.
+ # The OpenSSH server is able to create a TGT with the delegated
+ # client credentials, but this is not supported by GSS-API.
+ result = AUTH_SUCCESSFUL
+ self.transport.server_object.check_auth_gssapi_with_mic(username, result)
+ # okay, send result
+ self._send_auth_result(username, self.method, result)
+
+ def _parse_service_request(self, m):
+ self._restore_delegate_auth_handler()
+ return self._delegate._parse_service_request(m)
+
+ def _parse_userauth_request(self, m):
+ self._restore_delegate_auth_handler()
+ return self._delegate._parse_userauth_request(m)
+
+ _handler_table = {
+ MSG_SERVICE_REQUEST: _parse_service_request,
+ MSG_USERAUTH_REQUEST: _parse_userauth_request,
+ MSG_USERAUTH_GSSAPI_TOKEN: _parse_userauth_gssapi_token,
+ MSG_USERAUTH_GSSAPI_MIC: _parse_userauth_gssapi_mic,
+ }
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 19d8ee70..c68397dd 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -1838,6 +1838,8 @@ class Transport(threading.Thread, ClosingContextManager):
):
handler = self.auth_handler._handler_table[ptype]
handler(self.auth_handler, m)
+ if len(self._expected_packet) > 0:
+ continue
else:
self._log(WARNING, 'Oops, unhandled type %d' % ptype)
msg = Message()
diff --git a/tests/test_ssh_gss.py b/tests/test_ssh_gss.py
index 967b3b81..d8d05d2b 100644
--- a/tests/test_ssh_gss.py
+++ b/tests/test_ssh_gss.py
@@ -29,11 +29,13 @@ import unittest
import paramiko
+from tests.util import test_path
+from tests.test_client import FINGERPRINTS
class NullServer (paramiko.ServerInterface):
def get_allowed_auths(self, username):
- return 'gssapi-with-mic'
+ return 'gssapi-with-mic,publickey'
def check_auth_gssapi_with_mic(self, username,
gss_authenticated=paramiko.AUTH_FAILED,
@@ -45,6 +47,16 @@ class NullServer (paramiko.ServerInterface):
def enable_auth_gssapi(self):
return True
+ def check_auth_publickey(self, username, key):
+ try:
+ expected = FINGERPRINTS[key.get_name()]
+ except KeyError:
+ return paramiko.AUTH_FAILED
+ else:
+ if key.get_fingerprint() == expected:
+ return paramiko.AUTH_SUCCESSFUL
+ return paramiko.AUTH_FAILED
+
def check_channel_request(self, kind, chanid):
return paramiko.OPEN_SUCCEEDED
@@ -85,19 +97,21 @@ class GSSAuthTest(unittest.TestCase):
server = NullServer()
self.ts.start_server(self.event, server)
- def test_1_gss_auth(self):
+ def _test_connection(self, **kwargs):
"""
- Verify that Paramiko can handle SSHv2 GSS-API / SSPI authentication
- (gssapi-with-mic) in client and server mode.
+ (Most) kwargs get passed directly into SSHClient.connect().
+
+ The exception is ... no exception yet
"""
host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
self.tc = paramiko.SSHClient()
- self.tc.get_host_keys().add('[%s]:%d' % (self.hostname, self.port),
+ self.tc.set_missing_host_key_policy(paramiko.WarningPolicy())
+ self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port),
'ssh-rsa', public_host_key)
- self.tc.connect(self.hostname, self.port, username=self.username,
- gss_auth=True)
+ self.tc.connect(hostname=self.addr, port=self.port, username=self.username, gss_host=self.hostname,
+ gss_auth=True, **kwargs)
self.event.wait(1.0)
self.assert_(self.event.is_set())
@@ -120,3 +134,20 @@ class GSSAuthTest(unittest.TestCase):
stdin.close()
stdout.close()
stderr.close()
+
+ def test_1_gss_auth(self):
+ """
+ Verify that Paramiko can handle SSHv2 GSS-API / SSPI authentication
+ (gssapi-with-mic) in client and server mode.
+ """
+ self._test_connection(allow_agent=False,
+ look_for_keys=False)
+
+ def test_2_auth_trickledown(self):
+ """
+ Failed gssapi-with-mic auth doesn't prevent subsequent key auth from succeeding
+ """
+ self.hostname = "this_host_does_not_exists_and_causes_a_GSSAPI-exception"
+ self._test_connection(key_filename=[test_path('test_rsa.key')],
+ allow_agent=False,
+ look_for_keys=False)