Skip to content

Commit 07d0220

Browse files
committed
Support DCE IOV functions on macOS
The DCE IOV functions on macOS are not exported by any public header on the GSS.Framework. This PR defines some macros in the C file that when compiled against the GSS.Framework will alias the private symbols to the ones expected by GSSAPI. While the symbols are considered to be private they haven't changed across any macOS version since the introduction of GSS.Framework and the inclusion of ext_dce is still dependent on whether the symbol is present at compile time. 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.
1 parent 69f8b3a commit 07d0220

File tree

6 files changed

+201
-126
lines changed

6 files changed

+201
-126
lines changed

Diff for: gssapi/raw/__init__.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,20 @@
111111
except ImportError:
112112
pass
113113

114-
# optional DCE (IOV/AEAD) support
114+
# optional DCE (IOV) support
115115
try:
116116
from gssapi.raw.ext_dce import * # noqa
117117
# optional IOV MIC support (requires DCE support)
118118
from gssapi.raw.ext_iov_mic import * # noqa
119119
except ImportError:
120120
pass
121121

122+
# optional DCE (AEAD) support
123+
try:
124+
from gssapi.raw.ext_dce_aead import * # noqa
125+
except ImportError:
126+
pass
127+
122128
# optional RFC 6680 support
123129
try:
124130
from gssapi.raw.ext_rfc6680 import * # noqa

Diff for: gssapi/raw/ext_dce.pyx

+20-119
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,34 @@ from gssapi.raw.sec_contexts cimport SecurityContext
88

99
from gssapi.raw.misc import GSSError
1010
from gssapi.raw import types as gssapi_types
11-
from gssapi.raw.named_tuples import IOVUnwrapResult, WrapResult, UnwrapResult
11+
from gssapi.raw.named_tuples import IOVUnwrapResult
1212
from collections import namedtuple
1313
from collections.abc import Sequence
1414

1515
from enum import IntEnum
1616
from gssapi.raw._enum_extensions import ExtendableEnum
1717

18+
# Kept for backwards compatibility - functions used to be declared here
19+
try:
20+
from gssapi.raw.ext_dce_aead import wrap_aead, unwrap_aead
21+
except ImportError:
22+
pass
23+
1824

1925
cdef extern from "python_gssapi_ext.h":
26+
"""
27+
#ifdef OSX_HAS_GSS_FRAMEWORK
28+
#define gss_wrap_iov(t, u, v, w, x, y, z) \
29+
__ApplePrivate_gss_wrap_iov(t, u, v, w, x, y, z)
30+
#define gss_unwrap_iov(u, v, w, x, y, z) \
31+
__ApplePrivate_gss_unwrap_iov(u, v, w, x, y, z)
32+
#define gss_wrap_iov_length(t, u, v, w, x, y, z) \
33+
__ApplePrivate_gss_wrap_iov_length(t, u, v, w, x, y, z)
34+
#define gss_release_iov_buffer(x, y, z) \
35+
__ApplePrivate_gss_release_iov_buffer(x, y, z)
36+
#endif
37+
"""
38+
2039
# NB(directxman12): this wiki page has a different argument order
2140
# than the header file, and uses size_t instead of int
2241
# (this file matches the header file)
@@ -37,18 +56,6 @@ cdef extern from "python_gssapi_ext.h":
3756
gss_iov_buffer_desc *iov,
3857
int iov_count) nogil
3958

40-
OM_uint32 gss_wrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
41-
int conf_req, gss_qop_t qop_req,
42-
gss_buffer_t input_assoc_buffer,
43-
gss_buffer_t input_payload_buffer, int *conf_ret,
44-
gss_buffer_t output_message_buffer) nogil
45-
46-
OM_uint32 gss_unwrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
47-
gss_buffer_t input_message_buffer,
48-
gss_buffer_t input_assoc_buffer,
49-
gss_buffer_t output_payload_buffer,
50-
int *conf_ret, gss_qop_t *qop_ret) nogil
51-
5259
gss_iov_buffer_t GSS_C_NO_IOV_BUFFER
5360

