From 66a7107c8355ca5b583480c874c30008ec9875a9 Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Tue, 11 Jul 2017 17:07:27 -0400 Subject: [PATCH] Implement support for GSSAPI extension RFC 5587 RFC 5587 provides extended mech inquiry calls to GSSAPI. This adds the ability to indicate mechs by their mech attrs, along with determining the attrs supported by a mech. These calls are provided as a part of the raw interface and are not exposed in the high-level interface due to not having objects for mechs or attrs. Signed-off-by: Alexander Scheel --- README.txt | 2 + gssapi/raw/__init__.py | 6 ++ gssapi/raw/ext_rfc5587.pyx | 153 +++++++++++++++++++++++++++++++++++++ gssapi/raw/named_tuples.py | 6 ++ gssapi/tests/test_raw.py | 90 ++++++++++++++++++++++ setup.py | 1 + 6 files changed, 258 insertions(+) create mode 100644 gssapi/raw/ext_rfc5587.pyx diff --git a/README.txt b/README.txt index 84012015..b67cea28 100644 --- a/README.txt +++ b/README.txt @@ -136,6 +136,8 @@ Extensions In addition to RFC 2743/2744, Python-GSSAPI also has support for: +* RFC 5587 (Extended GSS Mechanism Inquiry APIs) + * RFC 5588 (GSS-API Extension for Storing Delegated Credentials) * (Additional) Credential Store Extension diff --git a/gssapi/raw/__init__.py b/gssapi/raw/__init__.py index eb1d290b..ef2677f8 100644 --- a/gssapi/raw/__init__.py +++ b/gssapi/raw/__init__.py @@ -69,6 +69,12 @@ except ImportError: pass +# optional RFC 5587 support +try: + from gssapi.raw.ext_rfc5587 import * # noqa +except ImportError: + pass + # optional RFC 5588 support try: from gssapi.raw.ext_rfc5588 import * # noqa diff --git a/gssapi/raw/ext_rfc5587.pyx b/gssapi/raw/ext_rfc5587.pyx new file mode 100644 index 00000000..88a6b4ce --- /dev/null +++ b/gssapi/raw/ext_rfc5587.pyx @@ -0,0 +1,153 @@ +from gssapi.raw.cython_types cimport * +from gssapi.raw.oids cimport OID +from gssapi.raw.cython_converters cimport c_create_oid_set +GSSAPI="BASE" # This ensures that a full module is generated by Cython + +from gssapi.raw.cython_converters cimport c_get_mech_oid_set + +from gssapi.raw.named_tuples import InquireAttrsResult, DisplayAttrResult +from gssapi.raw.misc import GSSError + +cdef extern from "python_gssapi_ext.h": + OM_uint32 gss_indicate_mechs_by_attrs( + OM_uint32 *minor_status, + const gss_OID_set desired_mech_attrs, + const gss_OID_set except_mech_attrs, + const gss_OID_set critical_mech_attrs, + gss_OID_set *mechs) nogil + + OM_uint32 gss_inquire_attrs_for_mech( + OM_uint32 *minor_status, + const gss_OID mech, + gss_OID_set *mech_attrs, + gss_OID_set *known_mech_attrs) nogil + + OM_uint32 gss_display_mech_attr( + OM_uint32 *minor_status, + const gss_OID mech_attr, + gss_buffer_t name, + gss_buffer_t short_desc, + gss_buffer_t long_desc) nogil + + +def indicate_mechs_by_attrs(desired_mech_attrs=None, except_mech_attrs=None, + critical_mech_attrs=None): + """ + indicate_mechs_by_attrs(desired_mech_attrs=None, except_mech_attrs=None, + critical_mech_attrs=None) + Get a set of mechanisms that have the specified attributes. + + Args: + desired_mech_attrs ([OID]): Attributes that the output mechs MUST + offer + except_mech_attrs ([OID]): Attributes that the output mechs MUST NOT + offer + critical_mech_attrs ([OID]): Attributes that the output mechs MUST + understand and offer + + Returns: + [MechType]: a set of mechs which satisfy the given criteria + + Raises: + GSSError + """ + cdef OM_uint32 maj_stat, min_stat + cdef gss_OID_set desired_attrs = GSS_C_NO_OID_SET + cdef gss_OID_set except_attrs = GSS_C_NO_OID_SET + cdef gss_OID_set critical_attrs = GSS_C_NO_OID_SET + cdef gss_OID_set mechs + + if desired_mech_attrs is not None: + desired_attrs = c_get_mech_oid_set(desired_mech_attrs) + + if except_mech_attrs is not None: + except_attrs = c_get_mech_oid_set(except_mech_attrs) + + if critical_mech_attrs is not None: + critical_attrs = c_get_mech_oid_set(critical_mech_attrs) + + with nogil: + maj_stat = gss_indicate_mechs_by_attrs(&min_stat, desired_attrs, + except_attrs, critical_attrs, + &mechs) + + if maj_stat == GSS_S_COMPLETE: + return c_create_oid_set(mechs) + else: + raise GSSError(maj_stat, min_stat) + + +def inquire_attrs_for_mech(OID mech): + """ + inquire_attrs_for_mech(mech) + Gets the set of attrs supported and known by a mechanism. + + Args: + mech (MechType): Mechanism to inquire about + + Returns: + InquireAttrsResult: the results of inquiry; a mech's attributes and + known attributes + + Raises: + GSSError + """ + cdef OM_uint32 maj_stat, min_stat + cdef gss_OID m = GSS_C_NO_OID + cdef gss_OID_set mech_attrs = GSS_C_NO_OID_SET + cdef gss_OID_set known_mech_attrs = GSS_C_NO_OID_SET + + if mech is not None: + m = &mech.raw_oid + + with nogil: + maj_stat = gss_inquire_attrs_for_mech(&min_stat, m, &mech_attrs, + &known_mech_attrs) + + if maj_stat == GSS_S_COMPLETE: + return InquireAttrsResult(c_create_oid_set(mech_attrs), + c_create_oid_set(known_mech_attrs)) + else: + raise GSSError(maj_stat, min_stat) + + +def display_mech_attr(OID attr): + """ + display_mech_attrs(attr) + Returns information about attributes in human readable form. + + Args: + attr (OID): Mechanism attribute to retrive names and descriptions of + + Returns: + DisplayAttrResult: the results of displaying the attribute; mech name, + short description, and long description. + + Raises: + GSSError + """ + cdef OM_uint32 maj_stat, min_stat + cdef gss_OID a = GSS_C_NO_OID + cdef gss_buffer_desc name + cdef gss_buffer_desc short_desc + cdef gss_buffer_desc long_desc + + if attr is not None: + a = &attr.raw_oid + + with nogil: + maj_stat = gss_display_mech_attr(&min_stat, a, &name, &short_desc, + &long_desc) + + if maj_stat == GSS_S_COMPLETE: + out_name = name.value[:name.length] + out_short = short_desc.value[:short_desc.length] + out_long = long_desc.value[:long_desc.length] + + gss_release_buffer(&min_stat, &name) + gss_release_buffer(&min_stat, &short_desc) + gss_release_buffer(&min_stat, &long_desc) + + return DisplayAttrResult(out_name, out_short, out_long) + else: + raise GSSError(maj_stat, min_stat) diff --git a/gssapi/raw/named_tuples.py b/gssapi/raw/named_tuples.py index 519b596d..137f19fd 100644 --- a/gssapi/raw/named_tuples.py +++ b/gssapi/raw/named_tuples.py @@ -64,3 +64,9 @@ GetNameAttributeResult = namedtuple('GetNamedAttributeResult', ['values', 'display_values', 'authenticated', 'complete']) + +InquireAttrsResult = namedtuple('InquireAttrsResult', + ['mech_attrs', 'known_mech_attrs']) + +DisplayAttrResult = namedtuple('DisplayAttrResult', ['name', 'short_desc', + 'long_desc']) diff --git a/gssapi/tests/test_raw.py b/gssapi/tests/test_raw.py index 37b2f05d..fbe54f1c 100644 --- a/gssapi/tests/test_raw.py +++ b/gssapi/tests/test_raw.py @@ -644,6 +644,96 @@ def test_add_cred_with_password(self): new_creds.should_be_a(gb.Creds) + @ktu.gssapi_extension_test('rfc5587', 'RFC 5587') + def test_rfc5587(self): + mechs = gb.indicate_mechs_by_attrs(None, None, None) + + mechs.should_be_a(set) + mechs.shouldnt_be_empty() + + # We need last_attr to be an attribute on last_mech. + # Since mechs is of type set and thus not indexable, these + # are used to track the last visited mech for testing + # purposes, and saves a call to inquire_attrs_for_mech(). + last_attr = None + last_mech = None + + for mech in mechs: + mech.shouldnt_be_none() + mech.should_be_a(gb.OID) + last_mech = mech + + inquire_out = gb.inquire_attrs_for_mech(mech) + mech_attrs = inquire_out.mech_attrs + known_mech_attrs = inquire_out.known_mech_attrs + + mech_attrs.should_be_a(set) + mech_attrs.shouldnt_be_empty() + + known_mech_attrs.should_be_a(set) + known_mech_attrs.shouldnt_be_empty() + + # Verify that we get data for every available + # attribute. Testing the contents of a few known + # attributes is done in test_display_mech_attr(). + for mech_attr in mech_attrs: + mech_attr.shouldnt_be_none() + mech_attr.should_be_a(gb.OID) + + display_out = gb.display_mech_attr(mech_attr) + display_out.name.shouldnt_be_none() + display_out.short_desc.shouldnt_be_none() + display_out.long_desc.shouldnt_be_none() + display_out.name.should_be_a(bytes) + display_out.short_desc.should_be_a(bytes) + display_out.long_desc.should_be_a(bytes) + + last_attr = mech_attr + + for mech_attr in known_mech_attrs: + mech_attr.shouldnt_be_none() + mech_attr.should_be_a(gb.OID) + + display_out = gb.display_mech_attr(mech_attr) + display_out.name.shouldnt_be_none() + display_out.short_desc.shouldnt_be_none() + display_out.long_desc.shouldnt_be_none() + display_out.name.should_be_a(bytes) + display_out.short_desc.should_be_a(bytes) + display_out.long_desc.should_be_a(bytes) + + attrs = set([last_attr]) + + mechs = gb.indicate_mechs_by_attrs(attrs, None, None) + mechs.shouldnt_be_empty() + mechs.should_include(last_mech) + + mechs = gb.indicate_mechs_by_attrs(None, attrs, None) + mechs.shouldnt_include(last_mech) + + mechs = gb.indicate_mechs_by_attrs(None, None, attrs) + mechs.shouldnt_be_empty() + mechs.should_include(last_mech) + + @ktu.gssapi_extension_test('rfc5587', 'RFC 5587') + def test_display_mech_attr(self): + test_attrs = [ + # oid, name, short_desc, long_desc + # Taken from krb5/src/tests/gssapi/t_saslname + [gb.OID.from_int_seq("1.3.6.1.5.5.13.24"), b"GSS_C_MA_CBINDINGS", + b"channel-bindings", b"Mechanism supports channel bindings."], + [gb.OID.from_int_seq("1.3.6.1.5.5.13.1"), + b"GSS_C_MA_MECH_CONCRETE", b"concrete-mech", + b"Mechanism is neither a pseudo-mechanism nor a composite " + b"mechanism."] + ] + + for attr in test_attrs: + display_out = gb.display_mech_attr(attr[0]) + display_out.name.should_be(attr[1]) + display_out.short_desc.should_be(attr[2]) + display_out.long_desc.should_be(attr[3]) + class TestIntEnumFlagSet(unittest.TestCase): def test_create_from_int(self): diff --git a/setup.py b/setup.py index b12ed60b..2f29f0ca 100755 --- a/setup.py +++ b/setup.py @@ -258,6 +258,7 @@ def gssapi_modules(lst): main_file('chan_bindings'), extension_file('s4u', 'gss_acquire_cred_impersonate_name'), extension_file('cred_store', 'gss_store_cred_into'), + extension_file('rfc5587', 'gss_indicate_mechs_by_attrs'), extension_file('rfc5588', 'gss_store_cred'), extension_file('cred_imp_exp', 'gss_import_cred'), extension_file('dce', 'gss_wrap_iov'),