Skip to content

Commit

Permalink
Implement RFC 6680 (Low-Level)
Browse files Browse the repository at this point in the history
This commit introduces optional support for RFC 6680 to
the low-level API.

Note that gss_display_name_ext is not tested, since
it is not implemented by MIT krb5.  Additionally,
in order to test the get/set/delete attribute methods,
you will have to install the demo greet plugin that
comes with krb5.  Otherwise, the tests will be skipped.

Part of #4

Also-Authored-By: Simo Sorce <simo@redhat.com>
  • Loading branch information
DirectXMan12 committed Mar 4, 2015
1 parent ee437ca commit 53f2de2
Show file tree
Hide file tree
Showing 7 changed files with 448 additions and 2 deletions.
6 changes: 6 additions & 0 deletions gssapi/raw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@
from gssapi.raw.ext_iov_mic import * # noqa
except ImportError:
pass

# optional RFC 6680 support
try:
from gssapi.raw.ext_rfc6680 import * # noqa
except ImportError:
pass
12 changes: 12 additions & 0 deletions gssapi/raw/ext_buffer_sets.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from gssapi.raw.cython_types cimport *

cdef extern from "python_gssapi.h":
ctypedef struct gss_buffer_set_desc:
size_t count
gss_buffer_desc *elements
ctypedef gss_buffer_set_desc* gss_buffer_set_t

gss_buffer_set_t GSS_C_NO_BUFFER_SET

OM_uint32 gss_release_buffer_set(OM_uint32 *min_stat,
gss_buffer_set_t *buffer_set) nogil
282 changes: 282 additions & 0 deletions gssapi/raw/ext_rfc6680.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
GSSAPI="BASE" # This ensures that a full module is generated by Cython

from gssapi.raw.cython_types cimport *
from gssapi.raw.ext_buffer_sets cimport *
from gssapi.raw.names cimport Name
from gssapi.raw.oids cimport OID
from gssapi.raw.cython_converters cimport c_make_oid

from gssapi.raw.misc import GSSError
from gssapi.raw.named_tuples import InquireNameResult, GetNameAttributeResult
from gssapi.raw import types as gsstypes

cdef extern from "gssapi/gssapi_ext.h":
OM_uint32 gss_display_name_ext(OM_uint32 *min_stat, gss_name_t name,
gss_OID name_type,
gss_buffer_t output_name) nogil

OM_uint32 gss_inquire_name(OM_uint32 *min_stat, gss_name_t name,
int *name_is_mn, gss_OID *mech_type,
gss_buffer_set_t *attrs) nogil

OM_uint32 gss_get_name_attribute(OM_uint32 *min_stat, gss_name_t name,
gss_buffer_t attr, int *authenticated,
int *complete, gss_buffer_t value,
gss_buffer_t display_value,
int *more) nogil

OM_uint32 gss_set_name_attribute(OM_uint32 *min_stat, gss_name_t name,
int complete, gss_buffer_t attr,
gss_buffer_t value) nogil

OM_uint32 gss_delete_name_attribute(OM_uint32 *min_stat, gss_name_t name,
gss_buffer_t attr) nogil

OM_uint32 gss_export_name_composite(OM_uint32 *min_stat, gss_name_t name,
gss_buffer_t exported_name) nogil

gss_OID GSS_C_NT_COMPOSITE_EXPORT


gsstypes.NameType.composite_export = c_make_oid(GSS_C_NT_COMPOSITE_EXPORT)


def display_name_ext(Name name not None, OID name_type not None):
"""
Display the given Name using the given name type OID
This method attempts to display the given Name using the syntax of
the given name type. If this is not possible, an appropriate error
will be raised.
Args:
name (Name): the name to display
name_type (OID): the name type (see NameType) to use to
display the given name
Returns:
bytes: the displayed name
Raises:
OperationUnavailableError: the given name could not be displayed
using the given name type
"""

# GSS_C_EMPTY_BUFFER
cdef gss_buffer_desc output_name = gss_buffer_desc(0, NULL)

cdef OM_uint32 maj_stat, min_stat

maj_stat = gss_display_name_ext(&min_stat, name.raw_name,
&name_type.raw_oid, &output_name)

if maj_stat == GSS_S_COMPLETE:
name_text = output_name.value[:output_name.length]
gss_release_buffer(&min_stat, &output_name)
return name_text
else:
raise GSSError(maj_stat, min_stat)


def inquire_name(Name name not None):
"""
Get information about a Name
This method retrives information about the given name, including
the set of attribute names for the given name, as well as whether or
not the name is a Mechanism Name. Additionally, if the given name is
a Mechanism Name, the associated mechansim is returned as well.
Args:
name (Name): the name about which to inquire
Returns:
InquireNameResult: the set of attribute names for the given name,
whether or not the name is a Mechanism Name, and potentially
the associated mechanism if it is a Mechanism Name
Raises:
GSSError
"""

cdef int name_is_mn = 0
cdef gss_OID mn_mech
cdef gss_buffer_set_t attr_names = GSS_C_NO_BUFFER_SET

cdef OM_uint32 maj_stat, min_stat

maj_stat = gss_inquire_name(&min_stat, name.raw_name, &name_is_mn,
&mn_mech, &attr_names)

cdef int i
cdef OID py_mech = None
if maj_stat == GSS_S_COMPLETE:
py_attr_names = []

if attr_names != GSS_C_NO_BUFFER_SET:
for i in range(attr_names.count):
attr_name = attr_names.elements[i]
py_attr_names.append(attr_name.value[:attr_name.length])

gss_release_buffer_set(&min_stat, &attr_names)

if name_is_mn:
py_mech = OID()
py_mech.raw_oid = mn_mech[0]

