diff --git a/pysrc/kerberos.py b/pysrc/kerberos.py index d4f53a6..0f8eb06 100644 --- a/pysrc/kerberos.py +++ b/pysrc/kerberos.py @@ -137,6 +137,7 @@ def getServerPrincipalDetails(service, hostname): GSS_C_PROT_READY_FLAG = 128 GSS_C_TRANS_FLAG = 256 +GSS_EXT_HAVE_PASSWORD = True def authGSSClientInit(service, **kwargs): @@ -160,6 +161,8 @@ def authGSSClientInit(service, **kwargs): @param mech_oid: Optional GGS mech OID + @param password: Optional string containing the service principal's password + @return: A tuple of (result, context) where result is the result code (see above) and context is an opaque value that will need to be passed to subsequent functions. @@ -179,6 +182,29 @@ def authGSSClientClean(context): """ +def authGSSSign(context, message, qop=0): + """ + Creates MIC (signature) of the message + + @param context: The context object returned from L{authGSSClientInit}. + + @param message: The text message (base64 encoded) + + @return: The MIC of the message (base64 encoded). + """ + + +def authGSSVerify(context, message, token, qop=0): + """ + Verify MIC (signature) of the message + + @param context: The context object returned from L{authGSSClientInit}. + + @param message: The text message (base64 encoded) + + @param token: The MIC of the message (base64 encoded). + """ + def authGSSClientInquireCred(context): """ diff --git a/src/kerberos.c b/src/kerberos.c index 1e889df..5f50997 100644 --- a/src/kerberos.c +++ b/src/kerberos.c @@ -31,6 +31,7 @@ // Basic renames (function parameters are the same) // No more int objects #define PyInt_FromLong PyLong_FromLong + #define PyString_FromString PyUnicode_FromString #endif #if PY_VERSION_HEX >= 0x03020000 @@ -154,6 +155,7 @@ static PyObject* authGSSClientInit(PyObject* self, PyObject* args, PyObject* key { const char *service = NULL; const char *principal = NULL; + const char *password = NULL; gss_client_state *state = NULL; PyObject *pystate = NULL; gss_server_state *delegatestate = NULL; @@ -161,14 +163,14 @@ static PyObject* authGSSClientInit(PyObject* self, PyObject* args, PyObject* key gss_OID mech_oid = GSS_C_NO_OID; PyObject *pymech_oid = NULL; static char *kwlist[] = { - "service", "principal", "gssflags", "delegated", "mech_oid", NULL + "service", "principal", "gssflags", "delegated", "mech_oid", "password", NULL }; long int gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG; int result = 0; if (! PyArg_ParseTupleAndKeywords( - args, keywds, "s|zlOO", kwlist, - &service, &principal, &gss_flags, &pydelegatestate, &pymech_oid + args, keywds, "s|zlOOz", kwlist, + &service, &principal, &gss_flags, &pydelegatestate, &pymech_oid, &password )) { return NULL; } @@ -194,7 +196,7 @@ static PyObject* authGSSClientInit(PyObject* self, PyObject* args, PyObject* key } result = authenticate_gss_client_init( - service, principal, gss_flags, delegatestate, mech_oid, state + service, principal, gss_flags, delegatestate, mech_oid, state, password ); if (result == AUTH_GSS_ERROR) { @@ -277,6 +279,76 @@ static PyObject *channelBindings(PyObject *self, PyObject *args, PyObject* keywd return Py_BuildValue("N", pychan_bindings); } +static PyObject *authGSSSign(PyObject *self, PyObject *args, PyObject* keywds) +{ + gss_client_state *state = NULL; + PyObject *pystate = NULL; + PyObject *pytoken = NULL; + char *message = NULL; + char *token = NULL; + static char *kwlist[] = {"context", "message", "qop", NULL}; + int result = 0; + unsigned int qop = 0; + + if (! PyArg_ParseTupleAndKeywords(args, keywds, "Os|I", kwlist, &pystate, &message, &qop)) { + return NULL; + } + + if (! PyCObject_Check(pystate)) { + PyErr_SetString(PyExc_TypeError, "Expected a context object"); + return NULL; + } + + state = (gss_client_state *)PyCObject_AsVoidPtr(pystate); + + if (state == NULL) { + return NULL; + } + + result = authenticate_gss_sign(state, message, qop, &token); + if (result == AUTH_GSS_ERROR) { + return NULL; + } + + pytoken = PyString_FromString(token); + free(token); + + return pytoken; +} + +static PyObject *authGSSVerify(PyObject *self, PyObject *args, PyObject* keywds) +{ + gss_client_state *state = NULL; + PyObject *pystate = NULL; + char *message = NULL; + char *token = NULL; + static char *kwlist[] = {"context", "message", "token", "qop", NULL}; + int result = 0; + unsigned int qop = 0; + + if (! PyArg_ParseTupleAndKeywords(args, keywds, "Oss|I", kwlist, &pystate, &message, &token, &qop)) { + return NULL; + } + + if (! PyCObject_Check(pystate)) { + PyErr_SetString(PyExc_TypeError, "Expected a context object"); + return NULL; + } + + state = (gss_client_state *)PyCObject_AsVoidPtr(pystate); + + if (state == NULL) { + return NULL; + } + + result = authenticate_gss_verify(state, message, token, qop); + if (result == AUTH_GSS_ERROR) { + return NULL; + } + + return Py_BuildValue("i", result); +} + static PyObject *authGSSClientStep(PyObject *self, PyObject *args, PyObject* keywds) { gss_client_state *state = NULL; @@ -727,6 +799,16 @@ static PyMethodDef KerberosMethods[] = { getServerPrincipalDetails, METH_VARARGS, "Return the service principal for a given service and hostname." }, + { + "authGSSSign", + (PyCFunction)authGSSSign, METH_VARARGS | METH_KEYWORDS, + "Compute MIC of the message", + }, + { + "authGSSVerify", + (PyCFunction)authGSSVerify, METH_VARARGS | METH_KEYWORDS, + "Verify MIC of the message", + }, { "authGSSClientInit", (PyCFunction)authGSSClientInit, METH_VARARGS | METH_KEYWORDS, diff --git a/src/kerberosgss.c b/src/kerberosgss.c index c82a5e4..10622a9 100644 --- a/src/kerberosgss.c +++ b/src/kerberosgss.c @@ -39,18 +39,18 @@ char* server_principal_details(const char* service, const char* hostname) char match[1024]; size_t match_len = 0; char* result = NULL; - + int code; krb5_context kcontext; krb5_keytab kt = NULL; krb5_kt_cursor cursor = NULL; krb5_keytab_entry entry; char* pname = NULL; - + // Generate the principal prefix we want to match snprintf(match, 1024, "%s/%s@", service, hostname); match_len = strlen(match); - + code = krb5_init_context(&kcontext); if (code) { PyErr_SetObject( @@ -61,7 +61,7 @@ char* server_principal_details(const char* service, const char* hostname) ); return NULL; } - + if ((code = krb5_kt_default(kcontext, &kt))) { PyErr_SetObject( KrbException_class, @@ -69,7 +69,7 @@ char* server_principal_details(const char* service, const char* hostname) ); goto end; } - + if ((code = krb5_kt_start_seq_get(kcontext, kt, &cursor))) { PyErr_SetObject( KrbException_class, @@ -79,7 +79,7 @@ char* server_principal_details(const char* service, const char* hostname) ); goto end; } - + while ((code = krb5_kt_next_entry(kcontext, kt, &entry, &cursor)) == 0) { if ((code = krb5_unparse_name(kcontext, entry.principal, &pname))) { PyErr_SetObject( @@ -90,7 +90,7 @@ char* server_principal_details(const char* service, const char* hostname) ); goto end; } - + if (strncmp(pname, match, match_len) == 0) { result = malloc(strlen(pname) + 1); if (result == NULL) { @@ -102,18 +102,18 @@ char* server_principal_details(const char* service, const char* hostname) krb5_free_keytab_entry_contents(kcontext, &entry); break; } - + krb5_free_unparsed_name(kcontext, pname); krb5_free_keytab_entry_contents(kcontext, &entry); } - + if (result == NULL) { PyErr_SetObject( KrbException_class, Py_BuildValue("((s:i))", "Principal not found in keytab", -1) ); } - + end: if (cursor) { krb5_kt_end_seq_get(kcontext, kt, &cursor); @@ -122,13 +122,14 @@ char* server_principal_details(const char* service, const char* hostname) krb5_kt_close(kcontext, kt); } krb5_free_context(kcontext); - + return result; } int authenticate_gss_client_init( const char* service, const char* principal, long int gss_flags, - gss_server_state* delegatestate, gss_OID mech_oid, gss_client_state* state + gss_server_state* delegatestate, gss_OID mech_oid, gss_client_state* state, + const char *password ) { OM_uint32 maj_stat; @@ -136,7 +137,7 @@ int authenticate_gss_client_init( gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc principal_token = GSS_C_EMPTY_BUFFER; int ret = AUTH_GSS_COMPLETE; - + state->server_name = GSS_C_NO_NAME; state->mech_oid = mech_oid; state->context = GSS_C_NO_CONTEXT; @@ -144,15 +145,15 @@ int authenticate_gss_client_init( state->client_creds = GSS_C_NO_CREDENTIAL; state->username = NULL; state->response = NULL; - + // Import server name first name_token.length = strlen(service); name_token.value = (char *)service; - + maj_stat = gss_import_name( &min_stat, &name_token, gss_krb5_nt_service_name, &state->server_name ); - + if (GSS_ERROR(maj_stat)) { set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; @@ -177,10 +178,26 @@ int authenticate_gss_client_init( goto end; } - maj_stat = gss_acquire_cred( - &min_stat, name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, - GSS_C_INITIATE, &state->client_creds, NULL, NULL - ); + if (password != NULL) { + gss_buffer_desc gss_password = { + .length = strlen(password), + .value = password + }; + maj_stat = gss_acquire_cred_with_password( + &min_stat, name, &gss_password, + GSS_C_INDEFINITE, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &state->client_creds, NULL, NULL + ); + } else { +#ifdef PRINTFS + printf("No password provided\n"); +#endif + maj_stat = gss_acquire_cred( + &min_stat, name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &state->client_creds, NULL, NULL + ); + } + if (GSS_ERROR(maj_stat)) { set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; @@ -204,7 +221,7 @@ int authenticate_gss_client_clean(gss_client_state *state) OM_uint32 maj_stat; OM_uint32 min_stat; int ret = AUTH_GSS_COMPLETE; - + if (state->context != GSS_C_NO_CONTEXT) { maj_stat = gss_delete_sec_context( &min_stat, &state->context, GSS_C_NO_BUFFER @@ -227,10 +244,134 @@ int authenticate_gss_client_clean(gss_client_state *state) free(state->response); state->response = NULL; } - + return ret; } +int authenticate_gss_sign( + gss_client_state* state, const char* message, unsigned int qop, char **token +) { + OM_uint32 maj_stat; + OM_uint32 min_stat; + gss_buffer_desc input_message = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + int ret = AUTH_GSS_CONTINUE; + + if (message && *message) { + size_t len; + input_message.value = base64_decode(message, &len); + if (input_message.value == NULL) + { + PyErr_NoMemory(); + ret = AUTH_GSS_ERROR; + goto end; + } + input_message.length = len; + } + + Py_BEGIN_ALLOW_THREADS + maj_stat = gss_get_mic( + &min_stat, + state->context, + qop, + &input_message, + &output_token + ); + Py_END_ALLOW_THREADS + + if (maj_stat != GSS_S_COMPLETE) { + set_gss_error(maj_stat, min_stat); + ret = AUTH_GSS_ERROR; + goto end; + } + + if (output_token.length && token) { + *token = base64_encode((const unsigned char *)output_token.value, output_token.length); + if (*token == NULL) { + PyErr_NoMemory(); + ret = AUTH_GSS_ERROR; + goto end; + } + } + + ret = AUTH_GSS_COMPLETE; + +end: + if (output_token.value) { + gss_release_buffer(&min_stat, &output_token); + } + + if (input_message.value) { + free(input_message.value); + } + + return AUTH_GSS_COMPLETE; +} + +int authenticate_gss_verify( + gss_client_state* state, const char* message, const char* token, unsigned int qop +) { + OM_uint32 maj_stat; + OM_uint32 min_stat; + gss_buffer_desc input_message = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + int ret = AUTH_GSS_CONTINUE; + + if (message && *message) { + size_t len; + input_message.value = base64_decode(message, &len); + if (input_message.value == NULL) + { + PyErr_NoMemory(); + ret = AUTH_GSS_ERROR; + goto end; + } + input_message.length = len; + } + + if (token && *token) { + size_t len; + input_token.value = base64_decode(token, &len); + if (input_token.value == NULL) + { + PyErr_NoMemory(); + ret = AUTH_GSS_ERROR; + goto end; + } + input_token.length = len; + } + + Py_BEGIN_ALLOW_THREADS + maj_stat = gss_verify_mic( + &min_stat, + state->context, + &input_message, + &input_token, + qop + ); + Py_END_ALLOW_THREADS + + if (maj_stat != GSS_S_COMPLETE) { + set_gss_error(maj_stat, min_stat); + ret = AUTH_GSS_ERROR; + goto end; + } + + ret = AUTH_GSS_COMPLETE; + +end: + if (input_token.value) { + free(input_token.value); + } + + if (input_message.value) { + free(input_message.value); + } + + return AUTH_GSS_COMPLETE; +} + + int authenticate_gss_client_step( gss_client_state* state, const char* challenge, struct gss_channel_bindings_struct* channel_bindings ) { @@ -239,13 +380,13 @@ int authenticate_gss_client_step( gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; int ret = AUTH_GSS_CONTINUE; - + // Always clear out the old response if (state->response != NULL) { free(state->response); state->response = NULL; } - + // If there is a challenge (data from the server) we need to give it to GSS if (challenge && *challenge) { size_t len; @@ -258,7 +399,7 @@ int authenticate_gss_client_step( } input_token.length = len; } - + // Do GSSAPI step Py_BEGIN_ALLOW_THREADS maj_stat = gss_init_sec_context( @@ -277,13 +418,13 @@ int authenticate_gss_client_step( NULL ); Py_END_ALLOW_THREADS - + if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) { set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; goto end; } - + ret = (maj_stat == GSS_S_COMPLETE) ? AUTH_GSS_COMPLETE : AUTH_GSS_CONTINUE; // Grab the client response to send back to the server if (output_token.length) { @@ -295,7 +436,7 @@ int authenticate_gss_client_step( } maj_stat = gss_release_buffer(&min_stat, &output_token); } - + // Try to get the user name if we have completed all GSS operations if (ret == AUTH_GSS_COMPLETE) { gss_name_t gssuser = GSS_C_NO_NAME; @@ -305,7 +446,7 @@ int authenticate_gss_client_step( ret = AUTH_GSS_ERROR; goto end; } - + gss_buffer_desc name_token; name_token.length = 0; maj_stat = gss_display_name(&min_stat, gssuser, &name_token, NULL); @@ -314,15 +455,15 @@ int authenticate_gss_client_step( gss_release_buffer(&min_stat, &name_token); } gss_release_name(&min_stat, &gssuser); - + set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; goto end; } else { - if (state->username != NULL) { - free(state->username); - state->username = NULL; - } + if (state->username != NULL) { + free(state->username); + state->username = NULL; + } state->username = (char *)malloc(name_token.length + 1); if (state->username == NULL) { PyErr_NoMemory(); @@ -355,14 +496,14 @@ int authenticate_gss_client_unwrap( gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; int ret = AUTH_GSS_CONTINUE; int conf = 0; - + // Always clear out the old response if (state->response != NULL) { free(state->response); state->response = NULL; state->responseConf = 0; } - + // If there is a challenge (data from the server) we need to give it to GSS if (challenge && *challenge) { size_t len; @@ -374,7 +515,7 @@ int authenticate_gss_client_unwrap( } input_token.length = len; } - + // Do GSSAPI step maj_stat = gss_unwrap( &min_stat, @@ -384,7 +525,7 @@ int authenticate_gss_client_unwrap( &conf, NULL ); - + if (maj_stat != GSS_S_COMPLETE) { set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; @@ -392,7 +533,7 @@ int authenticate_gss_client_unwrap( } else { ret = AUTH_GSS_COMPLETE; } - + // Grab the client response if (output_token.length) { state->response = base64_encode( @@ -429,13 +570,13 @@ int authenticate_gss_client_wrap( int ret = AUTH_GSS_CONTINUE; char buf[4096], server_conf_flags; unsigned long buf_size; - + // Always clear out the old response if (state->response != NULL) { free(state->response); state->response = NULL; } - + if (challenge && *challenge) { size_t len; input_token.value = base64_decode(challenge, &len); @@ -447,7 +588,7 @@ int authenticate_gss_client_wrap( } input_token.length = len; } - + if (user) { // get bufsize server_conf_flags = ((char*) input_token.value)[0]; @@ -463,7 +604,7 @@ int authenticate_gss_client_wrap( ); printf("Maximum GSS token size is %ld\n", buf_size); #endif - + // agree to terms (hack!) buf_size = htonl(buf_size); // not relevant without integrity/privacy memcpy(buf, &buf_size, 4); @@ -473,7 +614,7 @@ int authenticate_gss_client_wrap( input_token.value = buf; input_token.length = 4 + strlen(user); } - + // Do GSSAPI wrap maj_stat = gss_wrap( &min_stat, @@ -484,7 +625,7 @@ int authenticate_gss_client_wrap( NULL, &output_token ); - + if (maj_stat != GSS_S_COMPLETE) { set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; @@ -583,7 +724,7 @@ int authenticate_gss_server_init(const char *service, gss_server_state *state) OM_uint32 min_stat; gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER; int ret = AUTH_GSS_COMPLETE; - + state->context = GSS_C_NO_CONTEXT; state->server_name = GSS_C_NO_NAME; state->client_name = GSS_C_NO_NAME; @@ -594,7 +735,7 @@ int authenticate_gss_server_init(const char *service, gss_server_state *state) state->response = NULL; state->ccname = NULL; int cred_usage = GSS_C_ACCEPT; - + // Server name may be empty which means we aren't going to create our own creds size_t service_len = strlen(service); if (service_len != 0) { @@ -605,12 +746,12 @@ int authenticate_gss_server_init(const char *service, gss_server_state *state) else { name_token.length = strlen(service); name_token.value = (char *)service; - + maj_stat = gss_import_name( &min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE, &state->server_name ); - + if (GSS_ERROR(maj_stat)) { set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; @@ -630,7 +771,7 @@ int authenticate_gss_server_init(const char *service, gss_server_state *state) goto end; } } - + end: return ret; } @@ -640,7 +781,7 @@ int authenticate_gss_server_clean(gss_server_state *state) OM_uint32 maj_stat; OM_uint32 min_stat; int ret = AUTH_GSS_COMPLETE; - + if (state->context != GSS_C_NO_CONTEXT) { maj_stat = gss_delete_sec_context( &min_stat, &state->context, GSS_C_NO_BUFFER @@ -674,7 +815,7 @@ int authenticate_gss_server_clean(gss_server_state *state) free(state->ccname); state->ccname = NULL; } - + return ret; } @@ -686,13 +827,13 @@ int authenticate_gss_server_step( gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; int ret = AUTH_GSS_CONTINUE; - + // Always clear out the old response if (state->response != NULL) { free(state->response); state->response = NULL; } - + // If there is a challenge (data from the server) we need to give it to GSS if (challenge && *challenge) { size_t len; @@ -711,7 +852,7 @@ int authenticate_gss_server_step( ret = AUTH_GSS_ERROR; goto end; } - + Py_BEGIN_ALLOW_THREADS maj_stat = gss_accept_sec_context( &min_stat, @@ -727,13 +868,13 @@ int authenticate_gss_server_step( &state->client_creds ); Py_END_ALLOW_THREADS - + if (GSS_ERROR(maj_stat)) { set_gss_error(maj_stat, min_stat); ret = AUTH_GSS_ERROR; goto end; } - + // Grab the server response to send back to the client if (output_token.length) { state->response = base64_encode( @@ -747,7 +888,7 @@ int authenticate_gss_server_step( } maj_stat = gss_release_buffer(&min_stat, &output_token); } - + // Get the user name maj_stat = gss_display_name( &min_stat, state->client_name, &output_token, NULL @@ -766,7 +907,7 @@ int authenticate_gss_server_step( } strncpy(state->username, (char*) output_token.value, output_token.length); state->username[output_token.length] = 0; - + // Get the target name if no server creds were supplied if (state->server_creds == GSS_C_NO_CREDENTIAL) { gss_name_t target_name = GSS_C_NO_NAME; @@ -801,7 +942,7 @@ int authenticate_gss_server_step( } ret = AUTH_GSS_COMPLETE; - + end: if (output_token.length) { gss_release_buffer(&min_stat, &output_token); @@ -824,7 +965,7 @@ static void set_gss_error(OM_uint32 err_maj, OM_uint32 err_min) gss_buffer_desc status_string; char buf_maj[512]; char buf_min[512]; - + do { maj_stat = gss_display_status( &min_stat, @@ -839,7 +980,7 @@ static void set_gss_error(OM_uint32 err_maj, OM_uint32 err_min) } strncpy(buf_maj, (char*) status_string.value, sizeof(buf_maj)); gss_release_buffer(&min_stat, &status_string); - + maj_stat = gss_display_status( &min_stat, err_min, @@ -853,7 +994,7 @@ static void set_gss_error(OM_uint32 err_maj, OM_uint32 err_min) gss_release_buffer(&min_stat, &status_string); } } while (!GSS_ERROR(maj_stat) && msg_ctx != 0); - + PyErr_SetObject( GssException_class, Py_BuildValue("((s:i)(s:i))", buf_maj, err_maj, buf_min, err_min) diff --git a/src/kerberosgss.h b/src/kerberosgss.h index 362040d..47a272c 100644 --- a/src/kerberosgss.h +++ b/src/kerberosgss.h @@ -17,6 +17,7 @@ #include #include #include +#include #define krb5_get_err_text(context,code) error_message(code) @@ -55,11 +56,18 @@ 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_server_state* delegatestate, gss_OID mech_oid, gss_client_state* state + gss_server_state* delegatestate, gss_OID mech_oid, gss_client_state* state, + const char *password ); int authenticate_gss_client_clean( gss_client_state *state ); +int authenticate_gss_sign( + gss_client_state* state, const char* message, unsigned int qop, char **token +); +int authenticate_gss_verify( + gss_client_state* state, const char* message, const char* token, unsigned int qop +); int authenticate_gss_client_step( gss_client_state *state, const char *challenge, struct gss_channel_bindings_struct *channel_bindings );