Skip to content

Commit 13467fb

Browse files
jborean93frozencemetery
authored andcommitted
Implement gss_set_cred_option() and gss_set_sec_context_option()
These parts of th GGF extensions provide extended support for managing security contexts and credentials. In particular, with NTLM, they can be used to reset the crypto handles using the GSS_NTLMSSP_RESET_CRYPTO_OID_LENGTH OID. Draft IETF document for the gss_set_sec_context_option(): https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00 Draft IETF document for the gss_set_cred_option(): https://tools.ietf.org/html/draft-ietf-kitten-channel-bound-flag-02 Fixes: #51 [[email protected] edited commit message]
1 parent 5e58d2e commit 13467fb

File tree

6 files changed

+232
-4
lines changed

6 files changed

+232
-4
lines changed

Diff for: README.txt

+2
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ In addition to RFC 2743/2744, Python-GSSAPI also has support for:
156156

157157
* `acquire_cred_with_password` and `add_cred_with_password`
158158

159+
* GGF Extensions
160+
159161
The Team
160162
========
161163

Diff for: gssapi/raw/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,9 @@
125125
from gssapi.raw.ext_ggf import * # noqa
126126
except ImportError:
127127
pass
128+
129+
# optional set_cred_option support
130+
try:
131+
from gssapi.raw.ext_set_cred_opt import * # noqa
132+
except ImportError:
133+
pass

Diff for: gssapi/raw/ext_ggf.pyx

+64-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ GSSAPI="BASE" # This ensures that a full module is generated by Cython
1414

1515
from gssapi.raw.cython_types cimport *
1616
from gssapi.raw.ext_buffer_sets cimport *
17-
from gssapi.raw.cython_converters cimport c_get_mech_oid_set
1817
from gssapi.raw.misc import GSSError
1918
from gssapi.raw.oids cimport OID
2019
from gssapi.raw.creds cimport Creds
@@ -32,6 +31,11 @@ cdef extern from "python_gssapi_ext.h":
3231
const gss_OID desired_object,
3332
gss_buffer_set_t *data_set) nogil
3433

34+
OM_uint32 gss_set_sec_context_option(OM_uint32 *minor_status,
35+
gss_ctx_id_t *context_handle,
36+
const gss_OID desired_object,
37+
const gss_buffer_t value) nogil
38+
3539

