From aa8b6b2275fd14ba2cca3d2339ae61c7e7ddfa70 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Tue, 5 May 2020 14:08:48 -0400 Subject: [PATCH] Add Channel Binding support for GSSAPI/GSS-SPNEGO Backport of commit ids: 829a6ed086432e26dafa9d1dcf892aef4c42cfbd 944bd8a6205f840b105206ef83e8f6b9dff0138e Signed-off-by: Simo Sorce --- plugins/gssapi.c | 30 +++++++++++--- tests/runtests.py | 93 ++++++++++++++++++++++++++++++++++++++++---- tests/t_common.c | 24 ++++++++---- tests/t_common.h | 5 ++- tests/t_gssapi_cli.c | 24 ++++++++++-- tests/t_gssapi_srv.c | 24 ++++++++++-- 6 files changed, 172 insertions(+), 28 deletions(-) diff --git a/plugins/gssapi.c b/plugins/gssapi.c index ff663da..5d900c5 100644 --- a/plugins/gssapi.c +++ b/plugins/gssapi.c @@ -830,7 +830,9 @@ gssapi_server_mech_authneg(context_t *text, gss_buffer_desc name_without_realm; gss_name_t client_name_MN = NULL, without = NULL; gss_OID mech_type; - + gss_channel_bindings_t bindings = GSS_C_NO_CHANNEL_BINDINGS; + struct gss_channel_bindings_struct cb = {0}; + input_token = &real_input_token; output_token = &real_output_token; output_token->value = NULL; output_token->length = 0; @@ -902,6 +904,12 @@ gssapi_server_mech_authneg(context_t *text, real_input_token.length = clientinlen; } + if (params->cbinding != NULL) { + cb.application_data.length = params->cbinding->len; + cb.application_data.value = params->cbinding->data; + bindings = &cb; + } + GSS_LOCK_MUTEX_CTX(params->utils, text); maj_stat = @@ -909,7 +917,7 @@ gssapi_server_mech_authneg(context_t *text, &(text->gss_ctx), server_creds, input_token, - GSS_C_NO_CHANNEL_BINDINGS, + bindings, &text->client_name, &mech_type, output_token, @@ -1505,7 +1513,8 @@ static sasl_server_plug_t gssapi_server_plugins[] = | SASL_SEC_PASS_CREDENTIALS, SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY - | SASL_FEAT_DONTUSE_USERPASSWD, /* features */ + | SASL_FEAT_DONTUSE_USERPASSWD + | SASL_FEAT_CHANNEL_BINDING, /* features */ NULL, /* glob_context */ &gssapi_server_mech_new, /* mech_new */ &gssapi_server_mech_step, /* mech_step */ @@ -1529,6 +1538,7 @@ static sasl_server_plug_t gssapi_server_plugins[] = SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY | SASL_FEAT_DONTUSE_USERPASSWD + | SASL_FEAT_CHANNEL_BINDING | SASL_FEAT_SUPPORTS_HTTP, /* features */ &gss_spnego_oid, /* glob_context */ &gssapi_server_mech_new, /* mech_new */ @@ -1662,6 +1672,8 @@ static int gssapi_client_mech_step(void *conn_context, input_token->value = NULL; input_token->length = 0; gss_cred_id_t client_creds = (gss_cred_id_t)params->gss_creds; + gss_channel_bindings_t bindings = GSS_C_NO_CHANNEL_BINDINGS; + struct gss_channel_bindings_struct cb = {0}; if (clientout) *clientout = NULL; @@ -1777,6 +1789,12 @@ static int gssapi_client_mech_step(void *conn_context, req_flags = req_flags | GSS_C_DELEG_FLAG; } + if (params->cbinding != NULL) { + cb.application_data.length = params->cbinding->len; + cb.application_data.value = params->cbinding->data; + bindings = &cb; + } + GSS_LOCK_MUTEX_CTX(params->utils, text); maj_stat = gss_init_sec_context(&min_stat, client_creds, /* GSS_C_NO_CREDENTIAL */ @@ -1785,7 +1803,7 @@ static int gssapi_client_mech_step(void *conn_context, text->mech_type, req_flags, 0, - GSS_C_NO_CHANNEL_BINDINGS, + bindings, input_token, NULL, output_token, @@ -2190,7 +2208,8 @@ static sasl_client_plug_t gssapi_client_plugins[] = | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ SASL_FEAT_NEEDSERVERFQDN | SASL_FEAT_WANT_CLIENT_FIRST - | SASL_FEAT_ALLOWS_PROXY, /* features */ + | SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_CHANNEL_BINDING, /* features */ gssapi_required_prompts, /* required_prompts */ GSS_C_NO_OID, /* glob_context */ &gssapi_client_mech_new, /* mech_new */ @@ -2213,6 +2232,7 @@ static sasl_client_plug_t gssapi_client_plugins[] = SASL_FEAT_NEEDSERVERFQDN | SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_CHANNEL_BINDING | SASL_FEAT_SUPPORTS_HTTP, /* features */ gssapi_required_prompts, /* required_prompts */ &gss_spnego_oid, /* glob_context */ diff --git a/tests/runtests.py b/tests/runtests.py index f645adf..fc9cf24 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 import argparse +import base64 import os import shutil import signal @@ -126,14 +127,7 @@ def setup_kdc(testdir, env): return kdc, env - -def gssapi_tests(testdir): - """ SASL/GSSAPI Tests """ - env = setup_socket_wrappers(testdir) - kdc, kenv = setup_kdc(testdir, env) - #print("KDC: {}, ENV: {}".format(kdc, kenv)) - kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log') - +def gssapi_basic_test(kenv): try: srv = subprocess.Popen(["../tests/t_gssapi_srv"], stdout=subprocess.PIPE, @@ -155,11 +149,94 @@ def gssapi_tests(testdir): srv.returncode, srv.stderr.read().decode('utf-8'))) except Exception as e: print("FAIL: {}".format(e)) + return + + print("PASS: CLI({}) SRV({})".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) + +def gssapi_channel_binding_test(kenv): + try: + bindings = base64.b64encode("MATCHING CBS".encode('utf-8')) + srv = subprocess.Popen(["../tests/t_gssapi_srv", "-c", bindings], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=kenv) + srv.stdout.readline() # Wait for srv to say it is ready + cli = subprocess.Popen(["../tests/t_gssapi_cli", "-c", bindings], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=kenv) + try: + cli.wait(timeout=5) + srv.wait(timeout=5) + except Exception as e: + print("Failed on {}".format(e)); + cli.kill() + srv.kill() + if cli.returncode != 0 or srv.returncode != 0: + raise Exception("CLI ({}): {} --> SRV ({}): {}".format( + cli.returncode, cli.stderr.read().decode('utf-8'), + srv.returncode, srv.stderr.read().decode('utf-8'))) + except Exception as e: + print("FAIL: {}".format(e)) + return print("PASS: CLI({}) SRV({})".format( cli.stdout.read().decode('utf-8').strip(), srv.stdout.read().decode('utf-8').strip())) +def gssapi_channel_binding_mismatch_test(kenv): + result = "FAIL" + try: + bindings = base64.b64encode("SRV CBS".encode('utf-8')) + srv = subprocess.Popen(["../tests/t_gssapi_srv", "-c", bindings], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=kenv) + srv.stdout.readline() # Wait for srv to say it is ready + bindings = base64.b64encode("CLI CBS".encode('utf-8')) + cli = subprocess.Popen(["../tests/t_gssapi_cli", "-c", bindings], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=kenv) + try: + cli.wait(timeout=5) + srv.wait(timeout=5) + except Exception as e: + print("Failed on {}".format(e)); + cli.kill() + srv.kill() + if cli.returncode != 0 or srv.returncode != 0: + cli_err = cli.stderr.read().decode('utf-8').strip() + srv_err = srv.stderr.read().decode('utf-8').strip() + if "authentication failure" in srv_err: + result = "PASS" + raise Exception("CLI ({}): {} --> SRV ({}): {}".format( + cli.returncode, cli_err, srv.returncode, srv_err)) + except Exception as e: + print("{}: {}".format(result, e)) + return + + print("FAIL: This test should fail [CLI({}) SRV({})]".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) + +def gssapi_tests(testdir): + """ SASL/GSSAPI Tests """ + env = setup_socket_wrappers(testdir) + kdc, kenv = setup_kdc(testdir, env) + #print("KDC: {}, ENV: {}".format(kdc, kenv)) + kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log') + + print('GSSAPI BASIC:') + print(' ', end='') + gssapi_basic_test(kenv) + + print('GSSAPI CHANNEL BINDING:') + print(' ', end='') + gssapi_channel_binding_test(kenv) + + print('GSSAPI CHANNEL BINDING MISMTACH:') + print(' ', end='') + gssapi_channel_binding_mismatch_test(kenv) + os.killpg(kdc.pid, signal.SIGTERM) diff --git a/tests/t_common.c b/tests/t_common.c index 7168b2f..478e6a1 100644 --- a/tests/t_common.c +++ b/tests/t_common.c @@ -1,4 +1,5 @@ -/* TBD, add (C) */ +/* Copyright (C) Simo Sorce + * See COPYING file for License */ #include @@ -13,9 +14,6 @@ void send_string(int sd, const char *s, unsigned int l) { ssize_t ret; -fprintf(stderr, "s:%u ", l); -fflush(stderr); - ret = send(sd, &l, sizeof(l), 0); if (ret != sizeof(l)) s_error("send size", ret, sizeof(l), errno); @@ -34,8 +32,6 @@ void recv_string(int sd, char *buf, unsigned int *buflen) if (ret != sizeof(l)) s_error("recv size", ret, sizeof(l), errno); if (l == 0) { -fprintf(stderr, "r:0 "); -fflush(stderr); *buflen = 0; return; } @@ -45,8 +41,6 @@ fflush(stderr); ret = recv(sd, buf, l, 0); if (ret != l) s_error("recv data", ret, l, errno); -fprintf(stderr, "r:%ld ", ret); -fflush(stderr); *buflen = ret; } @@ -65,4 +59,18 @@ int getpath(void *context __attribute__((unused)), const char **path) return SASL_OK; } +void parse_cb(sasl_channel_binding_t *cb, char *buf, unsigned max, char *in) +{ + unsigned len; + int r; + r = sasl_decode64(in, strlen(in), buf, max, &len); + if (r != SASL_OK) { + saslerr(r, "failed to parse channel bindings"); + exit(-1); + } + cb->name = "TEST BINDINGS"; + cb->critical = 0; + cb->data = (unsigned char *)buf; + cb->len = len; +} diff --git a/tests/t_common.h b/tests/t_common.h index 4ee1976..a10def1 100644 --- a/tests/t_common.h +++ b/tests/t_common.h @@ -1,4 +1,5 @@ -/* TBD, add (C) */ +/* Copyright (C) Simo Sorce + * See COPYING file for License */ #include "config.h" @@ -7,9 +8,11 @@ #include #include +#include void s_error(const char *hdr, ssize_t ret, ssize_t len, int err); void send_string(int sd, const char *s, unsigned int l); void recv_string(int sd, char *buf, unsigned int *buflen); void saslerr(int why, const char *what); int getpath(void *context __attribute__((unused)), const char **path); +void parse_cb(sasl_channel_binding_t *cb, char *buf, unsigned max, char *in); diff --git a/tests/t_gssapi_cli.c b/tests/t_gssapi_cli.c index c833c05..a44a3f5 100644 --- a/tests/t_gssapi_cli.c +++ b/tests/t_gssapi_cli.c @@ -1,4 +1,5 @@ -/* TBD, add (C) */ +/* Copyright (C) Simo Sorce + * See COPYING file for License */ #include "t_common.h" @@ -13,6 +14,7 @@ #include #include +#include static int setup_socket(void) { @@ -32,7 +34,7 @@ static int setup_socket(void) return sock; } -int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) +int main(int argc, char *argv[]) { sasl_callback_t callbacks[2] = {}; char buf[8192]; @@ -40,8 +42,20 @@ int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) sasl_conn_t *conn; const char *data; unsigned int len; + sasl_channel_binding_t cb = {0}; + char cb_buf[256]; int sd; - int r; + int c, r; + + while ((c = getopt(argc, argv, "c:")) != EOF) { + switch (c) { + case 'c': + parse_cb(&cb, cb_buf, 256, optarg); + break; + default: + break; + } + } /* initialize the sasl library */ callbacks[0].id = SASL_CB_GETPATH; @@ -60,6 +74,10 @@ int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) exit(-1); } + if (cb.name) { + sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); + } + r = sasl_client_start(conn, "GSSAPI", NULL, &data, &len, &chosenmech); if (r != SASL_OK && r != SASL_CONTINUE) { saslerr(r, "starting SASL negotiation"); diff --git a/tests/t_gssapi_srv.c b/tests/t_gssapi_srv.c index 29f538d..ef1217f 100644 --- a/tests/t_gssapi_srv.c +++ b/tests/t_gssapi_srv.c @@ -1,4 +1,5 @@ -/* TBD, add (C) */ +/* Copyright (C) Simo Sorce + * See COPYING file for License */ #include "t_common.h" @@ -44,15 +45,28 @@ static int setup_socket(void) return sd; } -int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) +int main(int argc, char *argv[]) { sasl_callback_t callbacks[2] = {}; char buf[8192]; sasl_conn_t *conn; const char *data; unsigned int len; + sasl_channel_binding_t cb = {0}; + unsigned char cb_buf[256]; int sd; - int r; + int c, r; + + while ((c = getopt(argc, argv, "c:")) != EOF) { + switch (c) { + case 'c': + parse_cb(&cb, cb_buf, 256, optarg); + break; + default: + break; + } + } + /* initialize the sasl library */ callbacks[0].id = SASL_CB_GETPATH; @@ -72,6 +86,10 @@ int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) exit(-1); } + if (cb.name) { + sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); + } + sd = setup_socket(); len = 8192; -- 2.18.2