5461
OM_uint32 GSS_IOV_BUFFER_TYPE_EMPTY
@@ -447,109 +454,3 @@ def wrap_iov_length(SecurityContext context not None, IOV message not None,
447454
return <bint>conf_used
448455
else:
449456
raise GSSError(maj_stat, min_stat)
450-
451-
452-
def wrap_aead(SecurityContext context not None, bytes message not None,
453-
bytes associated=None, confidential=True, qop=None):
454-
"""
455-
wrap_aead(context, message, associated=None, confidential=True, qop=None)
456-
Wrap/Encrypt an AEAD message.
457-
458-
This method takes an input message and associated data,
459-
and outputs and AEAD message.
460-
461-
Args:
462-
context (SecurityContext): the current security context
463-
message (bytes): the message to wrap or encrypt
464-
associated (bytes): associated data to go with the message
465-
confidential (bool): whether or not to encrypt the message (True),
466-
or just wrap it with a MIC (False)
467-
qop (int): the desired Quality of Protection
468-
(or None for the default QoP)
469-
470-
Returns:
471-
WrapResult: the wrapped/encrypted total message, and whether or not
472-
encryption was actually used
473-
474-
Raises:
475-
GSSError
476-
"""
477-
478-
cdef int conf_req = confidential
479-
cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
480-
cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
481-
message)
482-
483-
cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
484-
cdef gss_buffer_desc assoc_buffer
485-
if associated is not None:
486-
assoc_buffer = gss_buffer_desc(len(associated), associated)
487-
assoc_buffer_ptr = &assoc_buffer
488-
489-
cdef int conf_used
490-
# GSS_C_EMPTY_BUFFER
491-
cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
492-
493-
cdef OM_uint32 maj_stat, min_stat
494-
495-
with nogil:
496-
maj_stat = gss_wrap_aead(&min_stat, context.raw_ctx, conf_req, qop_req,
497-
assoc_buffer_ptr, &message_buffer,
498-
&conf_used, &output_buffer)
499-
500-
if maj_stat == GSS_S_COMPLETE:
501-
output_message = (<char*>output_buffer.value)[:output_buffer.length]
502-
gss_release_buffer(&min_stat, &output_buffer)
503-
return WrapResult(output_message, <bint>conf_used)
504-
else:
505-
raise GSSError(maj_stat, min_stat)
506-
507-
508-
def unwrap_aead(SecurityContext context not None, bytes message not None,
509-
bytes associated=None):
510-
"""
511-
unwrap_aead(context, message, associated=None)
512-
Unwrap/Decrypt an AEAD message.
513-
514-
This method takes an encrpyted/wrapped AEAD message and some associated
515-
data, and returns an unwrapped/decrypted message.
516-
517-
Args:
518-
context (SecurityContext): the current security context
519-
message (bytes): the AEAD message to unwrap or decrypt
520-
associated (bytes): associated data that goes with the message
521-
522-
Returns:
523-
UnwrapResult: the unwrapped/decrypted message, whether or on
524-
encryption was used, and the QoP used
525-
526-
Raises:
527-
GSSError
528-
"""
529-
530-
cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message)
531-
532-
cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
533-
cdef gss_buffer_desc assoc_buffer
534-
if associated is not None:
535-
assoc_buffer = gss_buffer_desc(len(associated), associated)
536-
assoc_buffer_ptr = &assoc_buffer
537-
538-
# GSS_C_EMPTY_BUFFER
539-
cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
540-
cdef int conf_state
541-
cdef gss_qop_t qop_state
542-
543-
cdef OM_uint32 maj_stat, min_stat
544-
545-
with nogil:
546-
maj_stat = gss_unwrap_aead(&min_stat, context.raw_ctx, &input_buffer,
547-
assoc_buffer_ptr, &output_buffer,
548-
&conf_state, &qop_state)
549-
550-
if maj_stat == GSS_S_COMPLETE:
551-
output_message = (<char*>output_buffer.value)[:output_buffer.length]
552-
gss_release_buffer(&min_stat, &output_buffer)
553-
return UnwrapResult(output_message, <bint>conf_state, qop_state)
554-
else:
555-
raise GSSError(maj_stat, min_stat)