3640
def inquire_cred_by_oid(Creds cred_handle not None,
3741
OID desired_aspect not None):
@@ -50,7 +54,7 @@ def inquire_cred_by_oid(Creds cred_handle not None,
5054
list: A list of zero or more pieces of data (as bytes objects)
5155
5256
Raises:
53-
GSS_ERROR
57+
GSSError
5458
"""
5559

5660
cdef gss_buffer_set_t *data_set_ptr = NULL
@@ -93,14 +97,14 @@ def inquire_sec_context_by_oid(SecurityContext context not None,
9397
9498
Args:
9599
context (SecurityContext): the Security Context to query
96-
desired_aspect (OID): the desired aspected of the Security Context to
100+
desired_aspect (OID): the desired aspect of the Security Context to
97101
inquire about.
98102
99103
Returns:
100104
list: A list of zero or more pieces of data (as bytes objects)
101105
102106
Raises:
103-
GSS_ERROR
107+
GSSError
104108
"""
105109

106110
cdef gss_buffer_set_t *data_set_ptr = NULL
@@ -127,3 +131,59 @@ def inquire_sec_context_by_oid(SecurityContext context not None,
127131
return py_tokens
128132
else:
129133
raise GSSError(maj_stat, min_stat)
134+
135+
136+
def set_sec_context_option(OID desired_aspect not None,
137+
SecurityContext context=None,
138+
value=None):
139+
"""
140+
set_sec_context_option(desired_aspect, context=None, value=None)
141+
142+
This method is used to set a value for a specific OID of a
143+
:class:`SecurityContext` object. The OID and value to pass in depends on
144+
the mech the SecurityContext backs.
145+
146+
An example of how this can be used would be to reset the NTLM crypto engine
147+
used in gss-ntlmssp. The OID that controls this value is
148+
'1.3.6.1.4.1.7165.655.1.3' and it takes it a byte value that represents
149+
an int32 where 1 resets the verifier handle and any other int resets the
150+
sender handle.
151+
152+
Args:
153+
desired_aspect (OID): the desired aspect of the Security Context to set
154+
the value for.
155+
context (SecurityContext): the Security Context to set, or None to
156+
create a new context.
157+
value (bytes): the value to set on the desired aspect of the Security
158+
Context or None to send GSS_C_EMPTY_BUFFER.
159+
160+
Returns:
161+
SecurityContext: The output security context.
162+
163+
Raises:
164+
GSSError
165+
"""
166+
167+
cdef gss_buffer_desc value_buffer
168+
if value is not None:
169+
value_buffer = gss_buffer_desc(len(value), value)
170+
else:
171+
# GSS_C_EMPTY_BUFFER
172+
value_buffer = gss_buffer_desc(0, NULL)
173+
174+
cdef SecurityContext output_context = context
175+
if output_context is None:
176+
output_context = SecurityContext()
177+
178+
cdef OM_uint32 maj_stat, min_stat
179+
180+
with nogil:
181+
maj_stat = gss_set_sec_context_option(&min_stat,
182+
&output_context.raw_ctx,
183+
&desired_aspect.raw_oid,
184+
&value_buffer)
185+
186+
if maj_stat == GSS_S_COMPLETE:
187+
return output_context
188+
else:
189+
raise GSSError(maj_stat, min_stat)

Diff for: gssapi/raw/ext_set_cred_opt.pyx

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
gss_set_cred_option
3+
4+
Provides a way to set options on a credential based on the OID specified. A
5+
common use case is to set the GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos
6+
credential. This is used for interoperability with Microsoft's SSPI.
7+
8+
Note this function is commonly lumped with the GGF extensions but they are not
9+
part of the GGF IETF draft so it's separated into it's own file.
10+
11+
Closest draft IETF document for the gss_set_cred_option can be found at
12+
https://tools.ietf.org/html/draft-williams-kitten-channel-bound-flag-01
13+
"""
14+
GSSAPI="BASE" # This ensures that a full module is generated by Cython
15+
16+
from gssapi.raw.cython_types cimport *
17+
from gssapi.raw.ext_buffer_sets cimport *
18+
from gssapi.raw.misc import GSSError
19+
from gssapi.raw.oids cimport OID
20+
from gssapi.raw.creds cimport Creds
21+
22+
cdef extern from "python_gssapi_ext.h":
23+
24+
OM_uint32 gss_set_cred_option(OM_uint32 *minor_status,
25+
gss_cred_id_t *cred,
26+
const gss_OID desired_object,
27+
const gss_buffer_t value) nogil
28+
29+
30+
def set_cred_option(OID desired_aspect not None, Creds creds=None, value=None):
31+
"""
32+
set_cred_option(desired_aspect, creds=None, value=None)
33+
34+
This method is used to set options of a :class:`Creds` object based on
35+
an OID key. The options that can be set depends on the mech the credentials
36+
were created with.
37+
38+
An example of how this can be used would be to set the
39+
GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos credential. The OID string for
40+
this flag is '1.2.752.43.13.29' and it requires no value to be set. This
41+
must be set before the SecurityContext was initialised with the
42+
credentials.
43+
44+
Args:
45+
desired_aspect (OID): the desired aspect of the Credential to set.
46+
cred_handle (Creds): the Credentials to set, or None to create a new
47+
credential.
48+
value (bytes): the value to set on the desired aspect of the Credential
49+
or None to send GSS_C_EMPTY_BUFFER.
50+
51+
Returns:
52+
Creds: The output credential.
53+
54+
Raises:
55+
GSSError
56+
"""
57+
58+
cdef gss_buffer_desc value_buffer
59+
if value is not None:
60+
value_buffer = gss_buffer_desc(len(value), value)
61+
else:
62+
# GSS_C_EMPTY_BUFFER
63+
value_buffer = gss_buffer_desc(0, NULL)
64+
65+
cdef Creds output_creds = creds
66+
if output_creds is None:
67+
output_creds = Creds()
68+
69+
cdef OM_uint32 maj_stat, min_stat
70+
71+
with nogil:
72+
maj_stat = gss_set_cred_option(&min_stat,
73+
&output_creds.raw_creds,
74+
&desired_aspect.raw_oid,
75+
&value_buffer)
76+
77+
if maj_stat == GSS_S_COMPLETE:
78+
return output_creds
79+
else:
80+
raise GSSError(maj_stat, min_stat)

Diff for: gssapi/tests/test_raw.py

+79
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,85 @@ def test_inquire_sec_context_by_oid_should_raise_error(self):
843843
gb.inquire_sec_context_by_oid.should_raise(gb.GSSError, client_ctx,
844844
invalid_oid)
845845

846+
@ktu.gssapi_extension_test('ggf', 'Global Grid Forum')
847+
@ktu.gssapi_extension_test('password', 'Add Credential with Password')
848+
def test_set_sec_context_option(self):
849+
ntlm_mech = gb.OID.from_int_seq("1.3.6.1.4.1.311.2.2.10")
850+
username = gb.import_name(name=b"user",
851+
name_type=gb.NameType.user)
852+
try:
853+
cred = gb.acquire_cred_with_password(name=username,
854+
password=b"password",
855+
mechs=[ntlm_mech])
856+
except gb.GSSError:
857+
self.skipTest('You do not have the GSSAPI gss-ntlmssp mech '
858+
'installed')
859+
860+
server = gb.import_name(name=b"server",
861+
name_type=gb.NameType.hostbased_service)
862+
orig_context = gb.init_sec_context(server, creds=cred.creds,
863+
mech=ntlm_mech)[0]
864+
865+
# GSS_NTLMSSP_RESET_CRYPTO_OID_STRING
866+
reset_mech = gb.OID.from_int_seq("1.3.6.1.4.1.7165.655.1.3")
867+
out_context = gb.set_sec_context_option(reset_mech,
868+
context=orig_context,
869+
value=b"\x00" * 4)
870+
out_context.should_be_a(gb.SecurityContext)
871+
872+
@ktu.gssapi_extension_test('ggf', 'Global Grid Forum')
873+
@ktu.gssapi_extension_test('password', 'Add Credential with Password')
874+
def test_set_sec_context_option_fail(self):
875+
ntlm_mech = gb.OID.from_int_seq("1.3.6.1.4.1.311.2.2.10")
876+
username = gb.import_name(name=b"user",
877+
name_type=gb.NameType.user)
878+
try:
879+
cred = gb.acquire_cred_with_password(name=username,
880+
password=b"password",
881+
mechs=[ntlm_mech])
882+
except gb.GSSError:
883+
self.skipTest('You do not have the GSSAPI gss-ntlmssp mech '
884+
'installed')
885+
886+
server = gb.import_name(name=b"server",
887+
name_type=gb.NameType.hostbased_service)
888+
context = gb.init_sec_context(server, creds=cred.creds,
889+
mech=ntlm_mech)[0]
890+
891+
# GSS_NTLMSSP_RESET_CRYPTO_OID_STRING
892+
reset_mech = gb.OID.from_int_seq("1.3.6.1.4.1.7165.655.1.3")
893+
894+
# will raise a GSSError if no data was passed in
895+
gb.set_sec_context_option.should_raise(gb.GSSError, reset_mech,
896+
context)
897+
898+
@ktu.gssapi_extension_test('set_cred_opt', 'Kitten Set Credential Option')
899+
@ktu.krb_minversion_test('1.14',
900+
'GSS_KRB5_CRED_NO_CI_FLAGS_X was added in MIT '
901+
'krb5 1.14')
902+
def test_set_cred_option(self):
903+
name = gb.import_name(SERVICE_PRINCIPAL,
904+
gb.NameType.kerberos_principal)
905+
# GSS_KRB5_CRED_NO_CI_FLAGS_X
906+
no_ci_flags_x = gb.OID.from_int_seq("1.2.752.43.13.29")
907+
orig_cred = gb.acquire_cred(name).creds
908+
909+
# nothing much we can test here apart from it doesn't fail and the
910+
# id of the return cred is the same as the input one
911+
output_cred = gb.set_cred_option(no_ci_flags_x, creds=orig_cred)
912+
output_cred.should_be_a(gb.Creds)
913+
914+
@ktu.gssapi_extension_test('set_cred_opt', 'Kitten Set Credential Option')
915+
def test_set_cred_option_should_raise_error(self):
916+
name = gb.import_name(SERVICE_PRINCIPAL,
917+
gb.NameType.kerberos_principal)
918+
orig_cred = gb.acquire_cred(name).creds
919+
920+
# this is a fake OID and shouldn't work at all
921+
invalid_oid = gb.OID.from_int_seq("1.2.3.4.5.6.7.8.9")
922+
gb.set_cred_option.should_raise(gb.GSSError, invalid_oid, orig_cred,
923+
b"\x00")
924+
846925

847926
class TestIntEnumFlagSet(unittest.TestCase):
848927
def test_create_from_int(self):

Diff for: setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ def gssapi_modules(lst):
276276
extension_file('dce', 'gss_wrap_iov'),
277277
extension_file('iov_mic', 'gss_get_mic_iov'),
278278
extension_file('ggf', 'gss_inquire_sec_context_by_oid'),
279+
extension_file('set_cred_opt', 'gss_set_cred_option'),
279280

280281
# see ext_rfc6680_comp_oid for more information on this split
281282
extension_file('rfc6680', 'gss_display_name_ext'),

0 commit comments

Comments
 (0)