diff --git a/docs/source/gssapi.raw.rst b/docs/source/gssapi.raw.rst index bebdeebd..0083826e 100644 --- a/docs/source/gssapi.raw.rst +++ b/docs/source/gssapi.raw.rst @@ -116,6 +116,10 @@ DCE (IOV/AEAD) Extensions :members: :undoc-members: +.. automodule:: gssapi.raw.ext_dce_aead + :members: + :undoc-members: + IOV MIC Extensions ~~~~~~~~~~~~~~~~~~ diff --git a/gssapi/raw/__init__.py b/gssapi/raw/__init__.py index a74b74e9..b2320991 100644 --- a/gssapi/raw/__init__.py +++ b/gssapi/raw/__init__.py @@ -111,7 +111,7 @@ except ImportError: pass -# optional DCE (IOV/AEAD) support +# optional DCE (IOV) support try: from gssapi.raw.ext_dce import * # noqa # optional IOV MIC support (requires DCE support) @@ -119,6 +119,12 @@ except ImportError: pass +# optional DCE (AEAD) support +try: + from gssapi.raw.ext_dce_aead import * # noqa +except ImportError: + pass + # optional RFC 6680 support try: from gssapi.raw.ext_rfc6680 import * # noqa diff --git a/gssapi/raw/ext_dce.pyx b/gssapi/raw/ext_dce.pyx index 52d87e5d..a6aa8de9 100644 --- a/gssapi/raw/ext_dce.pyx +++ b/gssapi/raw/ext_dce.pyx @@ -8,15 +8,30 @@ from gssapi.raw.sec_contexts cimport SecurityContext from gssapi.raw.misc import GSSError from gssapi.raw import types as gssapi_types -from gssapi.raw.named_tuples import IOVUnwrapResult, WrapResult, UnwrapResult +from gssapi.raw.named_tuples import IOVUnwrapResult from collections import namedtuple from collections.abc import Sequence from enum import IntEnum from gssapi.raw._enum_extensions import ExtendableEnum +# Kept for backwards compatibility - functions used to be declared here +try: + from gssapi.raw.ext_dce_aead import wrap_aead, unwrap_aead +except ImportError: + pass + cdef extern from "python_gssapi_ext.h": + """ + #ifdef OSX_HAS_GSS_FRAMEWORK + #define gss_wrap_iov __ApplePrivate_gss_wrap_iov + #define gss_unwrap_iov __ApplePrivate_gss_unwrap_iov + #define gss_wrap_iov_length __ApplePrivate_gss_wrap_iov_length + #define gss_release_iov_buffer __ApplePrivate_gss_release_iov_buffer + #endif + """ + # NB(directxman12): this wiki page has a different argument order # than the header file, and uses size_t instead of int # (this file matches the header file) @@ -37,18 +52,6 @@ cdef extern from "python_gssapi_ext.h": gss_iov_buffer_desc *iov, int iov_count) nogil - OM_uint32 gss_wrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle, - int conf_req, gss_qop_t qop_req, - gss_buffer_t input_assoc_buffer, - gss_buffer_t input_payload_buffer, int *conf_ret, - gss_buffer_t output_message_buffer) nogil - - OM_uint32 gss_unwrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle, - gss_buffer_t input_message_buffer, - gss_buffer_t input_assoc_buffer, - gss_buffer_t output_payload_buffer, - int *conf_ret, gss_qop_t *qop_ret) nogil - gss_iov_buffer_t GSS_C_NO_IOV_BUFFER OM_uint32 GSS_IOV_BUFFER_TYPE_EMPTY @@ -447,109 +450,3 @@ def wrap_iov_length(SecurityContext context not None, IOV message not None, return conf_used else: raise GSSError(maj_stat, min_stat) - - -def wrap_aead(SecurityContext context not None, bytes message not None, - bytes associated=None, confidential=True, qop=None): - """ - wrap_aead(context, message, associated=None, confidential=True, qop=None) - Wrap/Encrypt an AEAD message. - - This method takes an input message and associated data, - and outputs and AEAD message. - - Args: - context (SecurityContext): the current security context - message (bytes): the message to wrap or encrypt - associated (bytes): associated data to go with the message - confidential (bool): whether or not to encrypt the message (True), - or just wrap it with a MIC (False) - qop (int): the desired Quality of Protection - (or None for the default QoP) - - Returns: - WrapResult: the wrapped/encrypted total message, and whether or not - encryption was actually used - - Raises: - GSSError - """ - - cdef int conf_req = confidential - cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT - cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message), - message) - - cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER - cdef gss_buffer_desc assoc_buffer - if associated is not None: - assoc_buffer = gss_buffer_desc(len(associated), associated) - assoc_buffer_ptr = &assoc_buffer - - cdef int conf_used - # GSS_C_EMPTY_BUFFER - cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL) - - cdef OM_uint32 maj_stat, min_stat - - with nogil: - maj_stat = gss_wrap_aead(&min_stat, context.raw_ctx, conf_req, qop_req, - assoc_buffer_ptr, &message_buffer, - &conf_used, &output_buffer) - - if maj_stat == GSS_S_COMPLETE: - output_message = (output_buffer.value)[:output_buffer.length] - gss_release_buffer(&min_stat, &output_buffer) - return WrapResult(output_message, conf_used) - else: - raise GSSError(maj_stat, min_stat) - - -def unwrap_aead(SecurityContext context not None, bytes message not None, - bytes associated=None): - """ - unwrap_aead(context, message, associated=None) - Unwrap/Decrypt an AEAD message. - - This method takes an encrpyted/wrapped AEAD message and some associated - data, and returns an unwrapped/decrypted message. - - Args: - context (SecurityContext): the current security context - message (bytes): the AEAD message to unwrap or decrypt - associated (bytes): associated data that goes with the message - - Returns: - UnwrapResult: the unwrapped/decrypted message, whether or on - encryption was used, and the QoP used - - Raises: - GSSError - """ - - cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message) - - cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER - cdef gss_buffer_desc assoc_buffer - if associated is not None: - assoc_buffer = gss_buffer_desc(len(associated), associated) - assoc_buffer_ptr = &assoc_buffer - - # GSS_C_EMPTY_BUFFER - cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL) - cdef int conf_state - cdef gss_qop_t qop_state - - cdef OM_uint32 maj_stat, min_stat - - with nogil: - maj_stat = gss_unwrap_aead(&min_stat, context.raw_ctx, &input_buffer, - assoc_buffer_ptr, &output_buffer, - &conf_state, &qop_state) - - if maj_stat == GSS_S_COMPLETE: - output_message = (output_buffer.value)[:output_buffer.length] - gss_release_buffer(&min_stat, &output_buffer) - return UnwrapResult(output_message, conf_state, qop_state) - else: - raise GSSError(maj_stat, min_stat) diff --git a/gssapi/raw/ext_dce_aead.pyx b/gssapi/raw/ext_dce_aead.pyx new file mode 100644 index 00000000..4272b27b --- /dev/null +++ b/gssapi/raw/ext_dce_aead.pyx @@ -0,0 +1,127 @@ +GSSAPI="BASE" # This ensures that a full module is generated by Cython + +from gssapi.raw.cython_types cimport * +from gssapi.raw.sec_contexts cimport SecurityContext + +from gssapi.raw.misc import GSSError +from gssapi.raw.named_tuples import WrapResult, UnwrapResult + + +cdef extern from "python_gssapi_ext.h": + OM_uint32 gss_wrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle, + int conf_req, gss_qop_t qop_req, + gss_buffer_t input_assoc_buffer, + gss_buffer_t input_payload_buffer, int *conf_ret, + gss_buffer_t output_message_buffer) nogil + + OM_uint32 gss_unwrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle, + gss_buffer_t input_message_buffer, + gss_buffer_t input_assoc_buffer, + gss_buffer_t output_payload_buffer, + int *conf_ret, gss_qop_t *qop_ret) nogil + + +def wrap_aead(SecurityContext context not None, bytes message not None, + bytes associated=None, confidential=True, qop=None): + """ + wrap_aead(context, message, associated=None, confidential=True, qop=None) + Wrap/Encrypt an AEAD message. + + This method takes an input message and associated data, + and outputs and AEAD message. + + Args: + context (SecurityContext): the current security context + message (bytes): the message to wrap or encrypt + associated (bytes): associated data to go with the message + confidential (bool): whether or not to encrypt the message (True), + or just wrap it with a MIC (False) + qop (int): the desired Quality of Protection + (or None for the default QoP) + + Returns: + WrapResult: the wrapped/encrypted total message, and whether or not + encryption was actually used + + Raises: + GSSError + """ + + cdef int conf_req = confidential + cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT + cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message), + message) + + cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER + cdef gss_buffer_desc assoc_buffer + if associated is not None: + assoc_buffer = gss_buffer_desc(len(associated), associated) + assoc_buffer_ptr = &assoc_buffer + + cdef int conf_used + # GSS_C_EMPTY_BUFFER + cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL) + + cdef OM_uint32 maj_stat, min_stat + + with nogil: + maj_stat = gss_wrap_aead(&min_stat, context.raw_ctx, conf_req, qop_req, + assoc_buffer_ptr, &message_buffer, + &conf_used, &output_buffer) + + if maj_stat == GSS_S_COMPLETE: + output_message = (output_buffer.value)[:output_buffer.length] + gss_release_buffer(&min_stat, &output_buffer) + return WrapResult(output_message, conf_used) + else: + raise GSSError(maj_stat, min_stat) + + +def unwrap_aead(SecurityContext context not None, bytes message not None, + bytes associated=None): + """ + unwrap_aead(context, message, associated=None) + Unwrap/Decrypt an AEAD message. + + This method takes an encrpyted/wrapped AEAD message and some associated + data, and returns an unwrapped/decrypted message. + + Args: + context (SecurityContext): the current security context + message (bytes): the AEAD message to unwrap or decrypt + associated (bytes): associated data that goes with the message + + Returns: + UnwrapResult: the unwrapped/decrypted message, whether or on + encryption was used, and the QoP used + + Raises: + GSSError + """ + + cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message) + + cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER + cdef gss_buffer_desc assoc_buffer + if associated is not None: + assoc_buffer = gss_buffer_desc(len(associated), associated) + assoc_buffer_ptr = &assoc_buffer + + # GSS_C_EMPTY_BUFFER + cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL) + cdef int conf_state + cdef gss_qop_t qop_state + + cdef OM_uint32 maj_stat, min_stat + + with nogil: + maj_stat = gss_unwrap_aead(&min_stat, context.raw_ctx, &input_buffer, + assoc_buffer_ptr, &output_buffer, + &conf_state, &qop_state) + + if maj_stat == GSS_S_COMPLETE: + output_message = (output_buffer.value)[:output_buffer.length] + gss_release_buffer(&min_stat, &output_buffer) + return UnwrapResult(output_message, conf_state, qop_state) + else: + raise GSSError(maj_stat, min_stat) diff --git a/gssapi/raw/python_gssapi_ext.h b/gssapi/raw/python_gssapi_ext.h index 3d32a4b8..f20a0770 100644 --- a/gssapi/raw/python_gssapi_ext.h +++ b/gssapi/raw/python_gssapi_ext.h @@ -1,5 +1,37 @@ #ifdef OSX_HAS_GSS_FRAMEWORK #include + +// GSS includes these in gssapi_private.h but these aren't present on the host +// so just explicitly define them. They have been slightly modified to follow +// the krb5 standard. The originals can be found at (introduced macOS 10.7). +// https://opensource.apple.com/source/Heimdal/Heimdal-172.18/lib/gssapi/gssapi/gssapi_spi.h.auto.html +OM_uint32 __ApplePrivate_gss_unwrap_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int *conf_state, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count); + +OM_uint32 __ApplePrivate_gss_wrap_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + int *conf_state, + gss_iov_buffer_desc *iov, + int iov_count); + +OM_uint32 __ApplePrivate_gss_wrap_iov_length(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + int *conf_state, + gss_iov_buffer_desc *iov, + int iov_count); + +OM_uint32 __ApplePrivate_gss_release_iov_buffer(OM_uint32 *minor_status, + gss_iov_buffer_desc *iov, + int iov_count); + #else #if defined(__MINGW32__) && defined(__MSYS__) #include diff --git a/gssapi/tests/test_raw.py b/gssapi/tests/test_raw.py index 3742b279..737ad602 100644 --- a/gssapi/tests/test_raw.py +++ b/gssapi/tests/test_raw.py @@ -1304,7 +1304,7 @@ def test_basic_iov_wrap_unwrap_prealloc(self): self.assertEqual(init_message[2].value, init_data) self.assertEqual(init_message[3].value, init_other_data) - @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)') + @ktu.gssapi_extension_test('dce', 'DCE (IOV)') def test_basic_iov_wrap_unwrap_autoalloc(self): init_data = b'some encrypted data' init_other_data = b'some other encrypted data' @@ -1334,7 +1334,7 @@ def test_basic_iov_wrap_unwrap_autoalloc(self): self.assertEqual(init_message[2].value, init_data) self.assertEqual(init_message[3].value, init_other_data) - @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)') + @ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)') def test_basic_aead_wrap_unwrap(self): assoc_data = b'some sig data' wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message", @@ -1353,7 +1353,7 @@ def test_basic_aead_wrap_unwrap(self): self.assertIsInstance(qop, int) self.assertGreaterEqual(qop, 0) - @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)') + @ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)') def test_basic_aead_wrap_unwrap_no_assoc(self): wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message") self.assertIsInstance(wrapped_message, bytes) @@ -1370,7 +1370,7 @@ def test_basic_aead_wrap_unwrap_no_assoc(self): self.assertIsInstance(qop, int) self.assertGreaterEqual(qop, 0) - @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)') + @ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)') def test_basic_aead_wrap_unwrap_bad_assoc_raises_error(self): assoc_data = b'some sig data' wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message", diff --git a/setup.py b/setup.py index f1c0aeca..1d7486a0 100755 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ def get_output(*args, **kwargs): if compile_args is None: if osx_has_gss_framework: - compile_args = ['-framework', 'GSS', '-DOSX_HAS_GSS_FRAMEWORK'] + compile_args = ['-DOSX_HAS_GSS_FRAMEWORK'] elif winkrb_path: compile_args = [ '-I%s' % os.path.join(winkrb_path, 'include'), @@ -382,7 +382,10 @@ def gssapi_modules(lst): extension_file('rfc5588', 'gss_store_cred'), extension_file('rfc5801', 'gss_inquire_saslname_for_mech'), extension_file('cred_imp_exp', 'gss_import_cred'), - extension_file('dce', 'gss_wrap_iov'), + extension_file('dce', + '__ApplePrivate_gss_wrap_iov' if osx_has_gss_framework + else 'gss_wrap_iov'), + extension_file('dce_aead', 'gss_wrap_aead'), 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'),