diff --git a/docs/source/gssapi.raw.rst b/docs/source/gssapi.raw.rst index 86b7b6a6..6248c1c4 100644 --- a/docs/source/gssapi.raw.rst +++ b/docs/source/gssapi.raw.rst @@ -57,6 +57,22 @@ raw Package :undoc-members: :show-inheritance: +:mod:`ext_password` Module +--------------------- + +.. automodule:: gssapi.raw.ext_password + :members: + :undoc-members: + :show-inheritance: + +:mod:`ext_password_add` Module +--------------------- + +.. automodule:: gssapi.raw.ext_password_add + :members: + :undoc-members: + :show-inheritance: + :mod:`mech_krb5` Module ----------------------- diff --git a/gssapi/raw/__init__.py b/gssapi/raw/__init__.py index 054855d9..a17b4569 100644 --- a/gssapi/raw/__init__.py +++ b/gssapi/raw/__init__.py @@ -36,3 +36,10 @@ import gssapi.raw.mech_krb5 # noqa except ImportError: pass + +# optional password support +try: + from gssapi.raw.ext_password import * # noqa + from gssapi.raw.ext_password_add import * # noqa +except ImportError: + pass diff --git a/gssapi/raw/ext_password.pyx b/gssapi/raw/ext_password.pyx new file mode 100644 index 00000000..3775bbdb --- /dev/null +++ b/gssapi/raw/ext_password.pyx @@ -0,0 +1,89 @@ +GSSAPI="BASE" # This ensures that a full module is generated by Cythin + +# Due to a bug in MIT Kerberos, add_cred_with_password was not properly +# exported for some time. In order to work around this, +# add_cred_with_password is in its own file. For more information, see: +# https://github.com/krb5/krb5/pull/244 + +from gssapi.raw.cython_types cimport * +from gssapi.raw.cython_converters cimport c_get_mech_oid_set +from gssapi.raw.cython_converters cimport c_create_oid_set +from gssapi.raw.cython_converters cimport c_py_ttl_to_c, c_c_ttl_to_py +from gssapi.raw.creds cimport Creds +from gssapi.raw.names cimport Name + +from gssapi.raw.misc import GSSError +from gssapi.raw.named_tuples import AcquireCredResult + +cdef extern from "gssapi/gssapi_ext.h": + OM_uint32 gss_acquire_cred_with_password(OM_uint32 *min_stat, + const gss_name_t desired_name, + const gss_buffer_t password, + OM_uint32 ttl, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_creds, + gss_OID_set *actual_mechs, + OM_uint32 *actual_ttl) nogil + + +def acquire_cred_with_password(Name name not None, password not None, + lifetime=None, mechs=None): + """ + Acquire credentials through provided password. + + This function is originally from Solaris and is not documented by either + MIT or Heimdal. + + Args: + name (Name): the name to acquire credentials for + password (str): the password used to acquire credentialss with + lifetime (int): the lifetime for the credentials (or None for + indefinite) + mechs ([MechType]): the desired mechanisms for which the credentials + should work (or None for the default set) + + Returns: + AcquireCredResult: the resulting credentials, the actual mechanisms + with which they may be used, and their actual lifetime (or None for + indefinite or not supported) + + Raises: + GSSError + """ + + cdef gss_buffer_desc password_buffer = gss_buffer_desc(len(password), + password) + + cdef OM_uint32 input_ttl = c_py_ttl_to_c(lifetime) + + cdef gss_OID_set desired_mechs + if mechs is not None: + desired_mechs = c_get_mech_oid_set(mechs) + else: + desired_mechs = GSS_C_NO_OID_SET + + cdef gss_cred_usage_t c_usage = GSS_C_INITIATE + + cdef gss_cred_id_t creds + cdef gss_OID_set actual_mechs + cdef OM_uint32 actual_ttl + + cdef OM_uint32 maj_stat, min_stat + + with nogil: + maj_stat = gss_acquire_cred_with_password( + &min_stat, name.raw_name, &password_buffer, input_ttl, + desired_mechs, c_usage, &creds, &actual_mechs, &actual_ttl) + + cdef OM_uint32 tmp_min_stat + if mechs is not None: + gss_release_oid_set(&tmp_min_stat, &desired_mechs) + + cdef Creds rc = Creds() + if maj_stat == GSS_S_COMPLETE: + rc.raw_creds = creds + return AcquireCredResult(rc, c_create_oid_set(actual_mechs), + c_c_ttl_to_py(actual_ttl)) + else: + raise GSSError(maj_stat, min_stat) diff --git a/gssapi/raw/ext_password_add.pyx b/gssapi/raw/ext_password_add.pyx new file mode 100644 index 00000000..4262546b --- /dev/null +++ b/gssapi/raw/ext_password_add.pyx @@ -0,0 +1,105 @@ +GSSAPI="BASE" # This ensures that a full module is generated by Cythin + +# Due to a bug in MIT Kerberos, add_cred_with_password was not properly +# exported for some time. In order to work around this, +# add_cred_with_password is in its own file. For more information, see: +# https://github.com/krb5/krb5/pull/244 + +from gssapi.raw.cython_types cimport * +from gssapi.raw.cython_converters cimport c_get_mech_oid_set +from gssapi.raw.cython_converters cimport c_create_oid_set +from gssapi.raw.cython_converters cimport c_py_ttl_to_c, c_c_ttl_to_py +from gssapi.raw.creds cimport Creds +from gssapi.raw.names cimport Name +from gssapi.raw.oids cimport OID + +from gssapi.raw.misc import GSSError +from gssapi.raw.named_tuples import AddCredResult + +cdef extern from "gssapi/gssapi_ext.h": + OM_uint32 gss_add_cred_with_password(OM_uint32 *min_stat, + const gss_cred_id_t input_cred_handle, + const gss_name_t desired_name, + const gss_OID desired_mech, + const gss_buffer_t password, + gss_cred_usage_t cred_usage, + OM_uint32 initiator_ttl, + OM_uint32 acceptor_ttl, + gss_cred_id_t *output_creds, + gss_OID_set *actual_mechs, + OM_uint32 *actual_init_ttl, + OM_uint32 *actual_accept_ttl) nogil + + +def add_cred_with_password(Creds input_cred not None, Name name not None, + OID mech not None, password not None, + usage="initiate", init_lifetime=None, + accept_lifetime=None): + + """ + Add a credential-element to a credential using provided password. + + This function is originally from Solaris and is not documented by either + MIT or Heimdal. + + Args: + input_cred (Creds): the credentials to add to + name (Name): the name to acquire credentials for + mech (MechType): the desired mechanism. Note that this is both + singular and required + password (str): the password used to acquire credentialss with + usage (str): the usage type for the credentials: may be + 'initiate', 'accept', or 'both' + init_lifetime (int): the lifetime for the credentials to remain valid + when using them to initiate security contexts (or None for + indefinite) + accept_lifetime (int): the lifetime for the credentials to remain + valid when using them to accept security contexts (or None for + indefinite) + + Returns: + AddCredResult: the actual mechanisms with which the credentials may be + used, the actual initiator TTL, and the actual acceptor TTL (the TTLs + may be None for indefinite or not supported) + + Raises: + GSSError + """ + + cdef gss_buffer_desc password_buffer = gss_buffer_desc(len(password), + password) + + cdef gss_cred_usage_t c_usage + if usage == "initiate": + c_usage = GSS_C_INITIATE + elif usage == "accept": + c_usage = GSS_C_ACCEPT + else: + c_usage = GSS_C_BOTH + + cdef OM_uint32 input_initiator_ttl = c_py_ttl_to_c(init_lifetime) + cdef OM_uint32 input_acceptor_ttl = c_py_ttl_to_c(accept_lifetime) + + cdef gss_cred_id_t creds + cdef gss_OID_set actual_mechs + cdef OM_uint32 actual_initiator_ttl + cdef OM_uint32 actual_acceptor_ttl + + cdef OM_uint32 maj_stat, min_stat + + with nogil: + maj_stat = gss_add_cred_with_password( + &min_stat, input_cred.raw_creds, name.raw_name, &mech.raw_oid, + &password_buffer, c_usage, input_initiator_ttl, + input_acceptor_ttl, &creds, &actual_mechs, &actual_initiator_ttl, + &actual_acceptor_ttl) + + cdef Creds rc + if maj_stat == GSS_S_COMPLETE: + rc = Creds() + rc.raw_creds = creds + return AddCredResult(rc, c_create_oid_set(actual_mechs), + c_c_ttl_to_py(actual_initiator_ttl), + c_c_ttl_to_py(actual_acceptor_ttl)) + else: + raise GSSError(maj_stat, min_stat) diff --git a/gssapi/tests/test_raw.py b/gssapi/tests/test_raw.py index 73033c4e..c1532f75 100644 --- a/gssapi/tests/test_raw.py +++ b/gssapi/tests/test_raw.py @@ -53,6 +53,9 @@ def tearDownClass(cls): class TestBaseUtilities(_GSSAPIKerberosTestCase): + def setUp(self): + self.realm.kinit(SERVICE_PRINCIPAL.decode("UTF-8"), flags=['-k']) + def test_indicate_mechs(self): mechs = gb.indicate_mechs() @@ -449,6 +452,48 @@ def test_inquire_mechs_for_name(self): res.shouldnt_be_none() res.should_include(gb.MechType.kerberos) + @_extension_test('password', 'Password') + def test_acquire_cred_with_password(self): + password = self.realm.password('user') + self.realm.kinit(self.realm.user_princ, password=password) + + name = gb.import_name('user', gb.NameType.kerberos_principal) + + imp_resp = gb.acquire_cred_with_password(name, password) + imp_resp.shouldnt_be_none() + + imp_creds, actual_mechs, output_ttl = imp_resp + + imp_creds.shouldnt_be_none() + imp_creds.should_be_a(gb.Creds) + + actual_mechs.shouldnt_be_empty() + actual_mechs.should_include(gb.MechType.kerberos) + + output_ttl.should_be_a(int) + + @_extension_test('password_add', 'Password (add)') + def test_add_cred_with_password(self): + password = self.realm.password('user') + self.realm.kinit(self.realm.user_princ, password=password) + + name = gb.import_name('user', gb.NameType.kerberos_principal) + + input_creds = gb.Creds() + imp_resp = gb.add_cred_with_password(input_creds, name, + gb.MechType.kerberos, password) + imp_resp.shouldnt_be_none() + + new_creds, actual_mechs, output_init_ttl, output_accept_ttl = imp_resp + + actual_mechs.shouldnt_be_empty() + actual_mechs.should_include(gb.MechType.kerberos) + + output_init_ttl.should_be_a(int) + output_accept_ttl.should_be_a(int) + + new_creds.should_be_a(gb.Creds) + class TestIntEnumFlagSet(unittest.TestCase): def test_create_from_int(self): diff --git a/setup.py b/setup.py index 434f9354..00a656e1 100755 --- a/setup.py +++ b/setup.py @@ -191,6 +191,10 @@ def gssapi_modules(lst): extension_file('cred_store', 'gss_store_cred_into'), extension_file('rfc5588', 'gss_store_cred'), extension_file('cred_imp_exp', 'gss_import_cred'), + + # see ext_password{,_add}.pyx for more information on this split + extension_file('password', 'gss_acquire_cred_with_password'), + extension_file('password_add', 'gss_add_cred_with_password'), ]), keywords=['gssapi', 'security'], install_requires=[