Skip to content

Commit 6c3820b

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 af11adf commit 6c3820b

File tree

7 files changed

+487
-2
lines changed

7 files changed

+487
-2
lines changed

gssapi/raw/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@
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+
except ImportError:
72+
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

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

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

+26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from gssapi._utils import import_gssapi_extension
2+
import os.path
23

34
try:
45
import commands
@@ -47,3 +48,28 @@ def ext_test(self, *args, **kwargs):
4748
return ext_test
4849

4950
return make_ext_test
51+
52+
53+
_KRB_PREFIX = None
54+
55+
56+
def _requires_krb_plugin(plugin_type, plugin_name):
57+
global _KRB_PREFIX
58+
if _KRB_PREFIX is None:
59+
_KRB_PREFIX = get_output("krb5-config --prefix")
60+
61+
plugin_path = os.path.join(_KRB_PREFIX, 'lib/krb5/plugins',
62+
plugin_type, '%s.so' % plugin_name)
63+
64+
def make_krb_plugin_test(func):
65+
def krb_plugin_test(self, *args, **kwargs):
66+
if not os.path.exists(plugin_path):
67+
self.skipTest("You do not have the GSSAPI {type}"
68+
"plugin {name} installed".format(
69+
type=plugin_type, name=plugin_name))
70+
else:
71+
func(self, *args, **kwargs)
72+
73+
return krb_plugin_test
74+
75+
return make_krb_plugin_test

0 commit comments

Comments
 (0)