From 49e965f41257a0ed299c58a7cf1c120ddf944aaa Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Tue, 5 May 2020 14:51:36 -0400 Subject: [PATCH] Add support for setting max ssf 0 to GSS-SPNEGO Bacport form this proposed PR (still open at bacport time): https://github.com/cyrusimap/cyrus-sasl/pull/603 Signed-off-by: Simo Sorce --- m4/sasl2.m4 | 13 +++++++ plugins/gssapi.c | 44 ++++++++++++++++++++- tests/runtests.py | 91 ++++++++++++++++++++++++++++++++++++++++---- tests/t_common.c | 13 ++++--- tests/t_common.h | 3 +- tests/t_gssapi_cli.c | 25 ++++++++++-- tests/t_gssapi_srv.c | 28 +++++++++++--- 7 files changed, 194 insertions(+), 23 deletions(-) diff --git a/m4/sasl2.m4 b/m4/sasl2.m4 index 56e0504..6effe99 100644 --- a/m4/sasl2.m4 +++ b/m4/sasl2.m4 @@ -287,6 +287,19 @@ if test "$gssapi" != no; then AC_CHECK_FUNCS(gss_oid_equal) LIBS="$cmu_save_LIBS" + cmu_save_LIBS="$LIBS" + LIBS="$LIBS $GSSAPIBASE_LIBS" + if test "$ac_cv_header_gssapi_gssapi_krb5_h" = "yes"; then + AC_CHECK_DECL(GSS_KRB5_CRED_NO_CI_FLAGS_X, + [AC_DEFINE(HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X,1, + [Define if your GSSAPI implementation supports GSS_KRB5_CRED_NO_CI_FLAGS_X])],, + [ + AC_INCLUDES_DEFAULT + #include + ]) + fi + LIBS="$cmu_save_LIBS" + cmu_save_LIBS="$LIBS" LIBS="$LIBS $GSSAPIBASE_LIBS" AC_CHECK_FUNCS(gss_get_name_attribute) diff --git a/plugins/gssapi.c b/plugins/gssapi.c index 5d900c5..7480316 100644 --- a/plugins/gssapi.c +++ b/plugins/gssapi.c @@ -1783,7 +1783,49 @@ static int gssapi_client_mech_step(void *conn_context, /* We want to try for privacy */ req_flags |= GSS_C_CONF_FLAG; } - } +#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X + /* The krb5 mechanism automatically adds INTEG and CONF flags even when + * not specified, this has the effect of rendering explicit requests + * of no confidentiality and integrity via setting maxssf 0 moot. + * However to interoperate with Windows machines it needs to be + * possible to unset these flags as Windows machines refuse to allow + * two layers (say TLS and GSSAPI) to both provide these services. + * So if we do not suppress these flags a SASL/GSS-SPNEGO negotiation + * over, say, LDAPS will fail against Windows Servers */ + } else if (params->props.max_ssf == 0) { + gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER; + if (client_creds == GSS_C_NO_CREDENTIAL) { + gss_OID_set_desc mechs = { 0 }; + gss_OID_set desired_mechs = GSS_C_NO_OID_SET; + if (text->mech_type != GSS_C_NO_OID) { + mechs.count = 1; + mechs.elements = text->mech_type; + desired_mechs = &mechs; + } + + maj_stat = gss_acquire_cred(&min_stat, GSS_C_NO_NAME, + GSS_C_INDEFINITE, desired_mechs, + GSS_C_INITIATE, + &text->client_creds, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + sasl_gss_seterror(text->utils, maj_stat, min_stat); + sasl_gss_free_context_contents(text); + return SASL_FAIL; + } + client_creds = text->client_creds; + } + + maj_stat = gss_set_cred_option(&min_stat, &client_creds, + (gss_OID)GSS_KRB5_CRED_NO_CI_FLAGS_X, + &empty_buffer); + if (GSS_ERROR(maj_stat)) { + sasl_gss_seterror(text->utils, maj_stat, min_stat); + sasl_gss_free_context_contents(text); + return SASL_FAIL; + } +#endif + } + if (params->props.security_flags & SASL_SEC_PASS_CREDENTIALS) { req_flags = req_flags | GSS_C_DELEG_FLAG; diff --git a/tests/runtests.py b/tests/runtests.py index fc9cf24..4106401 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -6,6 +6,7 @@ import os import shutil import signal import subprocess +import sys import time from string import Template @@ -149,11 +150,12 @@ def gssapi_basic_test(kenv): srv.returncode, srv.stderr.read().decode('utf-8'))) except Exception as e: print("FAIL: {}".format(e)) - return + return 1 print("PASS: CLI({}) SRV({})".format( cli.stdout.read().decode('utf-8').strip(), srv.stdout.read().decode('utf-8').strip())) + return 0 def gssapi_channel_binding_test(kenv): try: @@ -178,11 +180,12 @@ def gssapi_channel_binding_test(kenv): srv.returncode, srv.stderr.read().decode('utf-8'))) except Exception as e: print("FAIL: {}".format(e)) - return + return 1 print("PASS: CLI({}) SRV({})".format( cli.stdout.read().decode('utf-8').strip(), srv.stdout.read().decode('utf-8').strip())) + return 0 def gssapi_channel_binding_mismatch_test(kenv): result = "FAIL" @@ -212,11 +215,70 @@ def gssapi_channel_binding_mismatch_test(kenv): cli.returncode, cli_err, srv.returncode, srv_err)) except Exception as e: print("{}: {}".format(result, e)) - return + return 0 print("FAIL: This test should fail [CLI({}) SRV({})]".format( cli.stdout.read().decode('utf-8').strip(), srv.stdout.read().decode('utf-8').strip())) + return 1 + +def gss_spnego_basic_test(kenv): + try: + srv = subprocess.Popen(["../tests/t_gssapi_srv", "-N"], + 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", "-N"], + 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 1 + + print("PASS: CLI({}) SRV({})".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) + return 0 + +def gss_spnego_zeromaxssf_test(kenv): + try: + srv = subprocess.Popen(["../tests/t_gssapi_srv", "-N", "-z"], + 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", "-N", "-z"], + 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 1 + + print("PASS: CLI({}) SRV({})".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) + return 0 def gssapi_tests(testdir): """ SASL/GSSAPI Tests """ @@ -225,20 +287,32 @@ def gssapi_tests(testdir): #print("KDC: {}, ENV: {}".format(kdc, kenv)) kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log') + err = 0 + print('GSSAPI BASIC:') print(' ', end='') - gssapi_basic_test(kenv) + err += gssapi_basic_test(kenv) print('GSSAPI CHANNEL BINDING:') print(' ', end='') - gssapi_channel_binding_test(kenv) + err += gssapi_channel_binding_test(kenv) print('GSSAPI CHANNEL BINDING MISMTACH:') print(' ', end='') - gssapi_channel_binding_mismatch_test(kenv) + err += gssapi_channel_binding_mismatch_test(kenv) + + print('GSS-SPNEGO BASIC:') + print(' ', end='') + err += gss_spnego_basic_test(kenv) + + print('GSS-SPNEGO 0 MAXSSF:') + print(' ', end='') + err += gss_spnego_zeromaxssf_test(kenv) os.killpg(kdc.pid, signal.SIGTERM) + return err + if __name__ == "__main__": @@ -253,4 +327,7 @@ if __name__ == "__main__": shutil.rmtree(T) os.makedirs(T) - gssapi_tests(T) + err = gssapi_tests(T) + if err != 0: + print('{} test(s) FAILED'.format(err)) + sys.exit(-1) diff --git a/tests/t_common.c b/tests/t_common.c index 478e6a1..f56098e 100644 --- a/tests/t_common.c +++ b/tests/t_common.c @@ -23,20 +23,21 @@ void send_string(int sd, const char *s, unsigned int l) if (ret != l) s_error("send data", ret, l, errno); } -void recv_string(int sd, char *buf, unsigned int *buflen) +void recv_string(int sd, char *buf, unsigned int *buflen, bool allow_eof) { + unsigned int bufsize = *buflen; unsigned int l; ssize_t ret; + *buflen = 0; + ret = recv(sd, &l, sizeof(l), MSG_WAITALL); + if (allow_eof && ret == 0) return; if (ret != sizeof(l)) s_error("recv size", ret, sizeof(l), errno); - if (l == 0) { - *buflen = 0; - return; - } + if (l == 0) return; - if (*buflen < l) s_error("recv len", l, *buflen, E2BIG); + if (bufsize < l) s_error("recv len", l, bufsize, E2BIG); ret = recv(sd, buf, l, 0); if (ret != l) s_error("recv data", ret, l, errno); diff --git a/tests/t_common.h b/tests/t_common.h index a10def1..be24a53 100644 --- a/tests/t_common.h +++ b/tests/t_common.h @@ -4,6 +4,7 @@ #include "config.h" #include +#include #include #include @@ -12,7 +13,7 @@ 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 recv_string(int sd, char *buf, unsigned int *buflen, bool allow_eof); 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 a44a3f5..d9eafe1 100644 --- a/tests/t_gssapi_cli.c +++ b/tests/t_gssapi_cli.c @@ -46,12 +46,21 @@ int main(int argc, char *argv[]) char cb_buf[256]; int sd; int c, r; + const char *sasl_mech = "GSSAPI"; + bool spnego = false; + bool zeromaxssf = false; - while ((c = getopt(argc, argv, "c:")) != EOF) { + while ((c = getopt(argc, argv, "c:zN")) != EOF) { switch (c) { case 'c': parse_cb(&cb, cb_buf, 256, optarg); break; + case 'z': + zeromaxssf = true; + break; + case 'N': + spnego = true; + break; default: break; } @@ -78,7 +87,17 @@ int main(int argc, char *argv[]) sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); } - r = sasl_client_start(conn, "GSSAPI", NULL, &data, &len, &chosenmech); + if (spnego) { + sasl_mech = "GSS-SPNEGO"; + } + + if (zeromaxssf) { + /* set all security properties to 0 including maxssf */ + sasl_security_properties_t secprops = { 0 }; + sasl_setprop(conn, SASL_SEC_PROPS, &secprops); + } + + r = sasl_client_start(conn, sasl_mech, NULL, &data, &len, &chosenmech); if (r != SASL_OK && r != SASL_CONTINUE) { saslerr(r, "starting SASL negotiation"); printf("\n%s\n", sasl_errdetail(conn)); @@ -90,7 +109,7 @@ int main(int argc, char *argv[]) while (r == SASL_CONTINUE) { send_string(sd, data, len); len = 8192; - recv_string(sd, buf, &len); + recv_string(sd, buf, &len, false); r = sasl_client_step(conn, buf, len, NULL, &data, &len); if (r != SASL_OK && r != SASL_CONTINUE) { diff --git a/tests/t_gssapi_srv.c b/tests/t_gssapi_srv.c index ef1217f..448a218 100644 --- a/tests/t_gssapi_srv.c +++ b/tests/t_gssapi_srv.c @@ -56,12 +56,21 @@ int main(int argc, char *argv[]) unsigned char cb_buf[256]; int sd; int c, r; + const char *sasl_mech = "GSSAPI"; + bool spnego = false; + bool zeromaxssf = false; - while ((c = getopt(argc, argv, "c:")) != EOF) { + while ((c = getopt(argc, argv, "c:zN")) != EOF) { switch (c) { case 'c': parse_cb(&cb, cb_buf, 256, optarg); break; + case 'z': + zeromaxssf = true; + break; + case 'N': + spnego = true; + break; default: break; } @@ -90,12 +99,22 @@ int main(int argc, char *argv[]) sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); } + if (spnego) { + sasl_mech = "GSS-SPNEGO"; + } + + if (zeromaxssf) { + /* set all security properties to 0 including maxssf */ + sasl_security_properties_t secprops = { 0 }; + sasl_setprop(conn, SASL_SEC_PROPS, &secprops); + } + sd = setup_socket(); len = 8192; - recv_string(sd, buf, &len); + recv_string(sd, buf, &len, false); - r = sasl_server_start(conn, "GSSAPI", buf, len, &data, &len); + r = sasl_server_start(conn, sasl_mech, buf, len, &data, &len); if (r != SASL_OK && r != SASL_CONTINUE) { saslerr(r, "starting SASL negotiation"); printf("\n%s\n", sasl_errdetail(conn)); @@ -105,7 +124,7 @@ int main(int argc, char *argv[]) while (r == SASL_CONTINUE) { send_string(sd, data, len); len = 8192; - recv_string(sd, buf, &len); + recv_string(sd, buf, &len, true); r = sasl_server_step(conn, buf, len, &data, &len); if (r != SASL_OK && r != SASL_CONTINUE) { @@ -113,7 +132,6 @@ int main(int argc, char *argv[]) printf("\n%s\n", sasl_errdetail(conn)); exit(-1); } - } if (r != SASL_OK) exit(-1); -- 2.18.2