From d64e1c7905d43231631b1a8b3bca328a740ec95b Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 22 Mar 2018 19:16:12 +1000 Subject: [PATCH] Implement remaining GGF extensions This commit implements the remaining GGF extensions that provides extended support for managing security contexts and credentials. This commit adds the functions gss_set_sec_context_option and gss_set_cred_option. A common use case for gss_set_sec_context_option is with NTLM to reset the crypto handles using the GSS_NTLMSSP_RESET_CRYPTO_OID_LENGTH OID. Draft IETF document for the gss_set_sec_context_option can be found at https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00 Draft IETF document for the gss_set_cred_option can be found at https://tools.ietf.org/html/draft-williams-kitten-channel-bound-flag-00 --- README.txt | 2 + gssapi/raw/ext_ggf.pyx | 126 ++++++++++++++++++++++++++++++++++++++- gssapi/tests/test_raw.py | 61 +++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) 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/ext_ggf.pyx b/gssapi/raw/ext_ggf.pyx index 0d5255bf..1b22397f 100644 --- a/gssapi/raw/ext_ggf.pyx +++ b/gssapi/raw/ext_ggf.pyx @@ -9,6 +9,9 @@ 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 + +Draft IETF document for the gss_set_cred_option can be found at +https://tools.ietf.org/html/draft-williams-kitten-channel-bound-flag-00 """ GSSAPI="BASE" # This ensures that a full module is generated by Cython @@ -32,6 +35,17 @@ cdef extern from "python_gssapi_ext.h": const gss_OID desired_object, gss_buffer_set_t *data_set) nogil + # not in GGF draft but usually lumped together with the others + 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 + + 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): @@ -93,7 +107,7 @@ 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: @@ -127,3 +141,113 @@ def inquire_sec_context_by_oid(SecurityContext context not None, return py_tokens else: raise GSSError(maj_stat, min_stat) + + +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: + GSS_ERROR + """ + + 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) + + +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 reset's 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: + GSS_ERROR + """ + + 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/tests/test_raw.py b/gssapi/tests/test_raw.py index 20df9f88..929a628b 100644 --- a/gssapi/tests/test_raw.py +++ b/gssapi/tests/test_raw.py @@ -847,6 +847,67 @@ 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.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) + id(orig_cred).should_be(id(output_cred)) + + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + 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") + + # TODO: get these tests to detect gss-ntlmssp once it is installed + """ + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + @ktu.gssapi_extension_test('password', 'Add Credential with Password') + def test_set_sec_context_option(self): + # TODO: skip the test if gss-ntlmssp is not installed, MIT krb5 has not + # implemented any OID options for set_sec_context_option + ntlm_mech = gb.OID.from_int_seq("1.3.6.1.4.1.311.2.2.10") + reset_mech = gb.OID.from_int_seq("1.3.6.1.4.1.7165.655.1.3") + + username = gb.import_name(name=b"user", + name_type=gb.NameType.user) + server = gb.import_name(name=b"server", + name_type=gb.NameType.hostbased_service) + cred = gb.acquire_cred_with_password(name=username, + password=b"password", + mechs=[ntlm_mech]) + orig_context = gb.init_sec_context(server, creds=cred.creds, + mech=ntlm_mech)[0] + + out_context = gb.set_sec_context_option(reset_mech, + context=orig_context, + value=b"\x00" * 4) + id(orig_context).should_be(id(out_context)) + """ + + @ktu.gssapi_extension_test('ggf', 'Global Grid Forum') + def test_set_sec_context_option_fail(self): + # because MIT krb5 doesn't implement any OID's for + # gss_set_sec_context_option, we just need to query any OID and it will + # raise an exception + gb.set_sec_context_option.should_raise(gb.GSSError, + gb.OID.from_int_seq("1.2.3.4")) + class TestIntEnumFlagSet(unittest.TestCase): def test_create_from_int(self):