Skip to content

Commit 53f2de2

Browse files
committed
Implement RFC 6680 (Low-Level)
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 <[email protected]>
1 parent ee437ca commit 53f2de2

File tree

7 files changed

+448
-2
lines changed

7 files changed

+448
-2
lines changed

gssapi/raw/__init__.py

Lines changed: 6 additions & 0 deletions
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

Lines changed: 12 additions & 0 deletions
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

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
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):
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+
93+
Returns:
94+
InquireNameResult: the set of attribute names for the given name,
95+
whether or not the name is a Mechanism Name, and potentially
96+
the associated mechanism if it is a Mechanism Name
97+
98+
Raises:
99+
GSSError
100+
"""
101+
102+
cdef int name_is_mn = 0
103+
cdef gss_OID mn_mech
104+
cdef gss_buffer_set_t attr_names = GSS_C_NO_BUFFER_SET
105+
106+
cdef OM_uint32 maj_stat, min_stat
107+
108+
maj_stat = gss_inquire_name(&min_stat, name.raw_name, &name_is_mn,
109+
&mn_mech, &attr_names)
110+
111+
cdef int i
112+
cdef OID py_mech = None
113+
if maj_stat == GSS_S_COMPLETE:
114+
py_attr_names = []
115+
116+
if attr_names != GSS_C_NO_BUFFER_SET:
117+
for i in range(attr_names.count):
118+
attr_name = attr_names.elements[i]
119+
py_attr_names.append(attr_name.value[:attr_name.length])
120+
121+
gss_release_buffer_set(&min_stat, &attr_names)
122+
123+
if name_is_mn:
124+
py_mech = OID()
125+
py_mech.raw_oid = mn_mech[0]
126+
127+
return InquireNameResult(py_attr_names, <bint>name_is_mn, py_mech)
128+
else:
129+
raise GSSError(maj_stat, min_stat)
130+
131+
132+
def set_name_attribute(Name name not None, attr not None, value not None,
133+
bint complete=False):
134+
"""
135+
Add a value to a Name attribute
136+
137+
This method adds a value to the given attribute on the given name.
138+
139+
Args:
140+
name (Name): the Name on which to set the attribute
141+
attr (bytes): the name of the attribute
142+
value (bytes): the value to add
143+
complete (bool): whether or not to mark this attribute's value
144+
set as being "complete"
145+
146+
Raises:
147+
OperationUnavailableError: the given attribute name is unknown
148+
or could not be set
149+
"""
150+
151+
cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
152+
cdef gss_buffer_desc val_buff = gss_buffer_desc(len(value), value)
153+
154+
cdef OM_uint32 maj_stat, min_stat
155+
156+
maj_stat = gss_set_name_attribute(&min_stat, name.raw_name, complete,
157+
&attr_buff, &val_buff)
158+
159+
if maj_stat != GSS_S_COMPLETE:
160+
raise GSSError(maj_stat, min_stat)
161+
162+
163+
def get_name_attribute(Name name not None, attr not None, more=None):
164+
"""
165+
Get the value(s) of a Name attribute
166+
167+
This method retrieves the value(s) of the given attribute
168+
for the given Name.
169+
170+
Note that this functionality matches pseudo-API presented
171+
in RFC 6680, not the C API (which uses a state variable and
172+
multiple calls to retrieve multiple values).
173+
174+
Args:
175+
name (Name): the Name from which to get the attribute
176+
attr (bytes): the name of the attribute
177+
178+
Returns:
179+
GetNameAttributeResult: the raw version of the value(s),
180+
the human-readable version of the value(s), whether
181+
or not the attribute was authenticated, and whether or
182+
not the attribute's value set was marked as complete
183+
184+
Raises:
185+
OperationUnavailableError: the given attribute is unknown
186+
or unset
187+
"""
188+
cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
189+
190+
cdef gss_buffer_desc val_buff = gss_buffer_desc(0, NULL)
191+
cdef gss_buffer_desc displ_val_buff = gss_buffer_desc(0, NULL)
192+
cdef int complete
193+
cdef int authenticated
194+
195+
cdef int more_val = -1
196+
py_vals = []
197+
py_displ_vals = []
198+
199+
cdef OM_uint32 maj_stat, min_stat
200+
201+
while more_val != 0:
202+
maj_stat = gss_get_name_attribute(&min_stat, name.raw_name,
203+
&attr_buff,
204+
&authenticated, &complete,
205+
&val_buff, &displ_val_buff,
206+
&more_val)
207+
208+
if maj_stat == GSS_S_COMPLETE:
209+
py_vals.append(val_buff.value[:val_buff.length])
210+
py_displ_vals.append(
211+
displ_val_buff.value[:displ_val_buff.length])
212+
213+
gss_release_buffer(&min_stat, &val_buff)
214+
gss_release_buffer(&min_stat, &displ_val_buff)
215+
else:
216+
raise GSSError(maj_stat, min_stat)
217+
218+
return GetNameAttributeResult(py_vals, py_displ_vals, <bint>authenticated,
219+
<bint>complete)
220+
221+
222+
def delete_name_attribute(Name name not None, attr not None):
223+
"""
224+
Remove an attribute from a Name
225+
226+
This method removes an attribute from a Name. This method may be
227+
used before :func:`set_name_attribute` clear the values of an attribute
228+
before setting a new value (making the latter method work like a 'set'
229+
operation instead of an 'add' operation).
230+
231+
Note that the removal of certain attributes may not be allowed.
232+
233+
Args:
234+
name (Name): the name to remove the attribute from
235+
attr (bytes): the name of the attribute
236+
237+
Raises:
238+
OperationUnavailableError
239+
UnauthorizedError
240+
"""
241+
242+
cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
243+
244+
cdef OM_uint32 maj_stat, min_stat
245+
246+
maj_stat = gss_delete_name_attribute(&min_stat, name.raw_name,
247+
&attr_buff)
248+
249+
if maj_stat != GSS_S_COMPLETE:
250+
raise GSSError(maj_stat, min_stat)
251+
252+
253+
def export_name_composite(Name name not None):
254+
"""
255+
Export a name, preserving attribute information
256+
257+
This method functions similarly to :func:`export_name`, except that
258+
it preserves attribute information. The resulting bytes may be imported
259+
using :func:`import_name` with the `NameType.composite_export` name type.
260+
261+
Args:
262+
name (Name): the name to export
263+
264+
Returns:
265+
bytes: the exported composite name
266+
267+
Raises:
268+
GSSError
269+
"""
270+
271+
cdef gss_buffer_desc res = gss_buffer_desc(0, NULL)
272+
273+
cdef OM_uint32 maj_stat, min_stat
274+
275+
maj_stat = gss_export_name_composite(&min_stat, name.raw_name, &res)
276+
277+
if maj_stat == GSS_S_COMPLETE:
278+
py_res = res.value[:res.length]
279+
gss_release_buffer(&min_stat, &res)
280+
return py_res
281+
else:
282+
raise GSSError(maj_stat, min_stat)

gssapi/raw/named_tuples.py

Lines changed: 9 additions & 0 deletions
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

Lines changed: 26 additions & 0 deletions
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)