Skip to content

Commit

Permalink
Document SecurityContext class
Browse files Browse the repository at this point in the history
This commit adds documentation to the SecurityContext class.

Closes #7.
  • Loading branch information
DirectXMan12 committed Dec 17, 2014
1 parent d792728 commit 0e801a5
Showing 1 changed file with 226 additions and 8 deletions.
234 changes: 226 additions & 8 deletions gssapi/sec_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@

@six.add_metaclass(_utils.CheckLastError)
class SecurityContext(rsec_contexts.SecurityContext):
"""A GSSAPI Security Context
This class represents a GSSAPI security context that may be used
with and/or returned by other GSSAPI methods.
It inherits from the low-level GSSAPI
:class:`~gssapi.raw.sec_contexts.SecurityContext` class,
and thus may used with both low-level and high-level API methods.
This class may be pickled an unpickled.
"""

def __new__(cls, base=None, token=None,
name=None, creds=None, desired_lifetime=None, flags=None,
mech_type=None, channel_bindings=None, usage=None):
Expand All @@ -25,6 +37,31 @@ def __new__(cls, base=None, token=None,
def __init__(self, base=None, token=None,
name=None, creds=None, desired_lifetime=None, flags=None,
mech_type=None, channel_bindings=None, usage=None):
"""
The constructor creates a new security context, but does not begin
the initiate or accept process.
If the `base` argument is used, an existing
:class:`~gssapi.raw.sec_contexts.SecurityContext` object from
the low-level API is converted into a high-level object.
If the `token` argument is passed, the security context is imported
using the token.
Otherwise, a new security context is created.
If the `usage` argument is not passed, the constructor will attempt
to detect what the appropriate usage is based on either the existing
security context (if `base` or `token` are used) or the argument set.
For a security context of the `initiate` usage, the `name` argument
must be used, and the `creds`, `mech_type`, `flags`,
`desired_lifetime`, and `channel_bindings` arguments may be
used as well.
For a security context of the `accept` usage, the `creds` and
`channel_bindings` arguments may optionally be used.
"""

# NB(directxman12): _last_err must be set first
self._last_err = None
Expand Down Expand Up @@ -84,19 +121,95 @@ def __init__(self, base=None, token=None,
# TODO(directxman12): implement flag properties

def get_signature(self, message):
"""Calculate the signature for a message
This method calculates the signature (called a MIC) for
the given message, which may be then used with
:meth:`verify_signature` to confirm the validity of the
signature. This is useful if you wish to transmit the
message signature and message in your own format.
Args:
message (bytes): the input message
Returns:
bytes: the message signature
"""

# TODO(directxman12): check flags?
return rmessage.get_mic(self, message)

def verify_signature(self, message, mic):
"""Verify the signature for a message
This method verifies that a signature (generated by
:meth:`get_signature` is valid for the given method.
If the signature is valid, the method will return.
Otherwise, it will raise an error.
Args:
message (bytes): the message
mic (bytes): the signature to verify
Raises:
BadMICError: the signature was not valid
"""

return rmessage.verify_mic(self, message, mic)

def wrap(self, message, encrypt):
"""Wrap a message, optionally with encryption
This method generates a signature and uses it to
wrap the message, optionally encrypting it.
Args:
message (bytes): the message to wrap
encrypt (bool): whether or not to encrypt the message
Returns:
WrapResult: the wrapped message and details about it
(e.g. whether encryption was used succesfully)
"""

return rmessage.wrap(self, message, encrypt)

def unwrap(self, message):
"""Unwrap a wrapped message
This method unwraps/unencrypts a wrapped message,
verifying the signature along the way.
Args:
message (bytes): the message to unwrap/decrypt
Returns:
UnwrapResult: the unwrapped message and details about it
(e.g. wheter encryption was used)
"""

return rmessage.unwrap(self, message)

def encrypt(self, message):
"""Encrypt a message
This method wraps and encrypts a message, similarly to
:meth:`wrap`. The difference is that encryption is always
used, and the method will raise an exception if this is
not possible. Additionally, this method simply returns
the encrypted message directly.
Args:
message (bytes): the message to encrypt
Returns:
bytes: the encrypted message
Raises:
EncryptionNotUsed: the encryption could not be used
"""

res = self.wrap(message, encrypt=True)

if not res.encrypted:
Expand All @@ -105,6 +218,23 @@ def encrypt(self, message):
return res.message

def decrypt(self, message):
"""Decrypt a message
This method decrypts and unwraps a message, verifying the signature
along the way, similarly to :meth:`unwrap`. The difference is that
this method will raise an exception if encryption was by the context
and not used, and simply returns the decrypted message directly.
Args:
message (bytes): the encrypted message
Returns:
bytes: the decrypted message
Raises:
EncryptionNotUsed: encryption was expected, but not used
"""

res = self.unwrap(message)

if (not res.encrypted and
Expand All @@ -118,26 +248,80 @@ def decrypt(self, message):

def get_wrap_size_limit(self, desired_output_size,
encrypted=True):
"""Get the maximum message size for a given wrapped message size
This method calculates the maximum input message size for a given
wrapped/encrypted message size.
Args:
desired_output_size (int): the maximum output message size
encrypted (bool): whether or not encryption should be taken
into account
Returns:
int: the maximum input message size
"""

return rmessage.wrap_size_limit(self, desired_output_size,
encrypted)

def process_token(self, token):
"""Process an output token asynchronously
This method processes an output token even when the security context
was not expecting it.
Args:
token (bytes): the token to process
"""

rsec_contexts.process_context_token(self, token)

def export(self):
"""Export a security context
This method exports a security context, allowing it to be passed
between processes.
Returns:
bytes: the exported security context
"""

return rsec_contexts.export_sec_context(self)

INQUIRE_ARGS = ('initiator_name', 'target_name', 'lifetime',
'mech_type', 'flags', 'locally_init', 'complete')
_INQUIRE_ARGS = ('initiator_name', 'target_name', 'lifetime',
'mech_type', 'flags', 'locally_init', 'complete')

@_utils.check_last_err
def _inquire(self, **kwargs):
"""Inspect the security context for information
This method inspects the security context for information.
If no keyword arguments are passed, all available information
is returned. Otherwise, only the keyword arguments that
are passed and set to `True` are returned.
Args:
initiator_name (bool): get the initiator name for this context
target_name (bool): get the target name for this context
lifetime (bool): get the remaining lifetime for this context
mech_type (bool): get the mechanism used by this context
flags (bool): get the flags set on this context
locally_init (bool): get whether this context was locally initiated
complete (bool): get whether negotiation on this context has
been completed
Returns:
InquireContextResult: the results of the inquiry, with unused
fields set to None
"""
if not kwargs:
default_val = True
else:
default_val = False

for arg in self.INQUIRE_ARGS:
for arg in self._INQUIRE_ARGS:
kwargs[arg] = kwargs.get(arg, default_val)

res = rsec_contexts.inquire_context(self, **kwargs)
Expand All @@ -161,24 +345,58 @@ def _inquire(self, **kwargs):

@property
def lifetime(self):
"""Get the amount of time for which the context remains valid"""
return rsec_contexts.context_time(self)

initiator_name = _utils.inquire_property('initiator_name')
target_name = _utils.inquire_property('target_name')
mech_type = _utils.inquire_property('mech_type')
actual_flags = _utils.inquire_property('flags')
locally_initiated = _utils.inquire_property('locally_init')
initiator_name = _utils.inquire_property(
'initiator_name', 'Get the Name of the initiator of this context')
target_name = _utils.inquire_property(
'target_name', 'Get the Name of the target of this context')
mech_type = _utils.inquire_property(
'mech_type', 'Get the mechanism in use by this context')
actual_flags = _utils.inquire_property(
'flags', 'Get the flags set on this context')
locally_initiated = _utils.inquire_property(
'locally_init', 'Get whether this context was locally intiated')

@property
@_utils.check_last_err
def complete(self):
"""Get whether negotiation for this context has been completed"""
if self._started:
return self._inquire(complete=True).complete
else:
return False

@_utils.catch_and_return_token
def step(self, token=None):
"""Perform a negotation step
This method performs a negotiation step based on the usage type
of this context. If `__DEFER_STEP_ERRORS__` is set to true,
this method will return a token, even when exceptions would be
thrown. The generated exception will be thrown on the next
method call or property lookup on the context.
This method should be used in a while loop, as such:
.. code-block:: python
input_token = None
try:
while not ctx.complete:
output_token = ctx.step(input_token)
input_token = send_and_receive(output_token)
except GSSError as e:
handle_the_issue()
Args:
token (bytes): the input token from the other participant's step
Returns:
bytes: the output token to send to the other participant
"""

if self.usage == 'accept':
return self._acceptor_step(token=token)
else:
Expand Down

0 comments on commit 0e801a5

Please sign in to comment.