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