Skip to content

Commit 03863fd

Browse files
committed
Implement RFC 6680 (Low-Level)
This commit introduces optional support for RFC 6680 to the low-level API. The `get_name_attribute` and `set_name_attribute` methods adhere to the pseudo-API (which uses a single call to get/set multiple values on a single attribute) instead of the C bindings (which require the use of a state variable to get multiple values, and require multiple calls to add multiple values to a single attribute). 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 <[email protected]>
1 parent 1540fa2 commit 03863fd

8 files changed

+504
-1
lines changed

gssapi/raw/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,10 @@
6464
from gssapi.raw.ext_iov_mic import * # noqa
6565
except ImportError:
6666
pass
67+
68+
# optional RFC 6680 support
69+
try:
70+
from gssapi.raw.ext_rfc6680 import * # noqa
71+
from gssapi.raw.ext_rfc6680_comp_oid import * # noqa
72+
except ImportError:
73+
pass

gssapi/raw/ext_buffer_sets.pxd

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from gssapi.raw.cython_types cimport *
2+
3+
cdef extern from "python_gssapi.h":
4+
ctypedef struct gss_buffer_set_desc:
5+
size_t count
6+
gss_buffer_desc *elements
7+
ctypedef gss_buffer_set_desc* gss_buffer_set_t
8+
9+
gss_buffer_set_t GSS_C_NO_BUFFER_SET
10+
11+
OM_uint32 gss_release_buffer_set(OM_uint32 *min_stat,
12+
gss_buffer_set_t *buffer_set) nogil

gssapi/raw/ext_rfc6680.pyx

