aboutsummaryrefslogtreecommitdiff
path: root/paramiko/auth_handler.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/auth_handler.py')
-rw-r--r--paramiko/auth_handler.py207
1 files changed, 202 insertions, 5 deletions
diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py
index 57babef..b5fea65 100644
--- a/paramiko/auth_handler.py
+++ b/paramiko/auth_handler.py
@@ -28,20 +28,27 @@ from paramiko.common import cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, \
cMSG_USERAUTH_INFO_REQUEST, WARNING, AUTH_FAILED, cMSG_USERAUTH_PK_OK, \
cMSG_USERAUTH_INFO_RESPONSE, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, \
MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS, MSG_USERAUTH_FAILURE, \
- MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE
+ MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE, \
+ cMSG_USERAUTH_GSSAPI_RESPONSE, cMSG_USERAUTH_GSSAPI_TOKEN, \
+ cMSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, cMSG_USERAUTH_GSSAPI_ERROR, \
+ cMSG_USERAUTH_GSSAPI_ERRTOK, cMSG_USERAUTH_GSSAPI_MIC,\
+ MSG_USERAUTH_GSSAPI_RESPONSE, MSG_USERAUTH_GSSAPI_TOKEN, \
+ MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, MSG_USERAUTH_GSSAPI_ERROR, \
+ MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC
from paramiko.message import Message
from paramiko.py3compat import bytestring
from paramiko.ssh_exception import SSHException, AuthenticationException, \
BadAuthenticationType, PartialAuthentication
from paramiko.server import InteractiveQuery
+from paramiko.ssh_gss import GSSAuth
class AuthHandler (object):
"""
Internal class to handle the mechanics of authentication.
"""
-
+
def __init__(self, transport):
self.transport = weakref.proxy(transport)
self.username = None
@@ -56,7 +63,10 @@ class AuthHandler (object):
# for server mode:
self.auth_username = None
self.auth_fail_count = 0
-
+ # for GSSAPI
+ self.gss_host = None
+ self.gss_deleg_creds = True
+
def is_authenticated(self):
return self.authenticated
@@ -97,7 +107,7 @@ class AuthHandler (object):
self._request_auth()
finally:
self.transport.lock.release()
-
+
def auth_interactive(self, username, handler, event, submethods=''):
"""
response_list = handler(title, instructions, prompt_list)
@@ -112,7 +122,29 @@ class AuthHandler (object):
self._request_auth()
finally:
self.transport.lock.release()
-
+
+ def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds, event):
+ self.transport.lock.acquire()
+ try:
+ self.auth_event = event
+ self.auth_method = 'gssapi-with-mic'
+ self.username = username
+ self.gss_host = gss_host
+ self.gss_deleg_creds = gss_deleg_creds
+ self._request_auth()
+ finally:
+ self.transport.lock.release()
+
+ def auth_gssapi_keyex(self, username, event):
+ self.transport.lock.acquire()
+ try:
+ self.auth_event = event
+ self.auth_method = 'gssapi-keyex'
+ self.username = username
+ self._request_auth()
+ finally:
+ self.transport.lock.release()
+
def abort(self):
if self.auth_event is not None:
self.auth_event.set()
@@ -211,6 +243,77 @@ class AuthHandler (object):
elif self.auth_method == 'keyboard-interactive':
m.add_string('')
m.add_string(self.submethods)
+ elif self.auth_method == "gssapi-with-mic":
+ sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds)
+ m.add_bytes(sshgss.ssh_gss_oids())
+ # send the supported GSSAPI OIDs to the server
+ self.transport._send_message(m)
+ ptype, m = self.transport.packetizer.read_message()
+ if ptype == MSG_USERAUTH_BANNER:
+ self._parse_userauth_banner(m)
+ ptype, m = self.transport.packetizer.read_message()
+ if ptype == MSG_USERAUTH_GSSAPI_RESPONSE:
+ # Read the mechanism selected by the server. We send just
+ # the Kerberos V5 OID, so the server can only respond with
+ # this OID.
+ mech = m.get_string()
+ m = Message()
+ m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
+ m.add_string(sshgss.ssh_init_sec_context(self.gss_host,
+ mech,
+ self.username,))
+ self.transport._send_message(m)
+ while True:
+ ptype, m = self.transport.packetizer.read_message()
+ if ptype == MSG_USERAUTH_GSSAPI_TOKEN:
+ srv_token = m.get_string()
+ next_token = sshgss.ssh_init_sec_context(self.gss_host,
+ mech,
+ self.username,
+ srv_token)
+ # After this step the GSSAPI should not return any
+ # token. If it does, we keep sending the token to
+ # the server until no more token is returned.
+ if next_token is None:
+ break
+ else:
+ m = Message()
+ m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
+ m.add_string(next_token)
+ self.transport.send_message(m)
+ else:
+ raise SSHException("Received Package: %s" % MSG_NAMES[ptype])
+ m = Message()
+ m.add_byte(cMSG_USERAUTH_GSSAPI_MIC)
+ # send the MIC to the server
+ m.add_string(sshgss.ssh_get_mic(self.transport.session_id))
+ elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK:
+ # RFC 4462 says we are not required to implement GSS-API
+ # error messages.
+ # See RFC 4462 Section 3.8 in
+ # http://www.ietf.org/rfc/rfc4462.txt
+ raise SSHException("Server returned an error token")
+ elif ptype == MSG_USERAUTH_GSSAPI_ERROR:
+ maj_status = m.get_int()
+ min_status = m.get_int()
+ err_msg = m.get_string()
+ lang_tag = m.get_string() # we don't care!
+ raise SSHException("GSS-API Error:\nMajor Status: %s\n\
+ Minor Status: %s\ \nError Message:\
+ %s\n") % (str(maj_status),
+ str(min_status),
+ err_msg)
+ elif ptype == MSG_USERAUTH_FAILURE:
+ self._parse_userauth_failure(m)
+ return
+ else:
+ raise SSHException("Received Package: %s" % MSG_NAMES[ptype])
+ elif self.auth_method == 'gssapi-keyex' and\
+ self.transport.gss_kex_used:
+ kexgss = self.transport.kexgss_ctxt
+ kexgss.set_username(self.username)
+ mic_token = kexgss.ssh_get_mic(self.transport.session_id)
+ m.add_string(mic_token)
elif self.auth_method == 'none':
pass
else:
@@ -278,6 +381,8 @@ class AuthHandler (object):
self._disconnect_no_more_auth()
return
self.auth_username = username
+ # check if GSS-API authentication is enabled
+ gss_auth = self.transport.server_object.enable_auth_gssapi()
if method == 'none':
result = self.transport.server_object.check_auth_none(username)
@@ -343,6 +448,98 @@ class AuthHandler (object):
# make interactive query instead of response
self._interactive_query(result)
return
+ elif method == "gssapi-with-mic" and gss_auth:
+ sshgss = GSSAuth(method)
+ # Read the number of OID mechanisms supported by the client.
+ # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's
+ # the only OID we support.
+ mechs = m.get_int()
+ # We can't accept more than one OID, so if the SSH client sends
+ # more than one, disconnect.
+ if mechs > 1:
+ self.transport._log(INFO,
+ 'Disconnect: Received more than one GSS-API OID mechanism')
+ self._disconnect_no_more_auth()
+ desired_mech = m.get_string()
+ mech_ok = sshgss.ssh_check_mech(desired_mech)
+ # if we don't support the mechanism, disconnect.
+ if not mech_ok:
+ self.transport._log(INFO,
+ 'Disconnect: Received an invalid GSS-API OID mechanism')
+ self._disconnect_no_more_auth()
+ # send the Kerberos V5 GSSAPI OID to the client
+ 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:
+ raise SSHException("Client asked to handle paket %s"
+ %MSG_NAMES[ptype])
+ # 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
+ if retval == 0:
+ # 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)
+ else:
+ result = AUTH_FAILED
+ elif method == "gssapi-keyex" and gss_auth:
+ mic_token = m.get_string()
+ sshgss = self.transport.kexgss_ctxt
+ if sshgss is None:
+ # If there is no valid context, we reject the authentication
+ result = AUTH_FAILED
+ self._send_auth_result(username, method, result)
+ try:
+ sshgss.ssh_check_mic(mic_token,
+ self.transport.session_id,
+ self.auth_username)
+ except Exception:
+ result = AUTH_FAILED
+ self._send_auth_result(username, method, result)
+ raise
+ if retval == 0:
+ result = AUTH_SUCCESSFUL
+ self.transport.server_object.check_auth_gssapi_keyex(username,
+ result)
+ else:
+ result = AUTH_FAILED
else:
result = self.transport.server_object.check_auth_none(username)
# okay, send result