summaryrefslogtreecommitdiff
path: root/paramiko/ssh_gss.py
diff options
context:
space:
mode:
authorTanja Huthmacher <tanja.huthmacher@atos.net>2018-09-19 11:04:40 +0200
committerTanja Huthmacher <tanja.huthmacher@atos.net>2018-09-19 11:04:40 +0200
commit92c53775d0143547abba7de9f9d70417bbbef3c8 (patch)
tree389ccd2c7fdd34e7689591fb37eb11dea75b5cca /paramiko/ssh_gss.py
parent329aaf67b0d5391684dc0d32d76112a8d9dabae5 (diff)
downloadparamiko-92c53775d0143547abba7de9f9d70417bbbef3c8.tar.gz
Add compatibility with new "gssapi" package
Detect if "python-gssapi" or "gssapi" is installed at import time and use the appropriate API
Diffstat (limited to 'paramiko/ssh_gss.py')
-rw-r--r--paramiko/ssh_gss.py205
1 files changed, 192 insertions, 13 deletions
diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py
index eb8826e0..4c245471 100644
--- a/paramiko/ssh_gss.py
+++ b/paramiko/ssh_gss.py
@@ -47,12 +47,18 @@ from pyasn1.codec.der import encoder, decoder
#: :var str _API: Constraint for the used API
-_API = "MIT"
+_API = None
try:
import gssapi
-
- GSS_EXCEPTIONS = (gssapi.GSSException,)
+ if hasattr(gssapi, '__title__') and gssapi.__title__ == 'python-gssapi':
+ # old, unmaintained python-gssapi package
+ _API = "MIT" # keep this for compatibility
+ GSS_EXCEPTIONS = (gssapi.GSSException,)
+ else:
+ _API = "PYTHON-GSSAPI-NEW"
+ GSS_EXCEPTIONS = (gssapi.exceptions.GeneralError,
+ gssapi.raw.misc.GSSError,)
except (ImportError, OSError):
try:
import pywintypes
@@ -67,6 +73,7 @@ except (ImportError, OSError):
from paramiko.common import MSG_USERAUTH_REQUEST
from paramiko.ssh_exception import SSHException
+from paramiko._version import __version_info__
def GSSAuth(auth_method, gss_deleg_creds=True):
@@ -77,21 +84,24 @@ def GSSAuth(auth_method, gss_deleg_creds=True):
(gssapi-with-mic or gss-keyex)
:param bool gss_deleg_creds: Delegate client credentials or not.
We delegate credentials by default.
- :return: Either an `._SSH_GSSAPI` (Unix) object or an
- `_SSH_SSPI` (Windows) object
+ :return: Either an `._SSH_GSSAPI_OLD` or `._SSH_GSSAPI_NEW` (Unix)
+ object or an `_SSH_SSPI` (Windows) object
+ :rtype: Object
:raises: ``ImportError`` -- If no GSS-API / SSPI module could be imported.
:see: `RFC 4462 <http://www.ietf.org/rfc/rfc4462.txt>`_
- :note: Check for the available API and return either an `._SSH_GSSAPI`
- (MIT GSSAPI) object or an `._SSH_SSPI` (MS SSPI) object. If you
- get python-gssapi working on Windows, python-gssapi
- will be used and a `._SSH_GSSAPI` object will be returned.
+ :note: Check for the available API and return either an `._SSH_GSSAPI_OLD`
+ (MIT GSSAPI using python-gssapi package) object, an `._SSH_GSSAPI_NEW`
+ (MIT GSSAPI using gssapi package) object
+ or an `._SSH_SSPI` (MS SSPI) object.
If there is no supported API available,
``None`` will be returned.
"""
if _API == "MIT":
- return _SSH_GSSAPI(auth_method, gss_deleg_creds)
+ return _SSH_GSSAPI_OLD(auth_method, gss_deleg_creds)
+ elif _API == "PYTHON-GSSAPI-NEW":
+ return _SSH_GSSAPI_NEW(auth_method, gss_deleg_creds)
elif _API == "SSPI" and os.name == "nt":
return _SSH_SSPI(auth_method, gss_deleg_creds)
else:
@@ -100,7 +110,7 @@ def GSSAuth(auth_method, gss_deleg_creds=True):
class _SSH_GSSAuth(object):
"""
- Contains the shared variables and methods of `._SSH_GSSAPI` and
+ Contains the shared variables and methods of `._SSH_GSSAPI_*` and
`._SSH_SSPI`.
"""
@@ -222,9 +232,10 @@ class _SSH_GSSAuth(object):
return mic
-class _SSH_GSSAPI(_SSH_GSSAuth):
+class _SSH_GSSAPI_OLD(_SSH_GSSAuth):
"""
- Implementation of the GSS-API MIT Kerberos Authentication for SSH2.
+ Implementation of the GSS-API MIT Kerberos Authentication for SSH2,
+ using the older (unmaintained) python-gssapi package.
:see: `.GSSAuth`
"""
@@ -399,6 +410,174 @@ class _SSH_GSSAPI(_SSH_GSSAuth):
raise NotImplementedError
+if __version_info__[0] == 2 and __version_info__[0] <= 4:
+ # provide the old name for strict backward compatibility
+ _SSH_GSSAPI = _SSH_GSSAPI_OLD
+
+
+class _SSH_GSSAPI_NEW(_SSH_GSSAuth):
+ """
+ Implementation of the GSS-API MIT Kerberos Authentication for SSH2,
+ using the newer, currently maintained gssapi package.
+
+ :see: `.GSSAuth`
+ """
+ def __init__(self, auth_method, gss_deleg_creds):
+ """
+ :param str auth_method: The name of the SSH authentication mechanism
+ (gssapi-with-mic or gss-keyex)
+ :param bool gss_deleg_creds: Delegate client credentials or not
+ """
+ _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)
+
+ if self._gss_deleg_creds:
+ self._gss_flags = (gssapi.RequirementFlag.protection_ready,
+ gssapi.RequirementFlag.integrity,
+ gssapi.RequirementFlag.mutual_authentication,
+ gssapi.RequirementFlag.delegate_to_peer)
+ else:
+ self._gss_flags = (gssapi.RequirementFlag.protection_ready,
+ gssapi.RequirementFlag.integrity,
+ gssapi.RequirementFlag.mutual_authentication)
+
+ def ssh_init_sec_context(self, target, desired_mech=None,
+ username=None, recv_token=None):
+ """
+ Initialize a GSS-API context.
+
+ :param str username: The name of the user who attempts to login
+ :param str target: The hostname of the target to connect to
+ :param str desired_mech: The negotiated GSS-API mechanism
+ ("pseudo negotiated" mechanism, because we
+ support just the krb5 mechanism :-))
+ :param str recv_token: The GSS-API token received from the Server
+ :raise SSHException: Is raised if the desired mechanism of the client
+ is not supported
+ :raise gssapi.exceptions.GSSError: if there is an error signaled by the
+ GSS-API implementation
+ :return: A ``String`` if the GSS-API has returned a token or ``None`` if
+ no token was returned
+ :rtype: String or None
+ """
+ self._username = username
+ self._gss_host = target
+ targ_name = gssapi.Name("host@" + self._gss_host,
+ name_type=gssapi.NameType.hostbased_service)
+ if desired_mech is not None:
+ mech, __ = decoder.decode(desired_mech)
+ if mech.__str__() != self._krb5_mech:
+ raise SSHException("Unsupported mechanism OID.")
+ krb5_mech = gssapi.MechType.kerberos
+ token = None
+ if recv_token is None:
+ self._gss_ctxt = gssapi.SecurityContext(name=targ_name,
+ flags=self._gss_flags,
+ mech=krb5_mech,
+ usage='initiate')
+ token = self._gss_ctxt.step(token)
+ else:
+ token = self._gss_ctxt.step(recv_token)
+ self._gss_ctxt_status = self._gss_ctxt.complete
+ return token
+
+ def ssh_get_mic(self, session_id, gss_kex=False):
+ """
+ Create the MIC token for a SSH2 message.
+
+ :param str session_id: The SSH session ID
+ :param bool gss_kex: Generate the MIC for GSS-API Key Exchange or not
+ :return: gssapi-with-mic:
+ Returns the MIC token from GSS-API for the message we created
+ with ``_ssh_build_mic``.
+ gssapi-keyex:
+ Returns the MIC token from GSS-API with the SSH session ID as
+ message.
+ :rtype: String
+ :see: `._ssh_build_mic`
+ """
+ self._session_id = session_id
+ if not gss_kex:
+ mic_field = self._ssh_build_mic(self._session_id,
+ self._username,
+ self._service,
+ self._auth_method)
+ mic_token = self._gss_ctxt.get_signature(mic_field)
+ else:
+ # for key exchange with gssapi-keyex
+ mic_token = self._gss_srv_ctxt.get_signature(self._session_id)
+ return mic_token
+
+ def ssh_accept_sec_context(self, hostname, recv_token, username=None):
+ """
+ Accept a GSS-API context (server mode).
+
+ :param str hostname: The servers hostname
+ :param str username: The name of the user who attempts to login
+ :param str recv_token: The GSS-API Token received from the server,
+ if it's not the initial call.
+ :return: A ``String`` if the GSS-API has returned a token or ``None``
+ if no token was returned
+ :rtype: String or None
+ """
+ # hostname and username are not required for GSSAPI, but for SSPI
+ self._gss_host = hostname
+ self._username = username
+ if self._gss_srv_ctxt is None:
+ self._gss_srv_ctxt = gssapi.SecurityContext(usage='accept')
+ token = self._gss_srv_ctxt.step(recv_token)
+ self._gss_srv_ctxt_status = self._gss_srv_ctxt.complete
+ return token
+
+ def ssh_check_mic(self, mic_token, session_id, username=None):
+ """
+ Verify the MIC token for a SSH2 message.
+
+ :param str mic_token: The MIC token received from the client
+ :param str session_id: The SSH session ID
+ :param str username: The name of the user who attempts to login
+ :return: None if the MIC check was successful
+ :raises gssapi.exceptions.GSSError: if the MIC check failed
+ """
+ self._session_id = session_id
+ self._username = username
+ if self._username is not None:
+ # server mode
+ mic_field = self._ssh_build_mic(self._session_id,
+ self._username,
+ self._service,
+ self._auth_method)
+ self._gss_srv_ctxt.verify_signature(mic_field, mic_token)
+ else:
+ # for key exchange with gssapi-keyex
+ # client mode
+ self._gss_ctxt.verify_signature(self._session_id,
+ mic_token)
+
+ @property
+ def credentials_delegated(self):
+ """
+ Checks if credentials are delegated (server mode).
+
+ :return: ``True`` if credentials are delegated, otherwise ``False``
+ :rtype: bool
+ """
+ if self._gss_srv_ctxt.delegated_creds is not None:
+ return True
+ return False
+
+ def save_client_creds(self, client_token):
+ """
+ Save the Client token in a file. This is used by the SSH server
+ to store the client credentials if credentials are delegated
+ (server mode).
+
+ :param str client_token: The GSS-API token received form the client
+ :raise NotImplementedError: Credential delegation is currently not
+ supported in server mode
+ """
+ raise NotImplementedError
+
+
class _SSH_SSPI(_SSH_GSSAuth):
"""
Implementation of the Microsoft SSPI Kerberos Authentication for SSH2.