Skip to content

Commit 36c0da8

Browse files
committed
Implement partial support for GSSAPI extension GGF
GGF provides extended credential and security context inquiry that allows application to retrieve more information about the client's credentials and security context. One common use case is to use gss_inquire_sec_context_by_oid to retrieve the "session" key that is required by the SMB protocol for signing and encrypting a message. These calls are provided as a part of the raw interface and are not exposed in the high-level interface. Thanks to @vm86 for his work on the gss_inquire_sec_context_by_oid. Draft IETF document for these extensions can be found at https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00
1 parent beb69c9 commit 36c0da8

File tree

4 files changed

+223
-0
lines changed

4 files changed

+223
-0
lines changed

Diff for: gssapi/raw/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,9 @@
119119
from gssapi.raw.ext_rfc6680_comp_oid import * # noqa
120120
except ImportError:
121121
pass
122+
123+
# optional Global Grid Forum support
124+
try:
125+
from gssapi.raw.ext_ggf import * # noqa
126+
except ImportError:
127+
pass

Diff for: gssapi/raw/ext_ggf.pyx

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""
2+
GGF Extensions
3+
4+
GGF provides extended credential and security context inquiry that allows
5+
application to retrieve more information about the client's credentials and
6+
security context. One common use case is to use
7+
:meth:`inquire_sec_context_by_oid` to retrieve the "session" key that is
8+
required by the SMB protocol for signing and encrypting a message.
9+
10+
Draft IETF document for these extensions can be found at
11+
https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00
12+
"""
13+
GSSAPI="BASE" # This ensures that a full module is generated by Cython
14+
15+
from gssapi.raw.cython_types cimport *
16+
from gssapi.raw.ext_buffer_sets cimport *
17+
from gssapi.raw.cython_converters cimport c_get_mech_oid_set
18+
from gssapi.raw.misc import GSSError
19+
from gssapi.raw.oids cimport OID
20+
from gssapi.raw.creds cimport Creds
21+
from gssapi.raw.sec_contexts cimport SecurityContext
22+
23+
cdef extern from "python_gssapi_ext.h":
24+
25+
OM_uint32 gss_inquire_cred_by_oid(OM_uint32 *minor_status,
26+
const gss_cred_id_t cred_handle,
27+
const gss_OID desired_object,
28+
gss_buffer_set_t *data_set) nogil
29+
30+
OM_uint32 gss_inquire_sec_context_by_oid(OM_uint32 *minor_status,
31+
const gss_ctx_id_t context_handle,
32+
const gss_OID desired_object,
33+
gss_buffer_set_t *data_set) nogil
34+
35+
36+
def inquire_cred_by_oid(Creds cred_handle not None,
37+
OID desired_aspect not None):
38+
"""
39+
inquire_cred_by_oid(cred_handle, desired_aspect)
40+
41+
This method inspects a :class:`Creds` object for information
42+
specific to a particular desired aspect as an OID.
43+
44+
Args:
45+
cred_handle (Creds): the Credentials to query
46+
desired_aspect (OID): the desired aspect of the Credentials to inquire
47+
about.
48+
49+
Returns:
50+
list: A list of zero or more pieces of data (as bytes objects)
51+
52+
Raises:
53+
GSS_ERROR
54+
"""
55+
56+
cdef gss_buffer_set_t *data_set_ptr = NULL
57+
cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET
58+
cdef OM_uint32 maj_stat, min_stat
59+
60+
data_set_ptr = &data_set
61+
62+
with nogil:
63+
maj_stat = gss_inquire_cred_by_oid(&min_stat, cred_handle.raw_creds,
64+
&desired_aspect.raw_oid,
65+
data_set_ptr)
66+
67+
if maj_stat == GSS_S_COMPLETE:
68+
py_tokens = []
69+
70+
if data_set != GSS_C_NO_BUFFER_SET:
71+
for i in range(data_set.count):
72+
token = data_set.elements[i]
73+
py_tokens.append(token.value[:token.length])
74+
75+
gss_release_buffer_set(&min_stat, &data_set)
76+
77+
return py_tokens
78+
else:
79+
raise GSSError(maj_stat, min_stat)
80+
81+
82+
def inquire_sec_context_by_oid(SecurityContext context not None,
83+
OID desired_aspect not None):
84+
"""
85+
inquire_sec_context_by_oid(context, desired_aspect)
86+
87+
This method inspects a :class:`SecurityContext` object for information
88+
specific to a particular desired aspect as an OID.
89+
90+
This method can be used with the GSS_KRB5_INQ_SSPI_SESSION_KEY_OID OID to
91+
retrieve the required key that is used to derive the SMB/SAMBA signing and
92+
encryption keys.
93+
94+
Args:
95+
context (SecurityContext): the Security Context to query
96+
desired_aspect (OID): the desired aspected of the Security Context to
97+
inquire about.
98+
99+
Returns:
100+
list: A list of zero or more pieces of data (as bytes objects)
101+
102+
Raises:
103+
GSS_ERROR
104+
"""
105+
106+
cdef gss_buffer_set_t *data_set_ptr = NULL
107+
cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET
108+
cdef OM_uint32 maj_stat, min_stat
109+
110+
data_set_ptr = &data_set
111+
112+
with nogil:
113+
maj_stat = gss_inquire_sec_context_by_oid(&min_stat, context.raw_ctx,
114+
&desired_aspect.raw_oid,
115+
data_set_ptr)
116+
117+
if maj_stat == GSS_S_COMPLETE:
118+
py_tokens = []
119+
120+
if data_set != GSS_C_NO_BUFFER_SET:
121+
for i in range(data_set.count):
122+
token = data_set.elements[i]
123+
py_tokens.append(token.value[:token.length])
124+
125+
gss_release_buffer_set(&min_stat, &data_set)
126+
127+
return py_tokens
128+
else:
129+
raise GSSError(maj_stat, min_stat)

Diff for: gssapi/tests/test_raw.py

+87
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,93 @@ def test_sasl_names(self):
760760
cmp_mech.shouldnt_be_none()
761761
cmp_mech.should_be(mech)
762762

763+
@ktu.gssapi_extension_test('ggf', 'Global Grid Forum')
764+
@ktu.gssapi_extension_test('s4u', 'S4U')
765+
@ktu.krb_minversion_test('1.16',
766+
'querying impersonator name of krb5 GSS '
767+
'Credential using the '
768+
'GSS_KRB5_GET_CRED_IMPERSONATOR OID')
769+
def test_inquire_cred_by_oid_impersonator(self):
770+
svc_princ = SERVICE_PRINCIPAL.decode("UTF-8")
771+
self.realm.kinit(svc_princ, flags=['-k', '-f'])
772+
773+
target_name = gb.import_name(TARGET_SERVICE_NAME,
774+
gb.NameType.hostbased_service)
775+
776+
client_token = gb.init_sec_context(target_name).token
777+
778+
# if our acceptor creds have a usage of both, we get
779+
# s4u2proxy delegated credentials
780+
server_creds = gb.acquire_cred(None, usage='both').creds
781+
server_ctx_resp = gb.accept_sec_context(client_token,
782+
acceptor_creds=server_creds)
783+
784+
server_ctx_resp.shouldnt_be_none()
785+
server_ctx_resp.delegated_creds.shouldnt_be_none()
786+
server_ctx_resp.delegated_creds.should_be_a(gb.Creds)
787+
788+
# GSS_KRB5_GET_CRED_IMPERSONATOR
789+
oid = gb.OID.from_int_seq("1.2.840.113554.1.2.2.5.14")
790+
info = gb.inquire_cred_by_oid(server_ctx_resp.delegated_creds, oid)
791+
792+
info.should_be_a(list)
793+
info.shouldnt_be_empty()
794+
info[0].should_be_a(bytes)
795+
info[0].should_be(b"%s@%s" % (SERVICE_PRINCIPAL,
796+
self.realm.realm.encode('utf-8')))
797+
798+
@ktu.gssapi_extension_test('ggf', 'Global Grid Forum')
799+
def test_inquire_sec_context_by_oid(self):
800+
target_name = gb.import_name(TARGET_SERVICE_NAME,
801+
gb.NameType.hostbased_service)
802+
ctx_resp1 = gb.init_sec_context(target_name)
803+
804+
server_name = gb.import_name(SERVICE_PRINCIPAL,
805+
gb.NameType.kerberos_principal)
806+
server_creds = gb.acquire_cred(server_name)[0]
807+
server_resp = gb.accept_sec_context(ctx_resp1[3],
808+
acceptor_creds=server_creds)
809+
server_ctx = server_resp[0]
810+
server_tok = server_resp[3]
811+
812+
client_resp2 = gb.init_sec_context(target_name,
813+
context=ctx_resp1[0],
814+
input_token=server_tok)
815+
client_ctx = client_resp2[0]
816+
817+
# GSS_C_INQ_SSPI_SESSION_KEY
818+
session_key_oid = gb.OID.from_int_seq("1.2.840.113554.1.2.2.5.5")
819+
820+
client_key = gb.inquire_sec_context_by_oid(client_ctx, session_key_oid)
821+
server_key = gb.inquire_sec_context_by_oid(server_ctx, session_key_oid)
822+
823+
client_key.should_be_a(list)
824+
client_key.shouldnt_be_empty()
825+
server_key.should_be_a(list)
826+
server_key.shouldnt_be_empty()
827+
client_key.should_have_same_items_as(server_key)
828+
829+
@ktu.gssapi_extension_test('ggf', 'Global Grid Forum')
830+
def test_inquire_sec_context_by_oid_should_raise_error(self):
831+
target_name = gb.import_name(TARGET_SERVICE_NAME,
832+
gb.NameType.hostbased_service)
833+
ctx_resp1 = gb.init_sec_context(target_name)
834+
835+
server_name = gb.import_name(SERVICE_PRINCIPAL,
836+
gb.NameType.kerberos_principal)
837+
server_creds = gb.acquire_cred(server_name)[0]
838+
server_resp = gb.accept_sec_context(ctx_resp1[3],
839+
acceptor_creds=server_creds)
840+
841+
client_resp2 = gb.init_sec_context(target_name,
842+
context=ctx_resp1[0],
843+
input_token=server_resp[3])
844+
client_ctx = client_resp2[0]
845+
846+
invalid_oid = gb.OID.from_int_seq("1.2.3.4.5.6.7.8.9")
847+
gb.inquire_sec_context_by_oid.should_raise(gb.GSSError, client_ctx,
848+
invalid_oid)
849+
763850

764851
class TestIntEnumFlagSet(unittest.TestCase):
765852
def test_create_from_int(self):

Diff for: setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ def gssapi_modules(lst):
264264
extension_file('cred_imp_exp', 'gss_import_cred'),
265265
extension_file('dce', 'gss_wrap_iov'),
266266
extension_file('iov_mic', 'gss_get_mic_iov'),
267+
extension_file('ggf', 'gss_inquire_sec_context_by_oid'),
267268

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

0 commit comments

Comments
 (0)