From 36c0da8c18f184b2285be920b7d5c722f1997fbd Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 11 Jan 2018 13:15:36 +1000 Subject: [PATCH] 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 --- gssapi/raw/__init__.py | 6 ++ gssapi/raw/ext_ggf.pyx | 129 +++++++++++++++++++++++++++++++++++++++ gssapi/tests/test_raw.py | 87 ++++++++++++++++++++++++++ setup.py | 1 + 4 files changed, 223 insertions(+) create mode 100644 gssapi/raw/ext_ggf.pyx diff --git a/gssapi/raw/__init__.py b/gssapi/raw/__init__.py index 2ad87452..79eb04cf 100644 --- a/gssapi/raw/__init__.py +++ b/gssapi/raw/__init__.py @@ -119,3 +119,9 @@ from gssapi.raw.ext_rfc6680_comp_oid import * # noqa except ImportError: pass + +# optional Global Grid Forum support +try: + from gssapi.raw.ext_ggf import * # noqa +except ImportError: + pass diff --git a/gssapi/raw/ext_ggf.pyx b/gssapi/raw/ext_ggf.pyx new file mode 100644 index 00000000..0d5255bf --- /dev/null +++ b/gssapi/raw/ext_ggf.pyx @@ -0,0 +1,129 @@ +""" +GGF Extensions + +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 +:meth:`inquire_sec_context_by_oid` to retrieve the "session" key that is +required by the SMB protocol for signing and encrypting a message. + +Draft IETF document for these extensions can be found at +https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00 +""" +GSSAPI="BASE" # This ensures that a full module is generated by Cython + +from gssapi.raw.cython_types cimport * +from gssapi.raw.ext_buffer_sets cimport * +from gssapi.raw.cython_converters cimport c_get_mech_oid_set +from gssapi.raw.misc import GSSError +from gssapi.raw.oids cimport OID +from gssapi.raw.creds cimport Creds +from gssapi.raw.sec_contexts cimport SecurityContext + +cdef extern from "python_gssapi_ext.h": + + OM_uint32 gss_inquire_cred_by_oid(OM_uint32 *minor_status, + const gss_cred_id_t cred_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) nogil + + OM_uint32 gss_inquire_sec_context_by_oid(OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) nogil + + +def inquire_cred_by_oid(Creds cred_handle not None, + OID desired_aspect not None): + """ + inquire_cred_by_oid(cred_handle, desired_aspect) + + This method inspects a :class:`Creds` object for information + specific to a particular desired aspect as an OID. + + Args: + cred_handle (Creds): the Credentials to query + desired_aspect (OID): the desired aspect of the Credentials to inquire + about. + + Returns: + list: A list of zero or more pieces of data (as bytes objects) + + Raises: + GSS_ERROR + """ + + cdef gss_buffer_set_t *data_set_ptr = NULL + cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET + cdef OM_uint32 maj_stat, min_stat + + data_set_ptr = &data_set + + with nogil: + maj_stat = gss_inquire_cred_by_oid(&min_stat, cred_handle.raw_creds, + &desired_aspect.raw_oid, + data_set_ptr) + + if maj_stat == GSS_S_COMPLETE: + py_tokens = [] + + if data_set != GSS_C_NO_BUFFER_SET: + for i in range(data_set.count): + token = data_set.elements[i] + py_tokens.append(token.value[:token.length]) + + gss_release_buffer_set(&min_stat, &data_set) + + return py_tokens + else: + raise GSSError(maj_stat, min_stat) + + +def inquire_sec_context_by_oid(SecurityContext context not None, + OID desired_aspect not None): + """ + inquire_sec_context_by_oid(context, desired_aspect) + + This method inspects a :class:`SecurityContext` object for information + specific to a particular desired aspect as an OID. + + This method can be used with the GSS_KRB5_INQ_SSPI_SESSION_KEY_OID OID to + retrieve the required key that is used to derive the SMB/SAMBA signing and + encryption keys. + + Args: + context (SecurityContext): the Security Context to query + desired_aspect (OID): the desired aspected of the Security Context to + inquire about. + + Returns: + list: A list of zero or more pieces of data (as bytes objects) + + Raises: + GSS_ERROR + """ + + cdef gss_buffer_set_t *data_set_ptr = NULL + cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET + cdef OM_uint32 maj_stat, min_stat + + data_set_ptr = &data_set + + with nogil: + maj_stat = gss_inquire_sec_context_by_oid(&min_stat, context.raw_ctx, + &desired_aspect.raw_oid, + data_set_ptr) + + if maj_stat == GSS_S_COMPLETE: + py_tokens = [] + + if data_set != GSS_C_NO_BUFFER_SET: + for i in range(data_set.count): + token = data_set.elements[i] + py_tokens.append(token.value[:token.length]) + + gss_release_buffer_set(&min_stat, &data_set) + + return py_tokens + else: + raise GSSError(maj_stat, min_stat) diff --git a/gssapi/tests/test_raw.py b/gssapi/tests/test_raw.py index 90da9b77..20df9f88 100644 --- a/gssapi/tests/test_raw.py +++ b/gssapi/tests/test_raw.py @@ -760,6 +760,93 @@ def test_sasl_names(self): cmp_mech.shouldnt_be_none() cmp_mech.should_be(mech) + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + @ktu.gssapi_extension_test('s4u', 'S4U') + @ktu.krb_minversion_test('1.16', + 'querying impersonator name of krb5 GSS ' + 'Credential using the ' + 'GSS_KRB5_GET_CRED_IMPERSONATOR OID') + def test_inquire_cred_by_oid_impersonator(self): + svc_princ = SERVICE_PRINCIPAL.decode("UTF-8") + self.realm.kinit(svc_princ, flags=['-k', '-f']) + + target_name = gb.import_name(TARGET_SERVICE_NAME, + gb.NameType.hostbased_service) + + client_token = gb.init_sec_context(target_name).token + + # if our acceptor creds have a usage of both, we get + # s4u2proxy delegated credentials + server_creds = gb.acquire_cred(None, usage='both').creds + server_ctx_resp = gb.accept_sec_context(client_token, + acceptor_creds=server_creds) + + server_ctx_resp.shouldnt_be_none() + server_ctx_resp.delegated_creds.shouldnt_be_none() + server_ctx_resp.delegated_creds.should_be_a(gb.Creds) + + # GSS_KRB5_GET_CRED_IMPERSONATOR + oid = gb.OID.from_int_seq("1.2.840.113554.1.2.2.5.14") + info = gb.inquire_cred_by_oid(server_ctx_resp.delegated_creds, oid) + + info.should_be_a(list) + info.shouldnt_be_empty() + info[0].should_be_a(bytes) + info[0].should_be(b"%s@%s" % (SERVICE_PRINCIPAL, + self.realm.realm.encode('utf-8'))) + + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + def test_inquire_sec_context_by_oid(self): + target_name = gb.import_name(TARGET_SERVICE_NAME, + gb.NameType.hostbased_service) + ctx_resp1 = gb.init_sec_context(target_name) + + server_name = gb.import_name(SERVICE_PRINCIPAL, + gb.NameType.kerberos_principal) + server_creds = gb.acquire_cred(server_name)[0] + server_resp = gb.accept_sec_context(ctx_resp1[3], + acceptor_creds=server_creds) + server_ctx = server_resp[0] + server_tok = server_resp[3] + + client_resp2 = gb.init_sec_context(target_name, + context=ctx_resp1[0], + input_token=server_tok) + client_ctx = client_resp2[0] + + # GSS_C_INQ_SSPI_SESSION_KEY + session_key_oid = gb.OID.from_int_seq("1.2.840.113554.1.2.2.5.5") + + client_key = gb.inquire_sec_context_by_oid(client_ctx, session_key_oid) + server_key = gb.inquire_sec_context_by_oid(server_ctx, session_key_oid) + + client_key.should_be_a(list) + client_key.shouldnt_be_empty() + server_key.should_be_a(list) + server_key.shouldnt_be_empty() + client_key.should_have_same_items_as(server_key) + + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + def test_inquire_sec_context_by_oid_should_raise_error(self): + target_name = gb.import_name(TARGET_SERVICE_NAME, + gb.NameType.hostbased_service) + ctx_resp1 = gb.init_sec_context(target_name) + + server_name = gb.import_name(SERVICE_PRINCIPAL, + gb.NameType.kerberos_principal) + server_creds = gb.acquire_cred(server_name)[0] + server_resp = gb.accept_sec_context(ctx_resp1[3], + acceptor_creds=server_creds) + + client_resp2 = gb.init_sec_context(target_name, + context=ctx_resp1[0], + input_token=server_resp[3]) + client_ctx = client_resp2[0] + + invalid_oid = gb.OID.from_int_seq("1.2.3.4.5.6.7.8.9") + gb.inquire_sec_context_by_oid.should_raise(gb.GSSError, client_ctx, + invalid_oid) + class TestIntEnumFlagSet(unittest.TestCase): def test_create_from_int(self): diff --git a/setup.py b/setup.py index 2b38a3ef..afad908a 100755 --- a/setup.py +++ b/setup.py @@ -264,6 +264,7 @@ def gssapi_modules(lst): extension_file('cred_imp_exp', 'gss_import_cred'), extension_file('dce', 'gss_wrap_iov'), extension_file('iov_mic', 'gss_get_mic_iov'), + extension_file('ggf', 'gss_inquire_sec_context_by_oid'), # see ext_rfc6680_comp_oid for more information on this split extension_file('rfc6680', 'gss_display_name_ext'),