Skip to content

Commit 0b1f1c7

Browse files
committed
Document SecurityContext class
This commit adds documentation to the SecurityContext class. Closes #7.
1 parent 336ffb1 commit 0b1f1c7

File tree

1 file changed

+226
-8
lines changed

1 file changed

+226
-8
lines changed

gssapi/sec_contexts.py

Lines changed: 226 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313

1414
@six.add_metaclass(_utils.CheckLastError)
1515
class SecurityContext(rsec_contexts.SecurityContext):
16+
"""A GSSAPI Security Context
17+
18+
This class represents a GSSAPI security context that may be used
19+
with and/or returned by other GSSAPI methods.
20+
21+
It inherits from the low-level GSSAPI
22+
:class:`~gssapi.raw.sec_contexts.SecurityContext` class,
23+
and thus may used with both low-level and high-level API methods.
24+
25+
This class may be pickled an unpickled.
26+
"""
27+
1628
def __new__(cls, base=None, token=None,
1729
name=None, creds=None, desired_lifetime=None, flags=None,
1830
mech_type=None, channel_bindings=None, usage=None):
@@ -25,6 +37,31 @@ def __new__(cls, base=None, token=None,
2537
def __init__(self, base=None, token=None,
2638
name=None, creds=None, desired_lifetime=None, flags=None,
2739
mech_type=None, channel_bindings=None, usage=None):
40+
"""
41+
The constructor creates a new security context, but does not begin
42+
the initiate or accept process.
43+
44+
If the `base` argument is used, an existing
45+
:class:`~gssapi.raw.sec_contexts.SecurityContext` object from
46+
the low-level API is converted into a high-level object.
47+
48+
If the `token` argument is passed, the security context is imported
49+
using the token.
50+
51+
Otherwise, a new security context is created.
52+
53+
If the `usage` argument is not passed, the constructor will attempt
54+
to detect what the appropriate usage is based on either the existing
55+
security context (if `base` or `token` are used) or the argument set.
56+
57+
For a security context of the `initiate` usage, the `name` argument
58+
must be used, and the `creds`, `mech_type`, `flags`,
59+
`desired_lifetime`, and `channel_bindings` arguments may be
60+
used as well.
61+
62+
For a security context of the `accept` usage, the `creds` and
63+
`channel_bindings` arguments may optionally be used.
64+
"""
2865

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

86123
def get_signature(self, message):
124+
"""Calculate the signature for a message
125+
126+
This method calculates the signature (called a MIC) for
127+
the given message, which may be then used with
128+
:meth:`verify_signature` to confirm the validity of the
129+
signature. This is useful if you wish to transmit the
130+
message signature and message in your own format.
131+
132+
Args:
133+
message (bytes): the input message
134+
135+
Returns:
136+
bytes: the message signature
137+
"""
138+
87139
# TODO(directxman12): check flags?
88140
return rmessage.get_mic(self, message)
89141

90142
def verify_signature(self, message, mic):
143+
"""Verify the signature for a message
144+
145+
This method verifies that a signature (generated by
146+
:meth:`get_signature` is valid for the given method.
147+
148+
If the signature is valid, the method will return.
149+
Otherwise, it will raise an error.
150+
151+
Args:
152+
message (bytes): the message
153+
mic (bytes): the signature to verify
154+
155+
Raises:
156+
BadMICError: the signature was not valid
157+
"""
158+
91159
return rmessage.verify_mic(self, message, mic)
92160

93161
def wrap(self, message, encrypt):
162+
"""Wrap a message, optionally with encryption
163+
164+
This method generates a signature and uses it to
165+
wrap the message, optionally encrypting it.
166+
167+
Args:
168+
message (bytes): the message to wrap
169+
encrypt (bool): whether or not to encrypt the message
170+
171+
Returns:
172+
WrapResult: the wrapped message and details about it
173+
(e.g. whether encryption was used succesfully)
174+
"""
175+
94176
return rmessage.wrap(self, message, encrypt)
95177

96178
def unwrap(self, message):
179+
"""Unwrap a wrapped message
180+
181+
This method unwraps/unencrypts a wrapped message,
182+
verifying the signature along the way.
183+
184+
Args:
185+
message (bytes): the message to unwrap/decrypt
186+
187+
Returns:
188+
UnwrapResult: the unwrapped message and details about it
189+
(e.g. wheter encryption was used)
190+
"""
191+
97192
return rmessage.unwrap(self, message)
98193

99194
def encrypt(self, message):
195+
"""Encrypt a message
196+
197+
This method wraps and encrypts a message, similarly to
198+
:meth:`wrap`. The difference is that encryption is always
199+
used, and the method will raise an exception if this is
200+
not possible. Additionally, this method simply returns
201+
the encrypted message directly.
202+
203+
Args:
204+
message (bytes): the message to encrypt
205+
206+
Returns:
207+
bytes: the encrypted message
208+
209+
Raises:
210+
EncryptionNotUsed: the encryption could not be used
211+
"""
212+
100213
res = self.wrap(message, encrypt=True)
101214

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

107220
def decrypt(self, message):
221+
"""Decrypt a message
222+
223+
This method decrypts and unwraps a message, verifying the signature
224+
along the way, similarly to :meth:`unwrap`. The difference is that
225+
this method will raise an exception if encryption was by the context
226+
and not used, and simply returns the decrypted message directly.
227+
228+
Args:
229+
message (bytes): the encrypted message
230+
231+
Returns:
232+
bytes: the decrypted message
233+
234+
Raises:
235+
EncryptionNotUsed: encryption was expected, but not used
236+
"""
237+
108238
res = self.unwrap(message)
109239

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

119249
def get_wrap_size_limit(self, desired_output_size,
120250
encrypted=True):
251+
"""Get the maximum message size for a given wrapped message size
252+
253+
This method calculates the maximum input message size for a given
254+
wrapped/encrypted message size.
255+
256+
Args:
257+
desired_output_size (int): the maximum output message size
258+
encrypted (bool): whether or not encryption should be taken
259+
into account
260+
261+
Returns:
262+
int: the maximum input message size
263+
"""
264+
121265
return rmessage.wrap_size_limit(self, desired_output_size,
122266
encrypted)
123267

124268
def process_token(self, token):
269+
"""Process an output token asynchronously
270+
271+
This method processes an output token even when the security context
272+
was not expecting it.
273+
274+
Args:
275+
token (bytes): the token to process
276+
"""
277+
125278
rsec_contexts.process_context_token(self, token)
126279

127280
def export(self):
281+
"""Export a security context
282+
283+
This method exports a security context, allowing it to be passed
284+
between processes.
285+
286+
Returns:
287+
bytes: the exported security context
288+
"""
289+
128290
return rsec_contexts.export_sec_context(self)
129291

130-
INQUIRE_ARGS = ('initiator_name', 'target_name', 'lifetime',
131-
'mech_type', 'flags', 'locally_init', 'complete')
292+
_INQUIRE_ARGS = ('initiator_name', 'target_name', 'lifetime',
293+
'mech_type', 'flags', 'locally_init', 'complete')
132294

133295
@_utils.check_last_err
134296
def _inquire(self, **kwargs):
297+
"""Inspect the security context for information
298+
299+
This method inspects the security context for information.
300+
301+
If no keyword arguments are passed, all available information
302+
is returned. Otherwise, only the keyword arguments that
303+
are passed and set to `True` are returned.
304+
305+
Args:
306+
initiator_name (bool): get the initiator name for this context
307+
target_name (bool): get the target name for this context
308+
lifetime (bool): get the remaining lifetime for this context
309+
mech_type (bool): get the mechanism used by this context
310+
flags (bool): get the flags set on this context
311+
locally_init (bool): get whether this context was locally initiated
312+
complete (bool): get whether negotiation on this context has
313+
been completed
314+
315+
Returns:
316+
InquireContextResult: the results of the inquiry, with unused
317+
fields set to None
318+
"""
135319
if not kwargs:
136320
default_val = True
137321
else:
138322
default_val = False
139323

140-
for arg in self.INQUIRE_ARGS:
324+
for arg in self._INQUIRE_ARGS:
141325
kwargs[arg] = kwargs.get(arg, default_val)
142326

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

162346
@property
163347
def lifetime(self):
348+
"""Get the amount of time for which the context remains valid"""
164349
return rsec_contexts.context_time(self)
165350

166-
initiator_name = _utils.inquire_property('initiator_name')
167-
target_name = _utils.inquire_property('target_name')
168-
mech_type = _utils.inquire_property('mech_type')
169-
actual_flags = _utils.inquire_property('flags')
170-
locally_initiated = _utils.inquire_property('locally_init')
351+
initiator_name = _utils.inquire_property(
352+
'initiator_name', 'Get the Name of the initiator of this context')
353+
target_name = _utils.inquire_property(
354+
'target_name', 'Get the Name of the target of this context')
355+
mech_type = _utils.inquire_property(
356+
'mech_type', 'Get the mechanism in use by this context')
357+
actual_flags = _utils.inquire_property(
358+
'flags', 'Get the flags set on this context')
359+
locally_initiated = _utils.inquire_property(
360+
'locally_init', 'Get whether this context was locally intiated')
171361

172362
@property
173363
@_utils.check_last_err
174364
def complete(self):
365+
"""Get whether negotiation for this context has been completed"""
175366
if self._started:
176367
return self._inquire(complete=True).complete
177368
else:
178369
return False
179370

180371
@_utils.catch_and_return_token
181372
def step(self, token=None):
373+
"""Perform a negotation step
374+
375+
This method performs a negotiation step based on the usage type
376+
of this context. If `__DEFER_STEP_ERRORS__` is set to true,
377+
this method will return a token, even when exceptions would be
378+
thrown. The generated exception will be thrown on the next
379+
method call or property lookup on the context.
380+
381+
This method should be used in a while loop, as such:
382+
383+
.. code-block:: python
384+
385+
input_token = None
386+
try:
387+
while not ctx.complete:
388+
output_token = ctx.step(input_token)
389+
input_token = send_and_receive(output_token)
390+
except GSSError as e:
391+
handle_the_issue()
392+
393+
Args:
394+
token (bytes): the input token from the other participant's step
395+
396+
Returns:
397+
bytes: the output token to send to the other participant
398+
"""
399+
182400
if self.usage == 'accept':
183401
return self._acceptor_step(token=token)
184402
else:

0 commit comments

Comments
 (0)