From 13467fb2d58b1a59e0fb14d7407a4de7b1500e6d Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 22 Mar 2018 19:16:12 +1000 Subject: [PATCH] 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 [rharwood@redhat.com edited commit message] --- README.txt | 2 + gssapi/raw/__init__.py | 6 +++ gssapi/raw/ext_ggf.pyx | 68 ++++++++++++++++++++++++++-- gssapi/raw/ext_set_cred_opt.pyx | 80 +++++++++++++++++++++++++++++++++ gssapi/tests/test_raw.py | 79 ++++++++++++++++++++++++++++++++ setup.py | 1 + 6 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 gssapi/raw/ext_set_cred_opt.pyx diff --git a/README.txt b/README.txt index 8f083fd9..98315843 100644 --- a/README.txt +++ b/README.txt @@ -156,6 +156,8 @@ In addition to RFC 2743/2744, Python-GSSAPI also has support for: * `acquire_cred_with_password` and `add_cred_with_password` +* GGF Extensions + The Team ======== diff --git a/gssapi/raw/__init__.py b/gssapi/raw/__init__.py index 79eb04cf..5ce554f1 100644 --- a/gssapi/raw/__init__.py +++ b/gssapi/raw/__init__.py @@ -125,3 +125,9 @@ from gssapi.raw.ext_ggf import * # noqa except ImportError: pass + +# optional set_cred_option support +try: + from gssapi.raw.ext_set_cred_opt import * # noqa +except ImportError: + pass diff --git a/gssapi/raw/ext_ggf.pyx b/gssapi/raw/ext_ggf.pyx index 0d5255bf..7029d3db 100644 --- a/gssapi/raw/ext_ggf.pyx +++ b/gssapi/raw/ext_ggf.pyx @@ -14,7 +14,6 @@ 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 @@ -32,6 +31,11 @@ cdef extern from "python_gssapi_ext.h": const gss_OID desired_object, gss_buffer_set_t *data_set) nogil + OM_uint32 gss_set_sec_context_option(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + const gss_OID desired_object, + const gss_buffer_t value) nogil + def inquire_cred_by_oid(Creds cred_handle not None, OID desired_aspect not None): @@ -50,7 +54,7 @@ def inquire_cred_by_oid(Creds cred_handle not None, list: A list of zero or more pieces of data (as bytes objects) Raises: - GSS_ERROR + GSSError """ cdef gss_buffer_set_t *data_set_ptr = NULL @@ -93,14 +97,14 @@ def inquire_sec_context_by_oid(SecurityContext context not None, Args: context (SecurityContext): the Security Context to query - desired_aspect (OID): the desired aspected of the Security Context to + desired_aspect (OID): the desired aspect of the Security Context to inquire about. Returns: list: A list of zero or more pieces of data (as bytes objects) Raises: - GSS_ERROR + GSSError """ cdef gss_buffer_set_t *data_set_ptr = NULL @@ -127,3 +131,59 @@ def inquire_sec_context_by_oid(SecurityContext context not None, return py_tokens else: raise GSSError(maj_stat, min_stat) + + +def set_sec_context_option(OID desired_aspect not None, + SecurityContext context=None, + value=None): + """ + set_sec_context_option(desired_aspect, context=None, value=None) + + This method is used to set a value for a specific OID of a + :class:`SecurityContext` object. The OID and value to pass in depends on + the mech the SecurityContext backs. + + An example of how this can be used would be to reset the NTLM crypto engine + used in gss-ntlmssp. The OID that controls this value is + '1.3.6.1.4.1.7165.655.1.3' and it takes it a byte value that represents + an int32 where 1 resets the verifier handle and any other int resets the + sender handle. + + Args: + desired_aspect (OID): the desired aspect of the Security Context to set + the value for. + context (SecurityContext): the Security Context to set, or None to + create a new context. + value (bytes): the value to set on the desired aspect of the Security + Context or None to send GSS_C_EMPTY_BUFFER. + + Returns: + SecurityContext: The output security context. + + Raises: + GSSError + """ + + cdef gss_buffer_desc value_buffer + if value is not None: + value_buffer = gss_buffer_desc(len(value), value) + else: + # GSS_C_EMPTY_BUFFER + value_buffer = gss_buffer_desc(0, NULL) + + cdef SecurityContext output_context = context + if output_context is None: + output_context = SecurityContext() + + cdef OM_uint32 maj_stat, min_stat + + with nogil: + maj_stat = gss_set_sec_context_option(&min_stat, + &output_context.raw_ctx, + &desired_aspect.raw_oid, + &value_buffer) + + if maj_stat == GSS_S_COMPLETE: + return output_context + else: + raise GSSError(maj_stat, min_stat) diff --git a/gssapi/raw/ext_set_cred_opt.pyx b/gssapi/raw/ext_set_cred_opt.pyx new file mode 100644 index 00000000..a72f7487 --- /dev/null +++ b/gssapi/raw/ext_set_cred_opt.pyx @@ -0,0 +1,80 @@ +""" +gss_set_cred_option + +Provides a way to set options on a credential based on the OID specified. A +common use case is to set the GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos +credential. This is used for interoperability with Microsoft's SSPI. + +Note this function is commonly lumped with the GGF extensions but they are not +part of the GGF IETF draft so it's separated into it's own file. + +Closest draft IETF document for the gss_set_cred_option can be found at +https://tools.ietf.org/html/draft-williams-kitten-channel-bound-flag-01 +""" +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.misc import GSSError +from gssapi.raw.oids cimport OID +from gssapi.raw.creds cimport Creds + +cdef extern from "python_gssapi_ext.h": + + OM_uint32 gss_set_cred_option(OM_uint32 *minor_status, + gss_cred_id_t *cred, + const gss_OID desired_object, + const gss_buffer_t value) nogil + + +def set_cred_option(OID desired_aspect not None, Creds creds=None, value=None): + """ + set_cred_option(desired_aspect, creds=None, value=None) + + This method is used to set options of a :class:`Creds` object based on + an OID key. The options that can be set depends on the mech the credentials + were created with. + + An example of how this can be used would be to set the + GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos credential. The OID string for + this flag is '1.2.752.43.13.29' and it requires no value to be set. This + must be set before the SecurityContext was initialised with the + credentials. + + Args: + desired_aspect (OID): the desired aspect of the Credential to set. + cred_handle (Creds): the Credentials to set, or None to create a new + credential. + value (bytes): the value to set on the desired aspect of the Credential + or None to send GSS_C_EMPTY_BUFFER. + + Returns: + Creds: The output credential. + + Raises: + GSSError + """ + + cdef gss_buffer_desc value_buffer + if value is not None: + value_buffer = gss_buffer_desc(len(value), value) + else: + # GSS_C_EMPTY_BUFFER + value_buffer = gss_buffer_desc(0, NULL) + + cdef Creds output_creds = creds + if output_creds is None: + output_creds = Creds() + + cdef OM_uint32 maj_stat, min_stat + + with nogil: + maj_stat = gss_set_cred_option(&min_stat, + &output_creds.raw_creds, + &desired_aspect.raw_oid, + &value_buffer) + + if maj_stat == GSS_S_COMPLETE: + return output_creds + else: + raise GSSError(maj_stat, min_stat) diff --git a/gssapi/tests/test_raw.py b/gssapi/tests/test_raw.py index 036b26b6..12b467d9 100644 --- a/gssapi/tests/test_raw.py +++ b/gssapi/tests/test_raw.py @@ -843,6 +843,85 @@ def test_inquire_sec_context_by_oid_should_raise_error(self): gb.inquire_sec_context_by_oid.should_raise(gb.GSSError, client_ctx, invalid_oid) + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + @ktu.gssapi_extension_test('password', 'Add Credential with Password') + def test_set_sec_context_option(self): + ntlm_mech = gb.OID.from_int_seq("1.3.6.1.4.1.311.2.2.10") + username = gb.import_name(name=b"user", + name_type=gb.NameType.user) + try: + cred = gb.acquire_cred_with_password(name=username, + password=b"password", + mechs=[ntlm_mech]) + except gb.GSSError: + self.skipTest('You do not have the GSSAPI gss-ntlmssp mech ' + 'installed') + + server = gb.import_name(name=b"server", + name_type=gb.NameType.hostbased_service) + orig_context = gb.init_sec_context(server, creds=cred.creds, + mech=ntlm_mech)[0] + + # GSS_NTLMSSP_RESET_CRYPTO_OID_STRING + reset_mech = gb.OID.from_int_seq("1.3.6.1.4.1.7165.655.1.3") + out_context = gb.set_sec_context_option(reset_mech, + context=orig_context, + value=b"\x00" * 4) + out_context.should_be_a(gb.SecurityContext) + + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + @ktu.gssapi_extension_test('password', 'Add Credential with Password') + def test_set_sec_context_option_fail(self): + ntlm_mech = gb.OID.from_int_seq("1.3.6.1.4.1.311.2.2.10") + username = gb.import_name(name=b"user", + name_type=gb.NameType.user) + try: + cred = gb.acquire_cred_with_password(name=username, + password=b"password", + mechs=[ntlm_mech]) + except gb.GSSError: + self.skipTest('You do not have the GSSAPI gss-ntlmssp mech ' + 'installed') + + server = gb.import_name(name=b"server", + name_type=gb.NameType.hostbased_service) + context = gb.init_sec_context(server, creds=cred.creds, + mech=ntlm_mech)[0] + + # GSS_NTLMSSP_RESET_CRYPTO_OID_STRING + reset_mech = gb.OID.from_int_seq("1.3.6.1.4.1.7165.655.1.3") + + # will raise a GSSError if no data was passed in + gb.set_sec_context_option.should_raise(gb.GSSError, reset_mech, + context) + + @ktu.gssapi_extension_test('set_cred_opt', 'Kitten Set Credential Option') + @ktu.krb_minversion_test('1.14', + 'GSS_KRB5_CRED_NO_CI_FLAGS_X was added in MIT ' + 'krb5 1.14') + def test_set_cred_option(self): + name = gb.import_name(SERVICE_PRINCIPAL, + gb.NameType.kerberos_principal) + # GSS_KRB5_CRED_NO_CI_FLAGS_X + no_ci_flags_x = gb.OID.from_int_seq("1.2.752.43.13.29") + orig_cred = gb.acquire_cred(name).creds + + # nothing much we can test here apart from it doesn't fail and the + # id of the return cred is the same as the input one + output_cred = gb.set_cred_option(no_ci_flags_x, creds=orig_cred) + output_cred.should_be_a(gb.Creds) + + @ktu.gssapi_extension_test('set_cred_opt', 'Kitten Set Credential Option') + def test_set_cred_option_should_raise_error(self): + name = gb.import_name(SERVICE_PRINCIPAL, + gb.NameType.kerberos_principal) + orig_cred = gb.acquire_cred(name).creds + + # this is a fake OID and shouldn't work at all + invalid_oid = gb.OID.from_int_seq("1.2.3.4.5.6.7.8.9") + gb.set_cred_option.should_raise(gb.GSSError, invalid_oid, orig_cred, + b"\x00") + class TestIntEnumFlagSet(unittest.TestCase): def test_create_from_int(self): diff --git a/setup.py b/setup.py index f6527535..06a6f833 100755 --- a/setup.py +++ b/setup.py @@ -276,6 +276,7 @@ def gssapi_modules(lst): extension_file('dce', 'gss_wrap_iov'), extension_file('iov_mic', 'gss_get_mic_iov'), extension_file('ggf', 'gss_inquire_sec_context_by_oid'), + extension_file('set_cred_opt', 'gss_set_cred_option'), # see ext_rfc6680_comp_oid for more information on this split extension_file('rfc6680', 'gss_display_name_ext'),