Diff for: gssapi/raw/ext_dce_aead.pyx

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
GSSAPI="BASE" # This ensures that a full module is generated by Cython
2+
3+
from gssapi.raw.cython_types cimport *
4+
from gssapi.raw.sec_contexts cimport SecurityContext
5+
6+
from gssapi.raw.misc import GSSError
7+
from gssapi.raw.named_tuples import WrapResult, UnwrapResult
8+
9+
10+
cdef extern from "python_gssapi_ext.h":
11+
OM_uint32 gss_wrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
12+
int conf_req, gss_qop_t qop_req,
13+
gss_buffer_t input_assoc_buffer,
14+
gss_buffer_t input_payload_buffer, int *conf_ret,
15+
gss_buffer_t output_message_buffer) nogil
16+
17+
OM_uint32 gss_unwrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
18+
gss_buffer_t input_message_buffer,
19+
gss_buffer_t input_assoc_buffer,
20+
gss_buffer_t output_payload_buffer,
21+
int *conf_ret, gss_qop_t *qop_ret) nogil
22+
23+
24+
def wrap_aead(SecurityContext context not None, bytes message not None,
25+
bytes associated=None, confidential=True, qop=None):
26+
"""
27+
wrap_aead(context, message, associated=None, confidential=True, qop=None)
28+
Wrap/Encrypt an AEAD message.
29+
30+
This method takes an input message and associated data,
31+
and outputs and AEAD message.
32+
33+
Args:
34+
context (SecurityContext): the current security context
35+
message (bytes): the message to wrap or encrypt
36+
associated (bytes): associated data to go with the message
37+
confidential (bool): whether or not to encrypt the message (True),
38+
or just wrap it with a MIC (False)
39+
qop (int): the desired Quality of Protection
40+
(or None for the default QoP)
41+
42+
Returns:
43+
WrapResult: the wrapped/encrypted total message, and whether or not
44+
encryption was actually used
45+
46+
Raises:
47+
GSSError
48+
"""
49+
50+
cdef int conf_req = confidential
51+
cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
52+
cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
53+
message)
54+
55+
cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
56+
cdef gss_buffer_desc assoc_buffer
57+
if associated is not None:
58+
assoc_buffer = gss_buffer_desc(len(associated), associated)
59+
assoc_buffer_ptr = &assoc_buffer
60+
61+
cdef int conf_used
62+
# GSS_C_EMPTY_BUFFER
63+
cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
64+
65+
cdef OM_uint32 maj_stat, min_stat
66+
67+
with nogil:
68+
maj_stat = gss_wrap_aead(&min_stat, context.raw_ctx, conf_req, qop_req,
69+
assoc_buffer_ptr, &message_buffer,
70+
&conf_used, &output_buffer)
71+
72+
if maj_stat == GSS_S_COMPLETE:
73+
output_message = (<char*>output_buffer.value)[:output_buffer.length]
74+
gss_release_buffer(&min_stat, &output_buffer)
75+
return WrapResult(output_message, <bint>conf_used)
76+
else:
77+
raise GSSError(maj_stat, min_stat)
78+
79+
80+
def unwrap_aead(SecurityContext context not None, bytes message not None,
81+
bytes associated=None):
82+
"""
83+
unwrap_aead(context, message, associated=None)
84+
Unwrap/Decrypt an AEAD message.
85+
86+
This method takes an encrpyted/wrapped AEAD message and some associated
87+
data, and returns an unwrapped/decrypted message.
88+
89+
Args:
90+
context (SecurityContext): the current security context
91+
message (bytes): the AEAD message to unwrap or decrypt
92+
associated (bytes): associated data that goes with the message
93+
94+
Returns:
95+
UnwrapResult: the unwrapped/decrypted message, whether or on
96+
encryption was used, and the QoP used
97+
98+
Raises:
99+
GSSError
100+
"""
101+
102+
cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message)
103+
104+
cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
105+
cdef gss_buffer_desc assoc_buffer
106+
if associated is not None:
107+
assoc_buffer = gss_buffer_desc(len(associated), associated)
108+
assoc_buffer_ptr = &assoc_buffer
109+
110+
# GSS_C_EMPTY_BUFFER
111+
cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
112+
cdef int conf_state
113+
cdef gss_qop_t qop_state
114+
115+
cdef OM_uint32 maj_stat, min_stat
116+
117+
with nogil:
118+
maj_stat = gss_unwrap_aead(&min_stat, context.raw_ctx, &input_buffer,
119+
assoc_buffer_ptr, &output_buffer,
120+
&conf_state, &qop_state)
121+
122+
if maj_stat == GSS_S_COMPLETE:
123+
output_message = (<char*>output_buffer.value)[:output_buffer.length]
124+
gss_release_buffer(&min_stat, &output_buffer)
125+
return UnwrapResult(output_message, <bint>conf_state, qop_state)
126+
else:
127+
raise GSSError(maj_stat, min_stat)

