Skip to content

Commit

Permalink
Support DCE IOV functions on macOS
Browse files Browse the repository at this point in the history
The DCE IOV functions on macOS are not exported by any public header on
the GSS.Framework. They can still be accessed by dynamically getting the
address of the known symbols for each function and invoking that.

This allows users of this library on macOS to better interop with
Windows SSPI message encryption which typically require the IOV wrapping
functions that were previously unavailable.
  • Loading branch information
jborean93 committed Jun 4, 2020
1 parent 99ee548 commit 242c6eb
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 145 deletions.
8 changes: 7 additions & 1 deletion gssapi/raw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,20 @@
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)
from gssapi.raw.ext_iov_mic import * # noqa
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
Expand Down
251 changes: 113 additions & 138 deletions gssapi/raw/ext_dce.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,120 @@ else:
from collections.abc import Sequence


cdef extern from "python_gssapi_ext.h":
# 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)
OM_uint32 gss_wrap_iov(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
int conf_req_flag, gss_qop_t qop_req, int *conf_ret,
gss_iov_buffer_desc *iov, int iov_count) nogil

OM_uint32 gss_unwrap_iov(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
int* conf_ret, gss_qop_t *qop_ret,
gss_iov_buffer_desc *iov, int iov_count) nogil

OM_uint32 gss_wrap_iov_length(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
int conf_req, gss_qop_t qop_req,
int *conf_ret, gss_iov_buffer_desc *iov,
int iov_count) nogil

OM_uint32 gss_release_iov_buffer(OM_uint32 *min_stat,
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
IF MACOS_FRAMEWORK_PATH:
# GSS.Framework (macOS) does not expose the 4 gss IOV functions in its
# header. Instead dynamically lookup the function address in the GSS
# library file based on the known symbol name.

from posix.dlfcn cimport dlopen, dlerror, dlsym, dlclose, RTLD_NOW, \
RTLD_GLOBAL

cdef:
void *gss = NULL
void *wrap_iov_fn = NULL
void *unwrap_iov_fn = NULL
void *wrap_iov_length_fn = NULL
void *release_iov_buffer_fn = NULL

cdef void * _dlsym(void *handle, name):
# dlsym() returns NULL for a symbol that is not found but it could
# also return NULL as a valid symbol address. The correct way to test
# on an error is to:
# 1. call dlerror() to clear any existing errors,
# 2. call dlsym, then
# 3. call dlerror() to check whether that is NULL or not
# https://man7.org/linux/man-pages/man3/dlsym.3.html
dlerror()

hn = dlsym(handle, name)
err_msg = dlerror()
if err_msg != NULL:
raise ImportError(err_msg.decode('utf-8', errors='replace'))

return hn

gss = dlopen(MACOS_FRAMEWORK_PATH.encode(), RTLD_NOW | RTLD_GLOBAL)
if gss == NULL:
raise ImportError(dlerror().decode('utf-8', errors='replace'))

try:
# The symbol names were found when inspecting the GSS library file
# 'nm -gU /path/GSS'. Can confirm these symbols are present in the
# latest (10.15) to at least 10.11. Online docs seem to confirm these
# symbols have been present since iOS 6.0 which is roughly when this
# framework was introduced.
wrap_iov_fn = _dlsym(gss, b'__ApplePrivate_gss_wrap_iov')
unwrap_iov_fn = _dlsym(gss, b'__ApplePrivate_gss_unwrap_iov')
wrap_iov_length_fn = _dlsym(gss, b'__ApplePrivate_gss_wrap_iov_length')
release_iov_buffer_fn = _dlsym(gss,
b'__ApplePrivate_gss_release_iov_buffer')

finally:
dlclose(gss)

cdef OM_uint32 gss_wrap_iov(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
int conf_req_flag, gss_qop_t qop_req,
int *conf_ret, gss_iov_buffer_desc *iov,
int iov_count) nogil:

return (<OM_uint32(*)(OM_uint32*, gss_ctx_id_t, int, gss_qop_t, int*,
gss_iov_buffer_desc*, int) nogil>wrap_iov_fn)(
min_stat, ctx_handle, conf_req_flag, qop_req, conf_ret, iov,
iov_count)

cdef OM_uint32 gss_unwrap_iov(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
int* conf_ret, gss_qop_t *qop_ret,
gss_iov_buffer_desc *iov,
int iov_count) nogil:

return (<OM_uint32(*)(OM_uint32*, gss_ctx_id_t, int*, gss_qop_t*,
gss_iov_buffer_desc*, int) nogil>unwrap_iov_fn)(
min_stat, ctx_handle, conf_ret, qop_ret, iov, iov_count)

cdef OM_uint32 gss_wrap_iov_length(OM_uint32 *min_stat,
gss_ctx_id_t ctx_handle, int conf_req,
gss_qop_t qop_req, int *conf_ret,
gss_iov_buffer_desc *iov,
int iov_count) nogil:

return (<OM_uint32(*)(OM_uint32*, gss_ctx_id_t, int, gss_qop_t, int*,
gss_iov_buffer_desc*, int) nogil>wrap_iov_length_fn)(
min_stat, ctx_handle, conf_req, qop_req, conf_ret, iov, iov_count)

cdef OM_uint32 gss_release_iov_buffer(OM_uint32 *min_stat,
gss_iov_buffer_desc *iov,
int iov_count) nogil:

return (<OM_uint32(*)(OM_uint32*, gss_iov_buffer_desc*,
int) nogil>release_iov_buffer_fn)(
min_stat, iov, iov_count)

ELSE:
cdef extern from "python_gssapi_ext.h":
# 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)
OM_uint32 gss_wrap_iov(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
int conf_req_flag, gss_qop_t qop_req,
int *conf_ret, gss_iov_buffer_desc *iov,
int iov_count) nogil

OM_uint32 gss_unwrap_iov(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
int* conf_ret, gss_qop_t *qop_ret,
gss_iov_buffer_desc *iov, int iov_count) nogil

OM_uint32 gss_wrap_iov_length(OM_uint32 *min_stat,
gss_ctx_id_t ctx_handle, int conf_req,
gss_qop_t qop_req, int *conf_ret,
gss_iov_buffer_desc *iov,
int iov_count) nogil

OM_uint32 gss_release_iov_buffer(OM_uint32 *min_stat,
gss_iov_buffer_desc *iov,
int iov_count) nogil


cdef extern from "python_gssapi_ext.h":
gss_iov_buffer_t GSS_C_NO_IOV_BUFFER

OM_uint32 GSS_IOV_BUFFER_TYPE_EMPTY
Expand Down Expand Up @@ -452,109 +533,3 @@ def wrap_iov_length(SecurityContext context not None, IOV message not None,
return <bint>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 = (<char*>output_buffer.value)[:output_buffer.length]
gss_release_buffer(&min_stat, &output_buffer)
return WrapResult(output_message, <bint>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 = (<char*>output_buffer.value)[:output_buffer.length]
gss_release_buffer(&min_stat, &output_buffer)
return UnwrapResult(output_message, <bint>conf_state, qop_state)
else:
raise GSSError(maj_stat, min_stat)
Loading

0 comments on commit 242c6eb

Please sign in to comment.