Skip to content

Commit

Permalink
changes based on upstream PR
Browse files Browse the repository at this point in the history
  • Loading branch information
jborean93 committed Aug 7, 2017
1 parent b8135eb commit a3751b2
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 44 deletions.
53 changes: 53 additions & 0 deletions README.txt
Expand Up @@ -65,6 +65,59 @@ Then run test.py with suitable command line arguments:
-p : password for basic authenticate
-s : service principal for GSSAPI authentication (defaults to 'http@host.example.com')

================
CHANNEL BINDINGS
================

You can use this library to authenticate with Channel Binding support. Channel
Bindings are tags that identify the particular data channel being used with the
authentication. You can use Channel bindings to offer more proof of a valid
identity. Some services like Microsoft's Extended Protection can enforce
Channel Binding support on authorisation and you can use this library to meet
those requirements.

More details on Channel Bindings as set through the GSSAPI can be found here
<https://docs.oracle.com/cd/E19455-01/806-3814/overview-52/index.html>. Using
TLS as a example this is how you would add Channel Binding support to your
authentication mechanism. The following code snippet is based on RFC5929
<https://tools.ietf.org/html/rfc5929> using the 'tls-server-endpoint-point'
type.

.. code-block:: python

import hashlib

def get_channel_bindings_application_data(socket):
# This is a highly simplified example, there are other use cases
# where you might need to use different hash types or get a socket
# object somehow.
server_certificate = socket.getpeercert(True)
certificate_hash = hashlib.sha256(server_certificate).hexdigest().upper()
certificate_digest = base64.b16decode(certificate_hash)
application_data = b'tls-server-end-point:%s' % certificate_digest

return application_data

def main():
# Code to setup a socket with the server
# A lot of code to setup the handshake and start the auth process
socket = getsocketsomehow()

# Connect to the host and start the auth process

# Build the channel bindings object
application_data = get_channel_bindings_application_data(socket)
channel_bindings = kerberos.channelBindings(application_data=application_data)

# More work to get responses from the server

result, context = kerberos.authGSSClientInit(kerb_spn, gssflags=gssflags, principal=principal)

# Pass through the channel_bindings object as created in the kerberos.channelBindings method
result = kerberos.authGSSClientStep(context, neg_resp_value, channel_bindings=channel_bindings)

# Repeat as necessary

===========
Python APIs
===========
Expand Down
54 changes: 53 additions & 1 deletion pysrc/kerberos.py
Expand Up @@ -130,13 +130,16 @@ def authGSSClientClean(context):
@return: a result code (see above).
"""

def authGSSClientStep(context, challenge):
def authGSSClientStep(context, challenge, **kwargs):
"""
Processes a single GSSAPI client-side step using the supplied server data.
@param context: the context object returned from authGSSClientInit.
@param challenge: a string containing the base64-encoded server data (which may be empty
for the first step).
@param channel_bindings: Optional channel bindings to bind onto the auth request. This
struct can be built using the channelBindings function and it not specified, this process
will pass along GSS_C_NO_CHANNEL_BINDINGS as a default
@return: a result code (see above).
"""

Expand Down Expand Up @@ -238,3 +241,52 @@ def authGSSServerTargetName(context):
@param context: the context object returned from authGSSServerInit.
@return: a string containing the target name.
"""

"""
Address Types for Channel Bindings
https://docs.oracle.com/cd/E19455-01/806-3814/6jcugr7dp/index.html#reference-9
"""

GSS_C_AF_UNSPEC = 0
GSS_C_AF_LOCAL = 1
GSS_C_AF_INET = 2
GSS_C_AF_IMPLINK = 3
GSS_C_AF_PUP = 4
GSS_C_AF_CHAOS = 5
GSS_C_AF_NS = 6
GSS_C_AF_NBS = 7
GSS_C_AF_ECMA = 8
GSS_C_AF_DATAKIT = 9
GSS_C_AF_CCITT = 10
GSS_C_AF_SNA = 11
GSS_C_AF_DECnet = 12
GSS_C_AF_DLI = 13
GSS_C_AF_LAT = 14
GSS_C_AF_HYLINK = 15
GSS_C_AF_APPLETALK = 16
GSS_C_AF_BSC = 17
GSS_C_AF_DSS = 18
GSS_C_AF_OSI = 19
GSS_C_AF_X25 = 21
GSS_C_AF_NULLADDR = 255

