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