return InquireNameResult(py_attr_names, <bint>name_is_mn, py_mech)
else:
raise GSSError(maj_stat, min_stat)


def set_name_attribute(Name name not None, attr not None, value not None,
bint complete=False):
"""
Add a value to a Name attribute
This method adds a value to the given attribute on the given name.
Args:
name (Name): the Name on which to set the attribute
attr (bytes): the name of the attribute
value (bytes): the value to add
complete (bool): whether or not to mark this attribute's value
set as being "complete"
Raises:
OperationUnavailableError: the given attribute name is unknown
or could not be set
"""

cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
cdef gss_buffer_desc val_buff = gss_buffer_desc(len(value), value)

cdef OM_uint32 maj_stat, min_stat

maj_stat = gss_set_name_attribute(&min_stat, name.raw_name, complete,
&attr_buff, &val_buff)

if maj_stat != GSS_S_COMPLETE:
raise GSSError(maj_stat, min_stat)


def get_name_attribute(Name name not None, attr not None, more=None):
"""
Get the value(s) of a Name attribute
This method retrieves the value(s) of the given attribute
for the given Name.
Note that this functionality matches pseudo-API presented
in RFC 6680, not the C API (which uses a state variable and
multiple calls to retrieve multiple values).
Args:
name (Name): the Name from which to get the attribute
attr (bytes): the name of the attribute
Returns:
GetNameAttributeResult: the raw version of the value(s),
the human-readable version of the value(s), whether
or not the attribute was authenticated, and whether or
not the attribute's value set was marked as complete
Raises:
OperationUnavailableError: the given attribute is unknown
or unset
"""
cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)

cdef gss_buffer_desc val_buff = gss_buffer_desc(0, NULL)
cdef gss_buffer_desc displ_val_buff = gss_buffer_desc(0, NULL)
cdef int complete
cdef int authenticated

cdef int more_val = -1
py_vals = []
py_displ_vals = []

cdef OM_uint32 maj_stat, min_stat

while more_val != 0:
maj_stat = gss_get_name_attribute(&min_stat, name.raw_name,
&attr_buff,
&authenticated, &complete,
&val_buff, &displ_val_buff,
&more_val)

if maj_stat == GSS_S_COMPLETE:
py_vals.append(val_buff.value[:val_buff.length])
py_displ_vals.append(
displ_val_buff.value[:displ_val_buff.length])

gss_release_buffer(&min_stat, &val_buff)
gss_release_buffer(&min_stat, &displ_val_buff)
else:
raise GSSError(maj_stat, min_stat)

return GetNameAttributeResult(py_vals, py_displ_vals, <bint>authenticated,
<bint>complete)


def delete_name_attribute(Name name not None, attr not None):
"""
Remove an attribute from a Name
This method removes an attribute from a Name. This method may be
used before :func:`set_name_attribute` clear the values of an attribute
before setting a new value (making the latter method work like a 'set'
operation instead of an 'add' operation).
Note that the removal of certain attributes may not be allowed.
Args:
name (Name): the name to remove the attribute from
attr (bytes): the name of the attribute
Raises:
OperationUnavailableError
UnauthorizedError
"""

cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)

cdef OM_uint32 maj_stat, min_stat

maj_stat = gss_delete_name_attribute(&min_stat, name.raw_name,
&attr_buff)

if maj_stat != GSS_S_COMPLETE:
raise GSSError(maj_stat, min_stat)


def export_name_composite(Name name not None):
"""
Export a name, preserving attribute information
This method functions similarly to :func:`export_name`, except that
it preserves attribute information. The resulting bytes may be imported
using :func:`import_name` with the `NameType.composite_export` name type.
Args:
name (Name): the name to export
Returns:
bytes: the exported composite name
Raises:
GSSError
"""

cdef gss_buffer_desc res = gss_buffer_desc(0, NULL)

cdef OM_uint32 maj_stat, min_stat

maj_stat = gss_export_name_composite(&min_stat, name.raw_name, &res)

if maj_stat == GSS_S_COMPLETE:
py_res = res.value[:res.length]
gss_release_buffer(&min_stat, &res)
return py_res
else:
raise GSSError(maj_stat, min_stat)
9 changes: 9 additions & 0 deletions gssapi/raw/named_tuples.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,12 @@

IOVUnwrapResult = namedtuple('IOVUnwrapResult',
['encrypted', 'qop'])


InquireNameResult = namedtuple('InquireNameResult',
['attrs', 'is_mech_name', 'mech'])


GetNameAttributeResult = namedtuple('GetNamedAttributeResult',
['values', 'display_values',
'authenticated', 'complete'])
26 changes: 26 additions & 0 deletions gssapi/tests/_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from gssapi._utils import import_gssapi_extension
import os.path

try:
import commands
Expand Down Expand Up @@ -47,3 +48,28 @@ def ext_test(self, *args, **kwargs):
return ext_test

return make_ext_test


_KRB_PREFIX = None


def _requires_krb_plugin(plugin_type, plugin_name):
global _KRB_PREFIX
if _KRB_PREFIX is None:
_KRB_PREFIX = get_output("krb5-config --prefix")

plugin_path = os.path.join(_KRB_PREFIX, 'lib/krb5/plugins',
plugin_type, '%s.so' % plugin_name)

def make_krb_plugin_test(func):
def krb_plugin_test(self, *args, **kwargs):
if not os.path.exists(plugin_path):
self.skipTest("You do not have the GSSAPI {type}"
"plugin {name} installed".format(
type=plugin_type, name=plugin_name))
else:
func(self, *args, **kwargs)

return krb_plugin_test

return make_krb_plugin_test
Loading

0 comments on commit 53f2de2

Please sign in to comment.