+305
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
GSSAPI="BASE" # This ensures that a full module is generated by Cython
2+
3+
from gssapi.raw.cython_types cimport *
4+
from gssapi.raw.ext_buffer_sets cimport *
5+
from gssapi.raw.names cimport Name
6+
from gssapi.raw.oids cimport OID
7+
8+
from gssapi.raw.misc import GSSError
9+
from gssapi.raw.named_tuples import InquireNameResult, GetNameAttributeResult
10+
11+
cdef extern from "gssapi/gssapi_ext.h":
12+
OM_uint32 gss_display_name_ext(OM_uint32 *min_stat, gss_name_t name,
13+
gss_OID name_type,
14+
gss_buffer_t output_name) nogil
15+
16+
OM_uint32 gss_inquire_name(OM_uint32 *min_stat, gss_name_t name,
17+
int *name_is_mn, gss_OID *mech_type,
18+
gss_buffer_set_t *attrs) nogil
19+
20+
OM_uint32 gss_get_name_attribute(OM_uint32 *min_stat, gss_name_t name,
21+
gss_buffer_t attr, int *authenticated,
22+
int *complete, gss_buffer_t value,
23+
gss_buffer_t display_value,
24+
int *more) nogil
25+
26+
OM_uint32 gss_set_name_attribute(OM_uint32 *min_stat, gss_name_t name,
27+
int complete, gss_buffer_t attr,
28+
gss_buffer_t value) nogil
29+
30+
OM_uint32 gss_delete_name_attribute(OM_uint32 *min_stat, gss_name_t name,
31+
gss_buffer_t attr) nogil
32+
33+
OM_uint32 gss_export_name_composite(OM_uint32 *min_stat, gss_name_t name,
34+
gss_buffer_t exported_name) nogil
35+
36+
# GSS_C_NT_COMPOSITE_EXPORT lives in ext_rfc6680_comp_oid.pyx
37+
38+
39+
def display_name_ext(Name name not None, OID name_type not None):
40+
"""
41+
Display the given Name using the given name type OID
42+
43+
This method attempts to display the given Name using the syntax of
44+
the given name type. If this is not possible, an appropriate error
45+
will be raised.
46+
47+
Args:
48+
name (Name): the name to display
49+
name_type (OID): the name type (see NameType) to use to
50+
display the given name
51+
52+
Returns:
53+
bytes: the displayed name
54+
55+
Raises:
56+
OperationUnavailableError: the given name could not be displayed
57+
using the given name type
58+
"""
59+
60+
# GSS_C_EMPTY_BUFFER
61+
cdef gss_buffer_desc output_name = gss_buffer_desc(0, NULL)
62+
63+
cdef OM_uint32 maj_stat, min_stat
64+
65+
maj_stat = gss_display_name_ext(&min_stat, name.raw_name,
66+
&name_type.raw_oid, &output_name)
67+
68+
if maj_stat == GSS_S_COMPLETE:
69+
name_text = output_name.value[:output_name.length]
70+
gss_release_buffer(&min_stat, &output_name)
71+
return name_text
72+
else:
73+
raise GSSError(maj_stat, min_stat)
74+
75+
76+
def inquire_name(Name name not None, mech_name=True, attrs=True):
77+
"""
78+
Get information about a Name
79+
80+
This method retrives information about the given name, including
81+
the set of attribute names for the given name, as well as whether or
82+
not the name is a Mechanism Name. Additionally, if the given name is
83+
a Mechanism Name, the associated mechansim is returned as well.
84+
85+
Args:
86+
name (Name): the name about which to inquire
87+
mech_name (bool): whether or not to retrieve if this name
88+
is a mech_name (and the associate mechanism)
89+
attrs (bool): whether or not to retrieve the attribute name list
90+
91+
Returns:
92+
InquireNameResult: the set of attribute names for the given name,
93+
whether or not the name is a Mechanism Name, and potentially
94+
the associated mechanism if it is a Mechanism Name
95+
96+
Raises:
97+
GSSError
98+
"""
99+
100+
cdef int *name_is_mn_ptr = NULL
101+
cdef gss_OID *mn_mech_ptr = NULL
102+
cdef gss_buffer_set_t *attr_names_ptr = NULL
103+
104+
cdef gss_buffer_set_t attr_names = GSS_C_NO_BUFFER_SET
105+
if attrs:
106+
attr_names_ptr = &attr_names
107+
108+
cdef int name_is_mn = 0
109+
cdef gss_OID mn_mech
110+
if mech_name:
111+
name_is_mn_ptr = &name_is_mn
112+
mn_mech_ptr = &mn_mech
113+
114+
cdef OM_uint32 maj_stat, min_stat
115+
116+
maj_stat = gss_inquire_name(&min_stat, name.raw_name, name_is_mn_ptr,
117+
mn_mech_ptr, attr_names_ptr)
118+
119+
cdef int i
120+
cdef OID py_mech = None
121+
if maj_stat == GSS_S_COMPLETE:
122+
py_attr_names = []
123+
124+
if attr_names != GSS_C_NO_BUFFER_SET:
125+
for i in range(attr_names.count):
126+
attr_name = attr_names.elements[i]
127+
py_attr_names.append(attr_name.value[:attr_name.length])
128+
129+
gss_release_buffer_set(&min_stat, &attr_names)
130+
131+
if name_is_mn:
132+
py_mech = OID()
133+
py_mech.raw_oid = mn_mech[0]
134+
135+
return InquireNameResult(py_attr_names, <bint>name_is_mn, py_mech)
136+
else:
137+
raise GSSError(maj_stat, min_stat)
138+
139+
140+
def set_name_attribute(Name name not None, attr not None, value not None,
141+
bint complete=False):
142+
"""
143+
Set the value(s) of a Name attribute
144+
145+
This method sets the value(s) of the given attribute on the given name.
146+
147+
Note that this functionality more closely matches the pseudo-API
148+
presented in RFC 6680, not the C API (which uses multiple calls to
149+
add multiple values). However, multiple calls to this method will
150+
continue adding values, so :func:`delete_name_attribute` must be
151+
used in between calls to "clear" the values.
152+
153+
Args:
154+
name (Name): the Name on which to set the attribute
155+
attr (bytes): the name of the attribute
156+
value (list): a list of bytes objects to use as the value(s)
157+
complete (bool): whether or not to mark this attribute's value
158+
set as being "complete"
159+
160+
Raises:
161+
OperationUnavailableError: the given attribute name is unknown
162+
or could not be set
163+
"""
164+
165+
cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
166+
cdef gss_buffer_desc val_buff
167+
168+
cdef OM_uint32 maj_stat, min_stat
169+
170+
cdef size_t value_len = len(value)
171+
cdef size_t i
172+
for val in value:
173+
val_buff = gss_buffer_desc(len(val), val)
174+
i += 1
175+
if i == value_len:
176+
maj_stat = gss_set_name_attribute(&min_stat, name.raw_name,
177+
complete, &attr_buff, &val_buff)
178+
else:
179+
maj_stat = gss_set_name_attribute(&min_stat, name.raw_name, 0,
180+
&attr_buff, &val_buff)
181+
182+
if maj_stat != GSS_S_COMPLETE:
183+
raise GSSError(maj_stat, min_stat)
184+
185+
186+
def get_name_attribute(Name name not None, attr not None, more=None):
187+
"""
188+
Get the value(s) of a Name attribute
189+
190+
This method retrieves the value(s) of the given attribute
191+
for the given Name.
192+
193+
Note that this functionality matches pseudo-API presented
194+
in RFC 6680, not the C API (which uses a state variable and
195+
multiple calls to retrieve multiple values).
196+
197+
Args:
198+
name (Name): the Name from which to get the attribute
199+
attr (bytes): the name of the attribute
200+
201+
Returns:
202+
GetNameAttributeResult: the raw version of the value(s),
203+
the human-readable version of the value(s), whether
204+
or not the attribute was authenticated, and whether or
205+
not the attribute's value set was marked as complete
206+
207+
Raises:
208+
OperationUnavailableError: the given attribute is unknown
209+
or unset
210+
"""
211+
cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
212+
213+
cdef gss_buffer_desc val_buff = gss_buffer_desc(0, NULL)
214+
cdef gss_buffer_desc displ_val_buff = gss_buffer_desc(0, NULL)
215+
cdef int complete
216+
cdef int authenticated
217+
218+
cdef int more_val = -1
219+
py_vals = []
220+
py_displ_vals = []
221+
222+
cdef OM_uint32 maj_stat, min_stat
223+
224+
while more_val != 0:
225+
maj_stat = gss_get_name_attribute(&min_stat, name.raw_name,
226+
&attr_buff,
227+
&authenticated, &complete,
228+
&val_buff, &displ_val_buff,
229+
&more_val)
230+
231+
if maj_stat == GSS_S_COMPLETE:
232+
py_vals.append(val_buff.value[:val_buff.length])
233+
py_displ_vals.append(
234+
displ_val_buff.value[:displ_val_buff.length])
235+
236+
gss_release_buffer(&min_stat, &val_buff)
237+
gss_release_buffer(&min_stat, &displ_val_buff)
238+
else:
239+
raise GSSError(maj_stat, min_stat)
240+
241+
return GetNameAttributeResult(py_vals, py_displ_vals, <bint>authenticated,
242+
<bint>complete)
243+
244+
245+
def delete_name_attribute(Name name not None, attr not None):
246+
"""
247+
Remove an attribute from a Name
248+
249+
This method removes an attribute from a Name. This method may be
250+
used before :func:`set_name_attribute` clear the values of an attribute
251+
before setting a new value (making the latter method work like a 'set'
252+
operation instead of an 'add' operation).
253+
254+
Note that the removal of certain attributes may not be allowed.
255+
256+
Args:
257+
name (Name): the name to remove the attribute from
258+
attr (bytes): the name of the attribute
259+
260+
Raises:
261+
OperationUnavailableError
262+
UnauthorizedError
263+
"""
264+
265+
cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
266+
267+
cdef OM_uint32 maj_stat, min_stat
268+
269+
maj_stat = gss_delete_name_attribute(&min_stat, name.raw_name,
270+
&attr_buff)
271+
272+
if maj_stat != GSS_S_COMPLETE:
273+
raise GSSError(maj_stat, min_stat)
274+
275+
276+
def export_name_composite(Name name not None):
277+
"""
278+
Export a name, preserving attribute information
279+
280+
This method functions similarly to :func:`export_name`, except that
281+
it preserves attribute information. The resulting bytes may be imported
282+
using :func:`import_name` with the `NameType.composite_export` name type.
283+
284+
Args:
285+
name (Name): the name to export
286+
287+
Returns:
288+
bytes: the exported composite name
289+
290+
Raises:
291+
GSSError
292+
"""
293+
294+
cdef gss_buffer_desc res = gss_buffer_desc(0, NULL)
295+
296+
cdef OM_uint32 maj_stat, min_stat
297+
298+
maj_stat = gss_export_name_composite(&min_stat, name.raw_name, &res)
299+
300+
if maj_stat == GSS_S_COMPLETE:
301+
py_res = res.value[:res.length]
302+
gss_release_buffer(&min_stat, &res)
303+
return py_res
304+
else:
305+
raise GSSError(maj_stat, min_stat)

