Source code for gssapi.client

from __future__ import print_function
import gssapi.base as gss
import struct
import sys
from gssapi.type_wrappers import GSSName

[docs]def debug(p, v): print("{0}: {1}".format(p.upper(), v), file=sys.stderr)
[docs]class GSSClientError(Exception): """ GSS Client Error This Exception represents an error which occured when executing the GSS Client code (as opposed to :class:`GSSError` s, which are errors which occured directly in the GSSAPI C code). """ pass
[docs]class BasicGSSClient(object): """ Basic GSS Client This class implements all functionality needed to initialize a basic GSS connection and send/receive encrypted or signed messages. .. warning:: All methods in this class can potentially raise :class:`gssapi.base.GSSError` .. attribute:: service_principal The service principal to which we are connecting (as a :class:`gssapi.type_wrappers.GSSName`) .. attribute:: ctx Type: Capsule The internal GSS context object .. attribute:: token Type: bytes The last returned token from one of the token-manipulation methods .. attribute:: ttl Type: int >= 0 The desired time-to-live for the GSS context object .. attribute:: last_ttl Type: int > 0 The actual amount of time for which the current GSS context object will be valid .. attribute:: qop Type: int > 0 or None The current Quality of Protection being used in the encryption/decryption process (set this to the desired QoP, or None for default, to attempt to use that QoP) .. attribute:: services Type: [:class:`gssapi.base.RequirementFlag`] The flags to use when creating the GSS context .. attribute:: channel_bindings Type: TBD or None .. warning:: Not Currently Implemented .. attribute:: mech_type Type: Capsule or None Represents the desired mechanism type to be used (None uses the default type). .. seealso:: Function :func:`resolveMechType` """ def __init__(self, principal, dbg=debug, security_type='encrypted', max_msg_size=None): """ Creates a new BasicGSSClient :param str principal: the service principal to which to connect (automatically converted to a :class:`gssapi.type_wrappers.GSSName` :param dbg: a method for printing debug messages (not currently used) :type dbg: function(title, message) :param security_type: the level of security to use :type security_type: str containing enc(crypted)/conf(idential), integ(rity) or any, or just None :param max_msg_size: the maximum message size for encryption/decryption :type max_msg_zie: int > 0 or None (for default) """ self.debug = dbg self.service_principal = GSSName(principal) self.ctx = None self.token = None self.ttl = 0 self.last_ttl = None self.qop = None self.channel_bindings = None self.mech_type = None self.services = None if security_type[0:5] == 'integ': self.security_type = gss.RequirementFlag.integrity elif security_type[0:4] == 'conf' or security_type[0:3] == 'enc': self.security_type = gss.RequirementFlag.confidentiality elif security_type == 'any': self.security_type = None else: self.security_type = 0
[docs] def resolveMechType(self, mt): """ Sets the current mechanims type This method converts a :class:`gssapi.base.MechanismType` into a capsule object usable by internal methods, and then sets :attr:`mech_type` to the resulting capsule :param mt: the desired mechanism type :type mt: :class:`gssapi.base.MechanismType` """ self.mech_type = gss.getMechanismType(mt)
[docs] def createDefaultToken(self): """ Initializes a default token and security context This method gets and returns a default token, and initializes the corresponding security context :rtype: bytes :returns: the token created in the process of initializing the security context """ self.ctx, _, _, self.token, self.last_ttl, _ = gss.initSecContext(self.service_principal.capsule, services=self.services, channel_bindings=self.channel_bindings, mech_type=self.mech_type, time=self.ttl) return self.token
[docs] def processServerToken(self, server_tok): """ Processes a server token, and updates the security context This method processes a server token, updates the internal security context, and returns the new resulting token. :param bytes server_tok: the token sent from the server :rtype: bytes :returns: the token resulting from updating the security context """ self.ctx, _, _, self.token, self.last_ttl, _ = gss.initSecContext(self.service_principal.capsule, context=self.ctx, input_token=server_tok, services=self.services, channel_bindings=self.channel_bindings, mech_type=self.mech_type, time=self.ttl) return self.token
[docs] def encrypt(self, msg): """ Encrypts a message This method encrypts a message according to the current QoP (:attr:`qop`) and security level :param str msg: the message to be encrypted :rtype: bytes :returns: the encrypted form of the message :except GSSClientError: if the requested security level could not be used """ if self.security_type == gss.RequirementFlag.integrity: gss.wrap(self.ctx, msg, False, self.qop)[0] elif self.security_type == gss.RequirementFlag.confidentiality: res, used = gss.wrap(self.ctx, msg, True, self.qop) if not used: raise GSSClientError('User requested encryption, but it was not used!') return res else: return msg
[docs] def decrypt(self, msg): """ Decrypts a message This method decrypts a message encrypted by the server. :param bytes msg: the message to be decrypted :rtype: str :returns: the decrypted message :except GSSClientError: if encryption was requested but not used, or if the QoP failed to meet our standards """ if self.security_type is not None and self.security_type != 0: res, used, qop = gss.unwrap(self.ctx, msg) if not used and self.security_type == gss.RequirementFlag.confidentiality: raise GSSClientError('User requested encryption, but the server sent an unencrypted message!') if self.qop is None: self.qop = qop elif qop < self.qop: raise GSSClientError('Server used a lower quality of protection than we expected!') return res else: return msg
def __del__(self): if self.ctx is not None: gss.deleteSecContext(self.ctx)
[docs]class SASLGSSClientError(GSSClientError): """ SASL GSS Client Error This Exception represents an error which occured when executing the SASL GSS Client helper code (as opposed to :class:`GSSError`s, which are errors which occured directly in the GSSAPI C code). """ pass
[docs]class BasicSASLGSSClient(BasicGSSClient): """ A helper for using the SASL GSSAPI mechanism This class contains helper code to support implementing the SASL GSSAPI mechanism using PyGSSAPI. .. attribute:: user_principal The username to use in the authentication process .. warning:: Unlike :attr:`service_principal`, this is just a string, not a :class:`gssapi.type_wrappers.GSSName` """ def __init__(self, username, service_principal, max_msg_size=None, *args, **kwargs): """ Creates a new BasicSASLGSSClient This method creates a new BasicSASLGSSClient with the given username. All other parameters are used as in :class:`BasicGSSClient`. All relevant attributes are set according to the SASL GSSAPI RFC (http://tools.ietf.org/html/rfc4752). :param str username: the user principal with which to authenticate """ self.user_principal = username self.max_msg_size = max_msg_size super(BasicSASLGSSClient, self).__init__(service_principal, *args, **kwargs) self.channel_bindings = None self.resolveMechType(gss.MechType.kerberos) if (self.services is None): self.services = [] if (self.security_type == gss.RequirementFlag.confidentiality): self.services.append(self.security_type) self.services.append(gss.RequirementFlag.integrity) if (self.security_type != 0): self.services.extend([gss.RequirementFlag.mutual_authentication, gss.RequirementFlag.out_of_sequence_detection]) self.INV_SEC_LAYER_MASKS = {v:k for k, v in self.SEC_LAYER_MASKS.items()}
[docs] def step1(self): """ Creates a default token This method is step 1 in the SASL process, and creates a default token :rtype: bytes :returns: a default token to send to the server """ return self.createDefaultToken()
[docs] def step2(self, server_tok): """ Processes a server token This method is step 2 in the SASL process, and processes a server token :param bytes server_tok: the token returned from the server :rtype: bytes :returns: a token or empty string to be sent to the server """ return self.processServerToken(server_tok)
SEC_LAYER_MASKS = { 0: 1, gss.RequirementFlag.integrity: 2, gss.RequirementFlag.confidentiality: 4 } INV_SEC_LAYER_MASKS = None
[docs] def step3(self, tok): """ Deals with SSF This method deals with negotiating SSF (the security level) and max message size, setting the max message size appropriately :param bytes tok: the wrapped message sent from the server :rtype: bytes :returns: a wrapped message to be sent to the server declaring our security level and max message size """ unwrapped_tok = gss.unwrap(self.ctx, msg)[0] # we don't care out security for this sec_layers_supported_raw = ord(unwrapped_tok[0]) max_server_msg_size_raw = '\x00' + unwrapped_tok[1:4] max_server_msg_size = struct.unpack('!L', max_server_msg_size_raw)[0] if self.max_msg_size is None or self.max_msg_size > max_server_msg_size: self.max_msg_size = max_server_msg_size sec_layers_supported = [] for name, mask in self.SEC_LAYER_MASKS.items(): if sec_layers_supported_raw & mask > 0: sec_layers_supported.append(name) sec_layer_choice = 0 if self.security_type is None: # None means any for mask in self.SEC_LAYER_MASKS.values(): if mask & sec_layers_supported_raw > sec_layer_choice: sec_layer_choice = mask elif self.security_type in sec_layers_supported: sec_layer_choice = self.SEC_LAYER_MASKS[self.security_type] else: raise SASLGSSClientError('Server is unable to accomodate our security level!') if self.security_layer is None: self.security_layer = INV_SEC_LAYER_MASKS[sec_layer_choice] resp = chr(sec_layer_choice) + struct.pack('!L', self.max_msg_size)[0:3] + self.user_principal return gss.wrap(self.ctx, resp, False, self.qop)[0] # again, we don't care about our selected security type for this one