def channelBindings(**kwargs):
"""
Builds a gss_channel_bindings_struct which can be used to pass onto authGSSClientStep to bind
onto the auth. Details on Channel Bindings can be found at https://tools.ietf.org/html/rfc5929.
More details on the struct can be found at https://docs.oracle.com/cd/E19455-01/806-3814/overview-52/index.html
@param initiator_addrtype: Optional integer used to set the
initiator_addrtype, defaults to GSS_C_AF_UNSPEC if not set
@param initiator_address: Optional byte string containing the
initiator_address
@param acceptor_addrtype: Optional integer used to set the
acceptor_addrtype, defaults to GSS_C_AF_UNSPEC if not set
@param acceptor_address: Optional byte string containing the
acceptor_address
@param application_data: Optional byte string containing the
application_data. An example would be 'tls-server-end-point:{cert-hash}'
where {cert-hash} is the byte string hash of the server's certificate
@return: The gss_channel_bindings_struct pointer, which is the channel
bindings structure that can be passed onto authGSSClientStep
"""
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -58,7 +58,7 @@ def check_krb5_version():

setup (
name = "pykerberos",
version = "1.1.14",
version = "1.2.0",
description = "High-level interface to Kerberos",
long_description=long_description,
license="ASL 2.0",
Expand Down
121 changes: 82 additions & 39 deletions src/kerberos.c
Expand Up @@ -163,48 +163,68 @@ static PyObject *authGSSClientClean(PyObject *self, PyObject *args) {
return Py_BuildValue("i", AUTH_GSS_COMPLETE);
}

static PyObject *buildChannelBindingsStruct(PyObject *self, PyObject *args, PyObject* keywds) {
#if PY_MAJOR_VERSION >= 3
void destruct_channel_bindings(PyObject* o) {
struct gss_channel_bindings_struct *channel_bindings = PyCapsule_GetPointer(o, NULL);
#else
void destruct_channel_bindings(void* o) {
struct gss_channel_bindings_struct *channel_bindings = (struct gss_channel_bindings_struct *)o;
#endif

if (channel_bindings != NULL) {
if (channel_bindings->initiator_address.value != NULL) {
PyMem_Free(channel_bindings->initiator_address.value);
}

if (channel_bindings->acceptor_address.value != NULL) {
PyMem_Free(channel_bindings->acceptor_address.value);
}

if (channel_bindings->application_data.value != NULL) {
PyMem_Free(channel_bindings->application_data.value);
}

free(channel_bindings);
}
}

static PyObject *channelBindings(PyObject *self, PyObject *args, PyObject* keywds) {
int initiator_addrtype = GSS_C_AF_UNSPEC;
int acceptor_addrtype = GSS_C_AF_UNSPEC;
const char *initiator_address = NULL;
const char *acceptor_address = NULL;
const char *application_data = NULL;
PyObject *pychan_bindings;

const char *encoding = NULL;
char *initiator_address = NULL;
char *acceptor_address = NULL;
char *application_data = NULL;
int initiator_length = NULL;
int acceptor_length = NULL;
int application_length = NULL;

PyObject *pychan_bindings = NULL;
struct gss_channel_bindings_struct *input_chan_bindings;
static char *kwlist[] = {"initiator_addrtype", "acceptor_addrtype", "initiator_address", "acceptor_address", "application_data", NULL};
static char *kwlist[] = {"initiator_addrtype", "initiator_address", "acceptor_addrtype",
"acceptor_address", "application_data", NULL};

if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iisss", kwlist, &initiator_addrtype, &acceptor_addrtype, &initiator_address, &acceptor_address, &application_data)) {
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iet#iet#et#", kwlist,
&initiator_addrtype, &encoding, &initiator_address, &initiator_length,
&acceptor_addrtype, &encoding, &acceptor_address, &acceptor_length,
&encoding, &application_data, &application_length)) {
return NULL;
}

input_chan_bindings = (struct gss_channel_bindings_struct *) malloc(sizeof(struct gss_channel_bindings_struct));
pychan_bindings = PyNew(input_chan_bindings, NULL);
input_chan_bindings = (struct gss_channel_bindings_struct *) malloc(sizeof(struct gss_channel_bindings_struct));
pychan_bindings = PyNew(input_chan_bindings, &destruct_channel_bindings);

input_chan_bindings->initiator_addrtype = initiator_addrtype;
if (initiator_address == NULL) {
input_chan_bindings->initiator_address.length = 0;
input_chan_bindings->initiator_address.value = NULL;
} else {
input_chan_bindings->initiator_address.length = strlen(initiator_address);
input_chan_bindings->initiator_address.value = (char *)initiator_address;
}
input_chan_bindings->initiator_address.length = initiator_length;
input_chan_bindings->initiator_address.value = initiator_address;

input_chan_bindings->acceptor_addrtype = acceptor_addrtype;
if (acceptor_address == NULL) {
input_chan_bindings->acceptor_address.length = 0;
input_chan_bindings->acceptor_address.value = NULL;
} else {
input_chan_bindings->acceptor_address.length = strlen(acceptor_address);
input_chan_bindings->acceptor_address.value = (char *)acceptor_address;
}
input_chan_bindings->acceptor_address.length = acceptor_length;
input_chan_bindings->acceptor_address.value = acceptor_address;

if (application_data == NULL) {
input_chan_bindings->application_data.length = 0;
input_chan_bindings->application_data.value = NULL;
} else {
input_chan_bindings->application_data.length = strlen(application_data);
input_chan_bindings->application_data.value = (char *)application_data;
}
input_chan_bindings->application_data.length = application_length;
input_chan_bindings->application_data.value = application_data;

return Py_BuildValue("N", pychan_bindings);
}
Expand All @@ -214,11 +234,11 @@ static PyObject *authGSSClientStep(PyObject *self, PyObject *args, PyObject* key
PyObject *pystate;
char *challenge = NULL;
PyObject *pychan_bindings = NULL;
struct gss_channel_bindings_struct *input_chan_bindings;
static char *kwlist[] = {"state", "challenge", "input_chan_bindings", NULL};
struct gss_channel_bindings_struct *channel_bindings;
static char *kwlist[] = {"state", "challenge", "channel_bindings", NULL};
int result = 0;

if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|O", kwlist, &pystate, &challenge, &pychan_bindings)) {
if (! PyArg_ParseTupleAndKeywords(args, keywds, "Os|O", kwlist, &pystate, &challenge, &pychan_bindings)) {
return NULL;
}

Expand All @@ -233,16 +253,16 @@ static PyObject *authGSSClientStep(PyObject *self, PyObject *args, PyObject* key
}

if (pychan_bindings == NULL) {
input_chan_bindings = GSS_C_NO_CHANNEL_BINDINGS;
channel_bindings = GSS_C_NO_CHANNEL_BINDINGS;
} else {
if (!PyCheck(pychan_bindings)) {
PyErr_SetString(PyExc_TypeError, "Expected a gss_channel_bindings_struct object");
return NULL;
}
input_chan_bindings = PyGet(pychan_bindings, struct gss_channel_bindings_struct);
channel_bindings = PyGet(pychan_bindings, struct gss_channel_bindings_struct);
}

result = authenticate_gss_client_step(state, challenge, input_chan_bindings);
result = authenticate_gss_client_step(state, challenge, channel_bindings);
if (result == AUTH_GSS_ERROR) {
return NULL;
}
Expand Down Expand Up @@ -590,7 +610,7 @@ static PyMethodDef KerberosMethods[] = {
"Do a GSSAPI wrap."},
{"authGSSClientUnwrap", authGSSClientUnwrap, METH_VARARGS,
"Do a GSSAPI unwrap."},
{"buildChannelBindingsStruct", (PyCFunction)buildChannelBindingsStruct, METH_VARARGS | METH_KEYWORDS,
{"channelBindings", (PyCFunction)channelBindings, METH_VARARGS | METH_KEYWORDS,
"Build the Channel Bindings Structure based on the input."},
#ifdef GSSAPI_EXT
{"authGSSClientWrapIov", authGSSClientWrapIov, METH_VARARGS,
Expand Down Expand Up @@ -681,6 +701,29 @@ void initkerberos(void)
PyDict_SetItemString(d, "GSS_MECH_OID_KRB5", PyNew(&krb5_mech_oid, NULL));
PyDict_SetItemString(d, "GSS_MECH_OID_SPNEGO", PyNew(&spnego_mech_oid, NULL));

PyDict_SetItemString(d, "GSS_C_AF_UNSPEC", PyInt_FromLong(GSS_C_AF_UNSPEC));
PyDict_SetItemString(d, "GSS_C_AF_LOCAL", PyInt_FromLong(GSS_C_AF_LOCAL));
PyDict_SetItemString(d, "GSS_C_AF_INET", PyInt_FromLong(GSS_C_AF_INET));
PyDict_SetItemString(d, "GSS_C_AF_IMPLINK", PyInt_FromLong(GSS_C_AF_IMPLINK));
PyDict_SetItemString(d, "GSS_C_AF_PUP", PyInt_FromLong(GSS_C_AF_PUP));
PyDict_SetItemString(d, "GSS_C_AF_CHAOS", PyInt_FromLong(GSS_C_AF_CHAOS));
PyDict_SetItemString(d, "GSS_C_AF_NS", PyInt_FromLong(GSS_C_AF_NS));
PyDict_SetItemString(d, "GSS_C_AF_NBS", PyInt_FromLong(GSS_C_AF_NBS));
PyDict_SetItemString(d, "GSS_C_AF_ECMA", PyInt_FromLong(GSS_C_AF_ECMA));
PyDict_SetItemString(d, "GSS_C_AF_DATAKIT", PyInt_FromLong(GSS_C_AF_DATAKIT));
PyDict_SetItemString(d, "GSS_C_AF_CCITT", PyInt_FromLong(GSS_C_AF_CCITT));
PyDict_SetItemString(d, "GSS_C_AF_SNA", PyInt_FromLong(GSS_C_AF_SNA));
PyDict_SetItemString(d, "GSS_C_AF_DECnet", PyInt_FromLong(GSS_C_AF_DECnet));
PyDict_SetItemString(d, "GSS_C_AF_DLI", PyInt_FromLong(GSS_C_AF_DLI));
PyDict_SetItemString(d, "GSS_C_AF_LAT", PyInt_FromLong(GSS_C_AF_LAT));
PyDict_SetItemString(d, "GSS_C_AF_HYLINK", PyInt_FromLong(GSS_C_AF_HYLINK));
PyDict_SetItemString(d, "GSS_C_AF_APPLETALK", PyInt_FromLong(GSS_C_AF_APPLETALK));
PyDict_SetItemString(d, "GSS_C_AF_BSC", PyInt_FromLong(GSS_C_AF_BSC));
PyDict_SetItemString(d, "GSS_C_AF_DSS", PyInt_FromLong(GSS_C_AF_DSS));
PyDict_SetItemString(d, "GSS_C_AF_OSI", PyInt_FromLong(GSS_C_AF_OSI));
PyDict_SetItemString(d, "GSS_C_AF_X25", PyInt_FromLong(GSS_C_AF_X25));
PyDict_SetItemString(d, "GSS_C_AF_NULLADDR", PyInt_FromLong(GSS_C_AF_NULLADDR));

error:
if (PyErr_Occurred())
PyErr_SetString(PyExc_ImportError, "kerberos: init failed");
Expand Down
4 changes: 2 additions & 2 deletions src/kerberosgss.c
Expand Up @@ -205,7 +205,7 @@ int authenticate_gss_client_clean(gss_client_state *state)
return ret;
}

int authenticate_gss_client_step(gss_client_state* state, const char* challenge, struct gss_channel_bindings_struct* input_chan_bindings)
int authenticate_gss_client_step(gss_client_state* state, const char* challenge, struct gss_channel_bindings_struct* channel_bindings)
{
OM_uint32 maj_stat;
OM_uint32 min_stat;
Expand Down Expand Up @@ -238,7 +238,7 @@ int authenticate_gss_client_step(gss_client_state* state, const char* challenge,
state->mech_oid,
(OM_uint32)state->gss_flags,
0,
input_chan_bindings,
channel_bindings,
&input_token,
NULL,
&output_token,
Expand Down
2 changes: 1 addition & 1 deletion src/kerberosgss.h
Expand Up @@ -56,7 +56,7 @@ char* server_principal_details(const char* service, const char* hostname);

int authenticate_gss_client_init(const char* service, const char* principal, long int gss_flags, gss_OID mech_oid, gss_client_state* state);
int authenticate_gss_client_clean(gss_client_state *state);
int authenticate_gss_client_step(gss_client_state *state, const char *challenge, struct gss_channel_bindings_struct *input_chan_bindings);
int authenticate_gss_client_step(gss_client_state *state, const char *challenge, struct gss_channel_bindings_struct *channel_bindings);
int authenticate_gss_client_unwrap(gss_client_state* state, const char* challenge);
int authenticate_gss_client_wrap(gss_client_state* state, const char* challenge, const char* user, int protect);
#ifdef GSSAPI_EXT
Expand Down

0 comments on commit a3751b2

Please sign in to comment.