gssapi/raw/ext_rfc6680_comp_oid.pyx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
GSSAPI="BASE" # This ensures that a full module is generated by Cython
2+
3+
from gssapi.raw.cython_types cimport gss_OID
4+
from gssapi.raw.cython_converters cimport c_make_oid
5+
6+
from gssapi.raw import types as gsstypes
7+
8+
9+
# NB(directxman12): this is placed in separate file since the
10+
# GSS_C_NT_COMPOSITE_EXPORT constant didn't appear in MIT
11+
# krb5 until 1.11. However, due to the way that support was
12+
# written for composite tokens, simply using GSS_C_NT_EXPORT_NAME
13+
# will work in prior version which contain support for RFC 6680
14+
cdef extern from "python_gssapi_ext.h":
15+
gss_OID GSS_C_NT_COMPOSITE_EXPORT
16+
17+
18+
gsstypes.NameType.composite_export = c_make_oid(GSS_C_NT_COMPOSITE_EXPORT)

gssapi/raw/named_tuples.py

+9
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,12 @@
5555

5656
IOVUnwrapResult = namedtuple('IOVUnwrapResult',
5757
['encrypted', 'qop'])
58+
59+
60+
InquireNameResult = namedtuple('InquireNameResult',
61+
['attrs', 'is_mech_name', 'mech'])
62+
63+
64+
GetNameAttributeResult = namedtuple('GetNamedAttributeResult',
65+
['values', 'display_values',
66+
'authenticated', 'complete'])

gssapi/tests/_utils.py

+25
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,28 @@ def _find_plugin_dirs_src(search_path):
120120
return options_raw.split('\n')
121121
else:
122122
return None
123+
124+
125+
_KRB_PREFIX = None
126+
127+
128+
def _requires_krb_plugin(plugin_type, plugin_name):
129+
global _KRB_PREFIX
130+
if _KRB_PREFIX is None:
131+
_KRB_PREFIX = get_output("krb5-config --prefix")
132+
133+
plugin_path = os.path.join(_KRB_PREFIX, 'lib/krb5/plugins',
134+
plugin_type, '%s.so' % plugin_name)
135+
136+
def make_krb_plugin_test(func):
137+
def krb_plugin_test(self, *args, **kwargs):
138+
if not os.path.exists(plugin_path):
139+
self.skipTest("You do not have the GSSAPI {type}"
140+
"plugin {name} installed".format(
141+
type=plugin_type, name=plugin_name))
142+
else:
143+
func(self, *args, **kwargs)
144+
145+
return krb_plugin_test
146+
147+
return make_krb_plugin_test

0 commit comments

Comments
 (0)