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'),