Diff for: gssapi/raw/python_gssapi_ext.h

+38
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
#ifdef OSX_HAS_GSS_FRAMEWORK
22
#include <GSS/GSS.h>
3+
4+
// GSS includes these in gssapi_private.h but these aren't present on the host
5+
// so just explicitly define them.
6+
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
7+
__ApplePrivate_gss_unwrap_iov (
8+
OM_uint32 * __nonnull minor_status,
9+
__nonnull gss_ctx_id_t context_handle,
10+
int * __nullable conf_state,
11+
gss_qop_t *__nullable qop_state,
12+
gss_iov_buffer_desc *__nonnull iov,
13+
int iov_count);
14+
15+
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
16+
__ApplePrivate_gss_wrap_iov (
17+
OM_uint32 * __nonnull minor_status,
18+
__nonnull gss_ctx_id_t context_handle,
19+
int conf_req_flag,
20+
gss_qop_t qop_req,
21+
int * __nonnull conf_state,
22+
gss_iov_buffer_desc *__nonnull iov,
23+
int iov_count);
24+
25+
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
26+
__ApplePrivate_gss_wrap_iov_length (
27+
OM_uint32 * __nonnull minor_status,
28+
__nonnull gss_ctx_id_t context_handle,
29+
int conf_req_flag,
30+
gss_qop_t qop_req,
31+
int * __nullable conf_state,
32+
gss_iov_buffer_desc *__nonnull iov,
33+
int iov_count);
34+
35+
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
36+
__ApplePrivate_gss_release_iov_buffer (
37+
OM_uint32 * __nonnull minor_status,
38+
gss_iov_buffer_desc *__nonnull iov,
39+
int iov_count);
40+
341
#else
442
#if defined(__MINGW32__) && defined(__MSYS__)
543
#include <gss.h>

Diff for: gssapi/tests/test_raw.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,7 @@ def test_basic_iov_wrap_unwrap_prealloc(self):
13041304
self.assertEqual(init_message[2].value, init_data)
13051305
self.assertEqual(init_message[3].value, init_other_data)
13061306

1307-
@ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
1307+
@ktu.gssapi_extension_test('dce', 'DCE (IOV)')
13081308
def test_basic_iov_wrap_unwrap_autoalloc(self):
13091309
init_data = b'some encrypted data'
13101310
init_other_data = b'some other encrypted data'
@@ -1334,7 +1334,7 @@ def test_basic_iov_wrap_unwrap_autoalloc(self):
13341334
self.assertEqual(init_message[2].value, init_data)
13351335
self.assertEqual(init_message[3].value, init_other_data)
13361336

1337-
@ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
1337+
@ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)')
13381338
def test_basic_aead_wrap_unwrap(self):
13391339
assoc_data = b'some sig data'
13401340
wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message",
@@ -1353,7 +1353,7 @@ def test_basic_aead_wrap_unwrap(self):
13531353
self.assertIsInstance(qop, int)
13541354
self.assertGreaterEqual(qop, 0)
13551355

1356-
@ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
1356+
@ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)')
13571357
def test_basic_aead_wrap_unwrap_no_assoc(self):
13581358
wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message")
13591359
self.assertIsInstance(wrapped_message, bytes)
@@ -1370,7 +1370,7 @@ def test_basic_aead_wrap_unwrap_no_assoc(self):
13701370
self.assertIsInstance(qop, int)
13711371
self.assertGreaterEqual(qop, 0)
13721372

1373-
@ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
1373+
@ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)')
13741374
def test_basic_aead_wrap_unwrap_bad_assoc_raises_error(self):
13751375
assoc_data = b'some sig data'
13761376
wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message",

0 commit comments

Comments
 (0)