diff --git a/SOURCES/Add-channel-bindings-tests.patch b/SOURCES/Add-channel-bindings-tests.patch new file mode 100644 index 0000000..92c803d --- /dev/null +++ b/SOURCES/Add-channel-bindings-tests.patch @@ -0,0 +1,420 @@ +From 6af3fd382e99a9724413929af7eee7c86326ffd9 Mon Sep 17 00:00:00 2001 +From: Isaac Boukris +Date: Fri, 20 Mar 2020 00:17:28 +0100 +Subject: [PATCH] Add channel bindings tests + +[ghudson@mit.edu: adjusted test program to output channel-bound state +instead of optionally enforcing it; adjusted tests to check program +output; split out tests into separate Python script; made cosmetic +changes] + +ticket: 8900 +(cherry picked from commit b0b21b6d25b06f3e2b365dfe9dd4c99b3d43bf57) +[rharwood@redhat.com: .gitignore] +(cherry picked from commit 3e92520c1417f22447751cd9172d5ab30c2e0ad8) +--- + src/plugins/gssapi/negoextest/main.c | 18 +++++ + src/tests/gssapi/Makefile.in | 49 ++++++------ + src/tests/gssapi/common.c | 25 ++++-- + src/tests/gssapi/common.h | 9 +++ + src/tests/gssapi/deps | 4 + + src/tests/gssapi/t_bindings.c | 111 +++++++++++++++++++++++++++ + src/tests/gssapi/t_bindings.py | 43 +++++++++++ + src/tests/gssapi/t_negoex.py | 7 ++ + 8 files changed, 237 insertions(+), 29 deletions(-) + create mode 100644 src/tests/gssapi/t_bindings.c + create mode 100644 src/tests/gssapi/t_bindings.py + +diff --git a/src/plugins/gssapi/negoextest/main.c b/src/plugins/gssapi/negoextest/main.c +index 6c340f41b..72fc5273a 100644 +--- a/src/plugins/gssapi/negoextest/main.c ++++ b/src/plugins/gssapi/negoextest/main.c +@@ -57,6 +57,15 @@ gss_init_sec_context(OM_uint32 *minor_status, + const char *envstr; + uint8_t hops, mech_last_octet; + ++ envstr = getenv("GSS_INIT_BINDING"); ++ if (envstr != NULL) { ++ assert(strlen(envstr) > 0); ++ assert(input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS); ++ assert(strlen(envstr) == input_chan_bindings->application_data.length); ++ assert(strcmp((char *)input_chan_bindings->application_data.value, ++ envstr) == 0); ++ } ++ + if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) { + envstr = getenv("HOPS"); + hops = (envstr != NULL) ? atoi(envstr) : 1; +@@ -112,6 +121,15 @@ gss_accept_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle, + uint8_t hops, mech_last_octet; + const char *envstr; + ++ envstr = getenv("GSS_ACCEPT_BINDING"); ++ if (envstr != NULL) { ++ assert(strlen(envstr) > 0); ++ assert(input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS); ++ assert(strlen(envstr) == input_chan_bindings->application_data.length); ++ assert(strcmp((char *)input_chan_bindings->application_data.value, ++ envstr) == 0); ++ } ++ + /* + * The unwrapped token sits at the end and is just one byte giving the + * remaining number of hops. The final octet of the mech encoding should +diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in +index 5cc1e0f58..68c132b79 100644 +--- a/src/tests/gssapi/Makefile.in ++++ b/src/tests/gssapi/Makefile.in +@@ -9,33 +9,33 @@ LOCALINCLUDES = -I$(srcdir)/../../lib/gssapi/mechglue \ + -I../../lib/gssapi/generic + + SRCS= $(srcdir)/ccinit.c $(srcdir)/ccrefresh.c $(srcdir)/common.c \ +- $(srcdir)/t_accname.c $(srcdir)/t_add_cred.c $(srcdir)/t_ccselect.c \ +- $(srcdir)/t_ciflags.c $(srcdir)/t_context.c $(srcdir)/t_credstore.c \ +- $(srcdir)/t_enctypes.c $(srcdir)/t_err.c $(srcdir)/t_export_cred.c \ +- $(srcdir)/t_export_name.c $(srcdir)/t_gssexts.c \ +- $(srcdir)/t_imp_cred.c $(srcdir)/t_imp_name.c $(srcdir)/t_invalid.c \ +- $(srcdir)/t_inq_cred.c $(srcdir)/t_inq_ctx.c \ ++ $(srcdir)/t_accname.c $(srcdir)/t_add_cred.c $(srcdir)/t_bindings.c \ ++ $(srcdir)/t_ccselect.c $(srcdir)/t_ciflags.c $(srcdir)/t_context.c \ ++ $(srcdir)/t_credstore.c $(srcdir)/t_enctypes.c $(srcdir)/t_err.c \ ++ $(srcdir)/t_export_cred.c $(srcdir)/t_export_name.c \ ++ $(srcdir)/t_gssexts.c $(srcdir)/t_imp_cred.c $(srcdir)/t_imp_name.c \ ++ $(srcdir)/t_invalid.c $(srcdir)/t_inq_cred.c $(srcdir)/t_inq_ctx.c \ + $(srcdir)/t_inq_mechs_name.c $(srcdir)/t_iov.c \ + $(srcdir)/t_lifetime.c $(srcdir)/t_namingexts.c $(srcdir)/t_oid.c \ + $(srcdir)/t_pcontok.c $(srcdir)/t_prf.c $(srcdir)/t_s4u.c \ + $(srcdir)/t_s4u2proxy_krb5.c $(srcdir)/t_saslname.c \ + $(srcdir)/t_spnego.c $(srcdir)/t_srcattrs.c + +-OBJS= ccinit.o ccrefresh.o common.o t_accname.o t_add_cred.o t_ccselect.o \ +- t_ciflags.o t_context.o t_credstore.o t_enctypes.o t_err.o \ +- t_export_cred.o t_export_name.o t_gssexts.o t_imp_cred.o t_imp_name.o \ +- t_invalid.o t_inq_cred.o t_inq_ctx.o t_inq_mechs_name.o t_iov.o \ +- t_lifetime.o t_namingexts.o t_oid.o t_pcontok.o t_prf.o t_s4u.o \ +- t_s4u2proxy_krb5.o t_saslname.o t_spnego.o t_srcattrs.o ++OBJS= ccinit.o ccrefresh.o common.o t_accname.o t_add_cred.o t_bindings.o \ ++ t_ccselect.o t_ciflags.o t_context.o t_credstore.o t_enctypes.o \ ++ t_err.o t_export_cred.o t_export_name.o t_gssexts.o t_imp_cred.o \ ++ t_imp_name.o t_invalid.o t_inq_cred.o t_inq_ctx.o t_inq_mechs_name.o \ ++ t_iov.o t_lifetime.o t_namingexts.o t_oid.o t_pcontok.o t_prf.o \ ++ t_s4u.o t_s4u2proxy_krb5.o t_saslname.o t_spnego.o t_srcattrs.o + + COMMON_DEPS= common.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) + COMMON_LIBS= common.o $(GSS_LIBS) $(KRB5_BASE_LIBS) + +-all: ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags t_context \ +- t_credstore t_enctypes t_err t_export_cred t_export_name t_gssexts \ +- t_imp_cred t_imp_name t_invalid t_inq_cred t_inq_ctx t_inq_mechs_name \ +- t_iov t_lifetime t_namingexts t_oid t_pcontok t_prf t_s4u \ +- t_s4u2proxy_krb5 t_saslname t_spnego t_srcattrs ++all: ccinit ccrefresh t_accname t_add_cred t_bindings t_ccselect t_ciflags \ ++ t_context t_credstore t_enctypes t_err t_export_cred t_export_name \ ++ t_gssexts t_imp_cred t_imp_name t_invalid t_inq_cred t_inq_ctx \ ++ t_inq_mechs_name t_iov t_lifetime t_namingexts t_oid t_pcontok t_prf \ ++ t_s4u t_s4u2proxy_krb5 t_saslname t_spnego t_srcattrs + + check-unix: t_oid + $(RUN_TEST) ./t_invalid +@@ -43,11 +43,12 @@ check-unix: t_oid + $(RUN_TEST) ./t_prf + $(RUN_TEST) ./t_imp_name + +-check-pytests: ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags \ +- t_context t_credstore t_enctypes t_err t_export_cred t_export_name \ +- t_imp_cred t_inq_cred t_inq_ctx t_inq_mechs_name t_iov t_lifetime \ +- t_pcontok t_s4u t_s4u2proxy_krb5 t_spnego t_srcattrs ++check-pytests: ccinit ccrefresh t_accname t_add_cred t_bindings t_ccselect \ ++ t_ciflags t_context t_credstore t_enctypes t_err t_export_cred \ ++ t_export_name t_imp_cred t_inq_cred t_inq_ctx t_inq_mechs_name t_iov \ ++ t_lifetime t_pcontok t_s4u t_s4u2proxy_krb5 t_spnego t_srcattrs + $(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS) ++ $(RUNPYTEST) $(srcdir)/t_bindings.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_ccselect.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_client_keytab.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_enctypes.py $(PYTESTFLAGS) +@@ -64,6 +65,8 @@ t_accname: t_accname.o $(COMMON_DEPS) + $(CC_LINK) -o $@ t_accname.o $(COMMON_LIBS) + t_add_cred: t_add_cred.o $(COMMON_DEPS) + $(CC_LINK) -o $@ t_add_cred.o $(COMMON_LIBS) ++t_bindings: t_bindings.o $(COMMON_DEPS) ++ $(CC_LINK) -o $@ t_bindings.o $(COMMON_LIBS) + t_ccselect: t_ccselect.o $(COMMON_DEPS) + $(CC_LINK) -o $@ t_ccselect.o $(COMMON_LIBS) + t_ciflags: t_ciflags.o $(COMMON_DEPS) +@@ -118,8 +121,8 @@ t_srcattrs: t_srcattrs.o $(COMMON_DEPS) + $(CC_LINK) -o $@ t_srcattrs.o $(COMMON_LIBS) + + clean: +- $(RM) ccinit ccrefresh t_accname t_add_cred t_ccselect t_ciflags +- $(RM) t_context t_credstore t_enctypes t_err t_export_cred ++ $(RM) ccinit ccrefresh t_accname t_add_cred t_bindings t_ccselect ++ $(RM) t_ciflags t_context t_credstore t_enctypes t_err t_export_cred + $(RM) t_export_name t_gssexts t_imp_cred t_imp_name t_invalid + $(RM) t_inq_cred t_inq_ctx t_inq_mechs_name t_iov t_lifetime + $(RM) t_namingexts t_oid t_pcontok t_prf t_s4u t_s4u2proxy_krb5 +diff --git a/src/tests/gssapi/common.c b/src/tests/gssapi/common.c +index 83e9d9bb8..7ba72f7b2 100644 +--- a/src/tests/gssapi/common.c ++++ b/src/tests/gssapi/common.c +@@ -115,6 +115,20 @@ establish_contexts(gss_OID imech, gss_cred_id_t icred, gss_cred_id_t acred, + gss_name_t tname, OM_uint32 flags, gss_ctx_id_t *ictx, + gss_ctx_id_t *actx, gss_name_t *src_name, gss_OID *amech, + gss_cred_id_t *deleg_cred) ++{ ++ return establish_contexts_ex(imech, icred, acred, tname, flags, ictx, actx, ++ GSS_C_NO_CHANNEL_BINDINGS, ++ GSS_C_NO_CHANNEL_BINDINGS, NULL, src_name, ++ amech, deleg_cred); ++} ++ ++void ++establish_contexts_ex(gss_OID imech, gss_cred_id_t icred, gss_cred_id_t acred, ++ gss_name_t tname, OM_uint32 flags, gss_ctx_id_t *ictx, ++ gss_ctx_id_t *actx, gss_channel_bindings_t icb, ++ gss_channel_bindings_t acb, OM_uint32 *aret_flags, ++ gss_name_t *src_name, gss_OID *amech, ++ gss_cred_id_t *deleg_cred) + { + OM_uint32 minor, imaj, amaj; + gss_buffer_desc itok, atok; +@@ -126,17 +140,16 @@ establish_contexts(gss_OID imech, gss_cred_id_t icred, gss_cred_id_t acred, + for (;;) { + (void)gss_release_buffer(&minor, &itok); + imaj = gss_init_sec_context(&minor, icred, ictx, tname, imech, flags, +- GSS_C_INDEFINITE, +- GSS_C_NO_CHANNEL_BINDINGS, &atok, NULL, +- &itok, NULL, NULL); ++ GSS_C_INDEFINITE, icb, &atok, NULL, &itok, ++ NULL, NULL); + check_gsserr("gss_init_sec_context", imaj, minor); + if (amaj == GSS_S_COMPLETE) + break; + + (void)gss_release_buffer(&minor, &atok); +- amaj = gss_accept_sec_context(&minor, actx, acred, &itok, +- GSS_C_NO_CHANNEL_BINDINGS, src_name, +- amech, &atok, NULL, NULL, deleg_cred); ++ amaj = gss_accept_sec_context(&minor, actx, acred, &itok, acb, ++ src_name, amech, &atok, aret_flags, NULL, ++ deleg_cred); + check_gsserr("gss_accept_sec_context", amaj, minor); + (void)gss_release_buffer(&minor, &itok); + if (imaj == GSS_S_COMPLETE) +diff --git a/src/tests/gssapi/common.h b/src/tests/gssapi/common.h +index ae11b51d4..a5c8f87e6 100644 +--- a/src/tests/gssapi/common.h ++++ b/src/tests/gssapi/common.h +@@ -62,6 +62,15 @@ void establish_contexts(gss_OID imech, gss_cred_id_t icred, + gss_name_t *src_name, gss_OID *amech, + gss_cred_id_t *deleg_cred); + ++/* Establish contexts with channel bindings. */ ++void establish_contexts_ex(gss_OID imech, gss_cred_id_t icred, ++ gss_cred_id_t acred, gss_name_t tname, ++ OM_uint32 flags, gss_ctx_id_t *ictx, ++ gss_ctx_id_t *actx, gss_channel_bindings_t icb, ++ gss_channel_bindings_t acb, OM_uint32 *aret_flags, ++ gss_name_t *src_name, gss_OID *amech, ++ gss_cred_id_t *deleg_cred); ++ + /* Export *cred to a token, then release *cred and replace it by re-importing + * the token. */ + void export_import_cred(gss_cred_id_t *cred); +diff --git a/src/tests/gssapi/deps b/src/tests/gssapi/deps +index acd0e96f8..73e4d9a74 100644 +--- a/src/tests/gssapi/deps ++++ b/src/tests/gssapi/deps +@@ -33,6 +33,10 @@ $(OUTPRE)t_add_cred.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \ + $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \ + $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \ + common.h t_add_cred.c ++$(OUTPRE)t_bindings.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \ ++ $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \ ++ $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \ ++ common.h t_bindings.c + $(OUTPRE)t_ccselect.$(OBJEXT): $(BUILDTOP)/include/gssapi/gssapi.h \ + $(BUILDTOP)/include/gssapi/gssapi_ext.h $(BUILDTOP)/include/gssapi/gssapi_krb5.h \ + $(BUILDTOP)/include/krb5/krb5.h $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h \ +diff --git a/src/tests/gssapi/t_bindings.c b/src/tests/gssapi/t_bindings.c +new file mode 100644 +index 000000000..e8906715b +--- /dev/null ++++ b/src/tests/gssapi/t_bindings.c +@@ -0,0 +1,111 @@ ++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ ++/* ++ * Copyright (C) 2020 by Red Hat, Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in ++ * the documentation and/or other materials provided with the ++ * distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS ++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, ++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, ++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED ++ * OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++ ++#include "common.h" ++ ++/* ++ * Establish contexts (without and with GSS_C_DCE_STYLE) with the default ++ * initiator name, a specified principal name as target name, initiator ++ * bindings, and acceptor bindings. If any call is unsuccessful, display an ++ * error message. Output "yes" or "no" to indicate whether the contexts were ++ * reported as channel-bound on the acceptor. Exit with status 0 if all ++ * operations are successful, or 1 if not. ++ * ++ * Usage: ./t_bindings [-s] targetname icb acb ++ * ++ * An icb or abc value of "-" will not specify channel bindings. ++ */ ++ ++int ++main(int argc, char *argv[]) ++{ ++ OM_uint32 minor, flags1, flags2; ++ gss_name_t target_name; ++ gss_ctx_id_t ictx, actx; ++ struct gss_channel_bindings_struct icb_data = {0}, acb_data = {0}; ++ gss_channel_bindings_t icb = GSS_C_NO_CHANNEL_BINDINGS; ++ gss_channel_bindings_t acb = GSS_C_NO_CHANNEL_BINDINGS; ++ gss_OID_desc *mech; ++ ++ argv++; ++ argc--; ++ if (*argv != NULL && strcmp(*argv, "-s") == 0) { ++ mech = &mech_spnego; ++ argv++; ++ argc--; ++ } else { ++ mech = &mech_krb5; ++ } ++ ++ if (argc != 3) { ++ fprintf(stderr, "Usage: t_bindings [-s] targetname icb acb\n"); ++ return 1; ++ } ++ ++ target_name = import_name(argv[0]); ++ ++ if (strcmp(argv[1], "-") != 0) { ++ icb_data.application_data.length = strlen(argv[1]); ++ icb_data.application_data.value = argv[1]; ++ icb = &icb_data; ++ } ++ ++ if (strcmp(argv[2], "-") != 0) { ++ acb_data.application_data.length = strlen(argv[2]); ++ acb_data.application_data.value = argv[2]; ++ acb = &acb_data; ++ } ++ ++ establish_contexts_ex(mech, GSS_C_NO_CREDENTIAL, GSS_C_NO_CREDENTIAL, ++ target_name, 0, &ictx, &actx, icb, acb, &flags1, ++ NULL, NULL, NULL); ++ ++ /* Try again with GSS_C_DCE_STYLE */ ++ (void)gss_delete_sec_context(&minor, &ictx, NULL); ++ (void)gss_delete_sec_context(&minor, &actx, NULL); ++ ++ establish_contexts_ex(mech, GSS_C_NO_CREDENTIAL, GSS_C_NO_CREDENTIAL, ++ target_name, GSS_C_DCE_STYLE, &ictx, &actx, icb, acb, ++ &flags2, NULL, NULL, NULL); ++ assert((flags1 & GSS_C_CHANNEL_BOUND_FLAG) == ++ (flags2 & GSS_C_CHANNEL_BOUND_FLAG)); ++ printf("%s\n", (flags1 & GSS_C_CHANNEL_BOUND_FLAG) ? "yes" : "no"); ++ ++ (void)gss_delete_sec_context(&minor, &ictx, NULL); ++ (void)gss_delete_sec_context(&minor, &actx, NULL); ++ (void)gss_release_name(&minor, &target_name); ++ ++ return 0; ++} +diff --git a/src/tests/gssapi/t_bindings.py b/src/tests/gssapi/t_bindings.py +new file mode 100644 +index 000000000..f377977b6 +--- /dev/null ++++ b/src/tests/gssapi/t_bindings.py +@@ -0,0 +1,43 @@ ++from k5test import * ++ ++realm = K5Realm() ++server = 'p:' + realm.host_princ ++ ++mark('krb5 channel bindings') ++realm.run(['./t_bindings', server, '-', '-'], expected_msg='no') ++realm.run(['./t_bindings', server, 'a', '-'], expected_msg='no') ++realm.run(['./t_bindings', server, 'a', 'a'], expected_msg='yes') ++realm.run(['./t_bindings', server, '-', 'a'], expected_msg='no') ++realm.run(['./t_bindings', server, 'a', 'x'], ++ expected_code=1, expected_msg='Incorrect channel bindings') ++ ++mark('SPNEGO channel bindings') ++realm.run(['./t_bindings', '-s', server, '-', '-'], expected_msg='no') ++realm.run(['./t_bindings', '-s', server, 'a', '-'], expected_msg='no') ++realm.run(['./t_bindings', '-s', server, 'a', 'a'], expected_msg='yes') ++realm.run(['./t_bindings', '-s', server, '-', 'a'], expected_msg='no') ++realm.run(['./t_bindings', '-s', server, 'a', 'x'], ++ expected_code=1, expected_msg='Incorrect channel bindings') ++ ++client_aware_conf = {'libdefaults': {'client_aware_channel_bindings': 'true'}} ++e = realm.special_env('cb_aware', False, krb5_conf=client_aware_conf) ++ ++mark('krb5 client_aware_channel_bindings') ++realm.run(['./t_bindings', server, '-', '-'], env=e, expected_msg='no') ++realm.run(['./t_bindings', server, 'a', '-'], env=e, expected_msg='no') ++realm.run(['./t_bindings', server, 'a', 'a'], env=e, expected_msg='yes') ++realm.run(['./t_bindings', server, '-', 'a'], env=e, ++ expected_code=1, expected_msg='Incorrect channel bindings') ++realm.run(['./t_bindings', server, 'a', 'x'], env=e, ++ expected_code=1, expected_msg='Incorrect channel bindings') ++ ++mark('SPNEGO client_aware_channel_bindings') ++realm.run(['./t_bindings', '-s', server, '-', '-'], env=e, expected_msg='no') ++realm.run(['./t_bindings', '-s', server, 'a', '-'], env=e, expected_msg='no') ++realm.run(['./t_bindings', '-s', server, 'a', 'a'], env=e, expected_msg='yes') ++realm.run(['./t_bindings', '-s', server, '-', 'a'], env=e, ++ expected_code=1, expected_msg='Incorrect channel bindings') ++realm.run(['./t_bindings', '-s', server, 'a', 'x'], env=e, ++ expected_code=1, expected_msg='Incorrect channel bindings') ++ ++success('channel bindings tests') +diff --git a/src/tests/gssapi/t_negoex.py b/src/tests/gssapi/t_negoex.py +index 88470d2fa..a218899c4 100644 +--- a/src/tests/gssapi/t_negoex.py ++++ b/src/tests/gssapi/t_negoex.py +@@ -139,4 +139,11 @@ msgs = ('sending [3]AP_REQUEST', 'sending [7]CHALLENGE', 'sending [8]VERIFY', + 'sending [11]CHALLENGE', 'sending [12]VERIFY', 'sending [13]VERIFY') + test({'HOPS': '4', 'KEY': 'accept-always'}, expected_trace=()) + ++mark('channel bindings') ++e = realm.env.copy() ++e.update({'HOPS': '1', 'GSS_INIT_BINDING': 'a', 'GSS_ACCEPT_BINDING': 'b'}) ++# The test mech will verify that the bindings are communicated to the ++# mech, but does not set the channel-bound flag. ++realm.run(['./t_bindings', '-s', 'h:host', 'a', 'b'], env=e, expected_msg='no') ++ + success('NegoEx tests') diff --git a/SOURCES/Add-client_aware_channel_bindings-option.patch b/SOURCES/Add-client_aware_channel_bindings-option.patch new file mode 100644 index 0000000..b8faf33 --- /dev/null +++ b/SOURCES/Add-client_aware_channel_bindings-option.patch @@ -0,0 +1,265 @@ +From fe50c57f6428d7512868663bd226bdc9007148a9 Mon Sep 17 00:00:00 2001 +From: Isaac Boukris +Date: Tue, 10 Mar 2020 13:13:17 +0100 +Subject: [PATCH] Add client_aware_channel_bindings option + +Add client support for KERB_AP_OPTIONS_CBT in the form of a profile +option "client_aware_gss_bindings". Adjust the make_etype_list() +helper so that enctype negotiation and AP_OPTIONS can be included in +the same IF-RELEVANT wrapper. + +[ghudson@mit.edu: refactored; edited documentation; wrote commit +message] + +ticket: 8900 +(cherry picked from commit 225e6ef7f021cd1a8ef2a054af0ca58b7288fd81) +(cherry picked from commit 2a08fe3d2d1972df4ffe37d4bb64b161889ff988) +--- + doc/admin/conf_files/krb5_conf.rst | 6 + + src/include/k5-int.h | 1 + + src/lib/krb5/krb/mk_req_ext.c | 177 +++++++++++++++-------------- + 3 files changed, 98 insertions(+), 86 deletions(-) + +diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst +index 1d2aa7f68..1d8ffc1e4 100644 +--- a/doc/admin/conf_files/krb5_conf.rst ++++ b/doc/admin/conf_files/krb5_conf.rst +@@ -383,6 +383,12 @@ The libdefaults section may contain any of the following relations: + credentials will fail if the client machine does not have a + keytab. The default value is false. + ++**client_aware_channel_bindings** ++ If this flag is true, then all application protocol authentication ++ requests will be flagged to indicate that the application supports ++ channel bindings when operating over a secure channel. The ++ default value is false. ++ + .. _realms: + + [realms] +diff --git a/src/include/k5-int.h b/src/include/k5-int.h +index 0d9af3d95..eb18a4cd6 100644 +--- a/src/include/k5-int.h ++++ b/src/include/k5-int.h +@@ -299,6 +299,7 @@ typedef unsigned char u_char; + #define KRB5_CONF_V4_INSTANCE_CONVERT "v4_instance_convert" + #define KRB5_CONF_V4_REALM "v4_realm" + #define KRB5_CONF_VERIFY_AP_REQ_NOFAIL "verify_ap_req_nofail" ++#define KRB5_CONF_CLIENT_AWARE_GSS_BINDINGS "client_aware_channel_bindings" + + /* Cache configuration variables */ + #define KRB5_CC_CONF_FAST_AVAIL "fast_avail" +diff --git a/src/lib/krb5/krb/mk_req_ext.c b/src/lib/krb5/krb/mk_req_ext.c +index 9fc6a0e52..08504860c 100644 +--- a/src/lib/krb5/krb/mk_req_ext.c ++++ b/src/lib/krb5/krb/mk_req_ext.c +@@ -68,10 +68,9 @@ + */ + + static krb5_error_code +-make_etype_list(krb5_context context, +- krb5_enctype *desired_etypes, +- krb5_enctype tkt_enctype, +- krb5_authdata ***authdata); ++make_ap_authdata(krb5_context context, krb5_enctype *desired_enctypes, ++ krb5_enctype tkt_enctype, krb5_boolean client_aware_cb, ++ krb5_authdata ***authdata_out); + + static krb5_error_code + generate_authenticator(krb5_context, +@@ -263,7 +262,8 @@ generate_authenticator(krb5_context context, krb5_authenticator *authent, + krb5_enctype tkt_enctype) + { + krb5_error_code retval; +- krb5_authdata **ext_authdata = NULL; ++ krb5_authdata **ext_authdata = NULL, **ap_authdata, **combined; ++ int client_aware_cb; + + authent->client = client; + authent->checksum = cksum; +@@ -297,99 +297,104 @@ generate_authenticator(krb5_context context, krb5_authenticator *authent, + krb5_free_authdata(context, ext_authdata); + } + +- /* Only send EtypeList if we prefer another enctype to tkt_enctype */ +- if (desired_etypes != NULL && desired_etypes[0] != tkt_enctype) { +- TRACE_MK_REQ_ETYPES(context, desired_etypes); +- retval = make_etype_list(context, desired_etypes, tkt_enctype, +- &authent->authorization_data); ++ retval = profile_get_boolean(context->profile, KRB5_CONF_LIBDEFAULTS, ++ KRB5_CONF_CLIENT_AWARE_GSS_BINDINGS, NULL, ++ FALSE, &client_aware_cb); ++ if (retval) ++ return retval; ++ ++ /* Add etype negotiation or channel-binding awareness authdata to the ++ * front, if appropriate. */ ++ retval = make_ap_authdata(context, desired_etypes, tkt_enctype, ++ client_aware_cb, &ap_authdata); ++ if (retval) ++ return retval; ++ if (ap_authdata != NULL) { ++ retval = krb5_merge_authdata(context, ap_authdata, ++ authent->authorization_data, &combined); ++ krb5_free_authdata(context, ap_authdata); + if (retval) + return retval; ++ krb5_free_authdata(context, authent->authorization_data); ++ authent->authorization_data = combined; + } + + return(krb5_us_timeofday(context, &authent->ctime, &authent->cusec)); + } + +-/* RFC 4537 */ ++/* Set *out to a DER-encoded RFC 4537 etype list, or to NULL if no etype list ++ * should be sent. */ + static krb5_error_code +-make_etype_list(krb5_context context, +- krb5_enctype *desired_etypes, +- krb5_enctype tkt_enctype, +- krb5_authdata ***authdata) ++make_etype_list(krb5_context context, krb5_enctype *desired_enctypes, ++ krb5_enctype tkt_enctype, krb5_data **out) + { +- krb5_error_code code; +- krb5_etype_list etypes; +- krb5_data *enc_etype_list; +- krb5_data *ad_if_relevant; +- krb5_authdata *etype_adata[2], etype_adatum, **adata; +- int i; ++ krb5_etype_list etlist; ++ int count; + +- etypes.etypes = desired_etypes; ++ *out = NULL; + +- for (etypes.length = 0; +- etypes.etypes[etypes.length] != ENCTYPE_NULL; +- etypes.length++) +- { +- /* +- * RFC 4537: +- * +- * If the enctype of the ticket session key is included in the enctype +- * list sent by the client, it SHOULD be the last on the list; +- */ +- if (etypes.length && etypes.etypes[etypes.length - 1] == tkt_enctype) ++ /* Only send a list if we prefer another enctype to tkt_enctype. */ ++ if (desired_enctypes == NULL || desired_enctypes[0] == tkt_enctype) ++ return 0; ++ ++ /* Count elements of desired_etypes, stopping at tkt_enctypes if present. ++ * (Per RFC 4537, it must be the last option if it is included.) */ ++ for (count = 0; desired_enctypes[count] != ENCTYPE_NULL; count++) { ++ if (count > 0 && desired_enctypes[count - 1] == tkt_enctype) + break; + } + +- code = encode_krb5_etype_list(&etypes, &enc_etype_list); +- if (code) { +- return code; +- } +- +- etype_adatum.magic = KV5M_AUTHDATA; +- etype_adatum.ad_type = KRB5_AUTHDATA_ETYPE_NEGOTIATION; +- etype_adatum.length = enc_etype_list->length; +- etype_adatum.contents = (krb5_octet *)enc_etype_list->data; +- +- etype_adata[0] = &etype_adatum; +- etype_adata[1] = NULL; +- +- /* Wrap in AD-IF-RELEVANT container */ +- code = encode_krb5_authdata(etype_adata, &ad_if_relevant); +- if (code) { +- krb5_free_data(context, enc_etype_list); +- return code; +- } +- +- krb5_free_data(context, enc_etype_list); +- +- adata = *authdata; +- if (adata == NULL) { +- adata = (krb5_authdata **)calloc(2, sizeof(krb5_authdata *)); +- i = 0; +- } else { +- for (i = 0; adata[i] != NULL; i++) +- ; +- +- adata = (krb5_authdata **)realloc(*authdata, +- (i + 2) * sizeof(krb5_authdata *)); +- } +- if (adata == NULL) { +- krb5_free_data(context, ad_if_relevant); +- return ENOMEM; +- } +- *authdata = adata; +- +- adata[i] = (krb5_authdata *)malloc(sizeof(krb5_authdata)); +- if (adata[i] == NULL) { +- krb5_free_data(context, ad_if_relevant); +- return ENOMEM; +- } +- adata[i]->magic = KV5M_AUTHDATA; +- adata[i]->ad_type = KRB5_AUTHDATA_IF_RELEVANT; +- adata[i]->length = ad_if_relevant->length; +- adata[i]->contents = (krb5_octet *)ad_if_relevant->data; +- free(ad_if_relevant); /* contents owned by adata[i] */ +- +- adata[i + 1] = NULL; +- +- return 0; ++ etlist.etypes = desired_enctypes; ++ etlist.length = count; ++ return encode_krb5_etype_list(&etlist, out); ++} ++ ++/* Set *authdata_out to appropriate authenticator authdata for the request, ++ * encoded in a single AD_IF_RELEVANT element. */ ++static krb5_error_code ++make_ap_authdata(krb5_context context, krb5_enctype *desired_enctypes, ++ krb5_enctype tkt_enctype, krb5_boolean client_aware_cb, ++ krb5_authdata ***authdata_out) ++{ ++ krb5_error_code ret; ++ krb5_authdata etypes_ad, flags_ad, *list[3]; ++ krb5_data *der_etypes = NULL; ++ size_t count = 0; ++ uint8_t flagbuf[4]; ++ const uint32_t KERB_AP_OPTIONS_CBT = 0x4000; ++ ++ *authdata_out = NULL; ++ ++ /* Include an ETYPE_NEGOTIATION element if appropriate. */ ++ ret = make_etype_list(context, desired_enctypes, tkt_enctype, &der_etypes); ++ if (ret) ++ goto cleanup; ++ if (der_etypes != NULL) { ++ etypes_ad.magic = KV5M_AUTHDATA; ++ etypes_ad.ad_type = KRB5_AUTHDATA_ETYPE_NEGOTIATION; ++ etypes_ad.length = der_etypes->length; ++ etypes_ad.contents = (uint8_t *)der_etypes->data; ++ list[count++] = &etypes_ad; ++ } ++ ++ /* Include an AP_OPTIONS element if the CBT flag is configured. */ ++ if (client_aware_cb != 0) { ++ store_32_le(KERB_AP_OPTIONS_CBT, flagbuf); ++ flags_ad.magic = KV5M_AUTHDATA; ++ flags_ad.ad_type = KRB5_AUTHDATA_AP_OPTIONS; ++ flags_ad.length = 4; ++ flags_ad.contents = flagbuf; ++ list[count++] = &flags_ad; ++ } ++ ++ if (count > 0) { ++ list[count] = NULL; ++ ret = krb5_encode_authdata_container(context, ++ KRB5_AUTHDATA_IF_RELEVANT, ++ list, authdata_out); ++ } ++ ++cleanup: ++ krb5_free_data(context, der_etypes); ++ return ret; + } diff --git a/SOURCES/Implement-GSS_C_CHANNEL_BOUND_FLAG.patch b/SOURCES/Implement-GSS_C_CHANNEL_BOUND_FLAG.patch new file mode 100644 index 0000000..347c0fb --- /dev/null +++ b/SOURCES/Implement-GSS_C_CHANNEL_BOUND_FLAG.patch @@ -0,0 +1,92 @@ +From 3c15e9724dae95a4bf0899a8b8efc3e9e3f486ab Mon Sep 17 00:00:00 2001 +From: Alexander Scheel +Date: Wed, 5 Jul 2017 11:38:30 -0400 +Subject: [PATCH] Implement GSS_C_CHANNEL_BOUND_FLAG + +Define a new channel-bound GSS return flag, and set it in the krb5 +mech if the initiator sent channel bindings matching the acceptor's. +Do not error out if the acceptor specifies channel bindings and the +initiator does not send them. + +[ghudson@mit.edu: simplified code changes; fleshed out commit message] + +[iboukris: cherry-picked from another PR and reduced in scope] + +ticket: 8899 (new) +(cherry picked from commit 429a31146083fac21958631c2af572b08ec91022) +(cherry picked from commit 3ea1d6296ced3a998e79356f9be212e4c5e6a5d5) +--- + src/lib/gssapi/generic/gssapi_ext.h | 2 ++ + src/lib/gssapi/krb5/accept_sec_context.c | 18 +++++++++++++----- + 2 files changed, 15 insertions(+), 5 deletions(-) + +diff --git a/src/lib/gssapi/generic/gssapi_ext.h b/src/lib/gssapi/generic/gssapi_ext.h +index 218456e44..c675e8ebb 100644 +--- a/src/lib/gssapi/generic/gssapi_ext.h ++++ b/src/lib/gssapi/generic/gssapi_ext.h +@@ -595,6 +595,8 @@ gss_store_cred_into( + * attribute (along with any applicable RFC 5587 attributes). + */ + ++#define GSS_C_CHANNEL_BOUND_FLAG 2048 /* 0x00000800 */ ++ + OM_uint32 KRB5_CALLCONV + gssspi_query_meta_data( + OM_uint32 *minor_status, +diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c +index 70dd7fc0c..9d3e2f4fe 100644 +--- a/src/lib/gssapi/krb5/accept_sec_context.c ++++ b/src/lib/gssapi/krb5/accept_sec_context.c +@@ -427,6 +427,9 @@ kg_process_extension(krb5_context context, + GSS_C_SEQUENCE_FLAG | GSS_C_DCE_STYLE | \ + GSS_C_IDENTIFY_FLAG | GSS_C_EXTENDED_ERROR_FLAG) + ++/* A zero-value channel binding, for comparison */ ++static const uint8_t null_cb[CB_MD5_LEN]; ++ + /* + * The krb5 GSS mech appropriates the authenticator checksum field from RFC + * 4120 to store structured data instead of a checksum, indicated with checksum +@@ -435,9 +438,10 @@ kg_process_extension(krb5_context context, + * + * Interpret the checksum. Read delegated creds into *deleg_out if it is not + * NULL. Set *flags_out to the allowed subset of token flags, plus +- * GSS_C_DELEG_FLAG if a delegated credential was present. Process any +- * extensions found using exts. On error, set *code_out to a krb5_error code +- * for use as a minor status value. ++ * GSS_C_DELEG_FLAG if a delegated credential was present and ++ * GSS_C_CHANNEL_BOUND_FLAG if matching channel bindings are present. Process ++ * any extensions found using exts. On error, set *code_out to a krb5_error ++ * code for use as a minor status value. + */ + static OM_uint32 + process_checksum(OM_uint32 *minor_status, krb5_context context, +@@ -450,7 +454,7 @@ process_checksum(OM_uint32 *minor_status, krb5_context context, + krb5_error_code code = 0; + OM_uint32 status, option_id, token_flags; + size_t cb_len, option_len; +- krb5_boolean valid; ++ krb5_boolean valid, token_cb_present = FALSE, cb_match = FALSE; + krb5_key subkey; + krb5_data option, empty = empty_data(); + krb5_checksum cb_cksum; +@@ -516,7 +520,9 @@ process_checksum(OM_uint32 *minor_status, krb5_context context, + goto fail; + } + assert(cb_cksum.length == cb_len); +- if (k5_bcmp(token_cb, cb_cksum.contents, cb_len) != 0) { ++ token_cb_present = (k5_bcmp(token_cb, null_cb, cb_len) != 0); ++ cb_match = (k5_bcmp(token_cb, cb_cksum.contents, cb_len) == 0); ++ if (token_cb_present && !cb_match) { + status = GSS_S_BAD_BINDINGS; + goto fail; + } +@@ -525,6 +531,8 @@ process_checksum(OM_uint32 *minor_status, krb5_context context, + /* Read the token flags and accept some of them as context flags. */ + token_flags = k5_input_get_uint32_le(&in); + *flags_out = token_flags & INITIATOR_FLAGS; ++ if (cb_match) ++ *flags_out |= GSS_C_CHANNEL_BOUND_FLAG; + + /* Read the delegated credential if present. */ + if (in.len >= 4 && (token_flags & GSS_C_DELEG_FLAG)) { diff --git a/SOURCES/Implement-KERB_AP_OPTIONS_CBT-server-side.patch b/SOURCES/Implement-KERB_AP_OPTIONS_CBT-server-side.patch new file mode 100644 index 0000000..156088b --- /dev/null +++ b/SOURCES/Implement-KERB_AP_OPTIONS_CBT-server-side.patch @@ -0,0 +1,103 @@ +From 7aea9fc73fb508e3168581990eb2e2ff7a1aea31 Mon Sep 17 00:00:00 2001 +From: Isaac Boukris +Date: Mon, 9 Mar 2020 16:04:21 +0100 +Subject: [PATCH] Implement KERB_AP_OPTIONS_CBT (server side) + +Add server support for Microsoft's KERB_AP_OPTIONS_CBT as described in +MS-KILE. If the client includes the AP option in the authenticator +authdata and the server passed channel bindings, require the bindings +to match. + +[ghudson@mit.edu: refactored to put more logic in the helper function; +added a comment; clarified commit message] + +ticket: 8900 (new) +(cherry picked from commit 4f7c77b64a048ca5e3199b26b31493698c777a9c) +(cherry picked from commit 6407bf087fe53088d91efd09df736e979cd4e8db) +--- + src/include/krb5/krb5.hin | 1 + + src/lib/gssapi/krb5/accept_sec_context.c | 45 +++++++++++++++++++++++- + 2 files changed, 45 insertions(+), 1 deletion(-) + +diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin +index f8269fb17..9264bede1 100644 +--- a/src/include/krb5/krb5.hin ++++ b/src/include/krb5/krb5.hin +@@ -1915,6 +1915,7 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype, + #define KRB5_AUTHDATA_SIGNTICKET 512 /**< formerly 142 in krb5 1.8 */ + #define KRB5_AUTHDATA_FX_ARMOR 71 + #define KRB5_AUTHDATA_AUTH_INDICATOR 97 ++#define KRB5_AUTHDATA_AP_OPTIONS 143 + /** @} */ /* end of KRB5_AUTHDATA group */ + + /* password change constants */ +diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c +index 9d3e2f4fe..175a24c4e 100644 +--- a/src/lib/gssapi/krb5/accept_sec_context.c ++++ b/src/lib/gssapi/krb5/accept_sec_context.c +@@ -430,6 +430,32 @@ kg_process_extension(krb5_context context, + /* A zero-value channel binding, for comparison */ + static const uint8_t null_cb[CB_MD5_LEN]; + ++/* Look for AP_OPTIONS in authdata. If present and the options include ++ * KERB_AP_OPTIONS_CBT, set *cbt_out to true. */ ++static krb5_error_code ++check_cbt(krb5_context context, krb5_authdata **authdata, ++ krb5_boolean *cbt_out) ++{ ++ krb5_error_code code; ++ uint32_t ad_ap_options; ++ const uint32_t KERB_AP_OPTIONS_CBT = 0x4000; ++ ++ *cbt_out = FALSE; ++ ++ code = krb5_find_authdata(context, NULL, authdata, ++ KRB5_AUTHDATA_AP_OPTIONS, &authdata); ++ if (code || authdata == NULL) ++ return code; ++ if (authdata[1] != NULL || authdata[0]->length != 4) ++ return KRB5KRB_AP_ERR_MSG_TYPE; ++ ++ ad_ap_options = load_32_le(authdata[0]->contents); ++ if (ad_ap_options & KERB_AP_OPTIONS_CBT) ++ *cbt_out = TRUE; ++ ++ return 0; ++} ++ + /* + * The krb5 GSS mech appropriates the authenticator checksum field from RFC + * 4120 to store structured data instead of a checksum, indicated with checksum +@@ -454,7 +480,7 @@ process_checksum(OM_uint32 *minor_status, krb5_context context, + krb5_error_code code = 0; + OM_uint32 status, option_id, token_flags; + size_t cb_len, option_len; +- krb5_boolean valid, token_cb_present = FALSE, cb_match = FALSE; ++ krb5_boolean valid, client_cbt, token_cb_present = FALSE, cb_match = FALSE; + krb5_key subkey; + krb5_data option, empty = empty_data(); + krb5_checksum cb_cksum; +@@ -582,6 +608,23 @@ process_checksum(OM_uint32 *minor_status, krb5_context context, + } + } + ++ /* ++ * If the client asserts the KERB_AP_OPTIONS_CBT flag (from MS-KILE) in the ++ * authenticator authdata, and the acceptor passed channel bindings, ++ * require matching channel bindings from the client. The intent is to ++ * prevent an authenticator generated for use outside of a TLS channel from ++ * being used inside of one. ++ */ ++ code = check_cbt(context, authenticator->authorization_data, &client_cbt); ++ if (code) { ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ if (client_cbt && acceptor_cb != GSS_C_NO_CHANNEL_BINDINGS && !cb_match) { ++ status = GSS_S_BAD_BINDINGS; ++ goto fail; ++ } ++ + status = GSS_S_COMPLETE; + + fail: diff --git a/SOURCES/Improve-negoex_parse_token-code-hygiene.patch b/SOURCES/Improve-negoex_parse_token-code-hygiene.patch new file mode 100644 index 0000000..c7136e1 --- /dev/null +++ b/SOURCES/Improve-negoex_parse_token-code-hygiene.patch @@ -0,0 +1,31 @@ +From ca72aa3a2e4ca8bc1b1c33e46ca59ed4b3f20393 Mon Sep 17 00:00:00 2001 +From: Robbie Harwood +Date: Tue, 9 Jun 2020 16:23:37 -0400 +Subject: [PATCH] Improve negoex_parse_token() code hygiene + +If the while loop in negoex_parse_token() runs for zero iterations, +major will be used initialized. Currently this cannot happen, but +only because both of the call sites check for zero-length tokens. +Initialize major for safety. + +[ghudson@mit.edu: rewrote commit message] + +(cherry picked from commit 4f91b6f8fa6fe1de662b3fdac0d59b7758ec642a) +(cherry picked from commit c726a72c68244129eb08b840b92144acfa776573) +--- + src/lib/gssapi/spnego/negoex_util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/lib/gssapi/spnego/negoex_util.c b/src/lib/gssapi/spnego/negoex_util.c +index 700368456..99580fd79 100644 +--- a/src/lib/gssapi/spnego/negoex_util.c ++++ b/src/lib/gssapi/spnego/negoex_util.c +@@ -454,7 +454,7 @@ negoex_parse_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, + gss_const_buffer_t token, + struct negoex_message **messages_out, size_t *count_out) + { +- OM_uint32 major; ++ OM_uint32 major = GSS_S_COMPLETE; + size_t count = 0; + struct k5input in; + struct negoex_message *messages = NULL, *newptr; diff --git a/SOURCES/Omit-PA_FOR_USER-if-we-can-t-compute-its-checksum.patch b/SOURCES/Omit-PA_FOR_USER-if-we-can-t-compute-its-checksum.patch new file mode 100644 index 0000000..a852fa7 --- /dev/null +++ b/SOURCES/Omit-PA_FOR_USER-if-we-can-t-compute-its-checksum.patch @@ -0,0 +1,35 @@ +From 4c4c22639eb2794e563370a2ee48a34dbdddc639 Mon Sep 17 00:00:00 2001 +From: Isaac Boukris +Date: Sat, 6 Jun 2020 11:03:37 +0200 +Subject: [PATCH] Omit PA_FOR_USER if we can't compute its checksum + +OpenSSL in FIPS mode will refuse to perform hmac-md5. Omit the legacy +PA_FOR_USER element in this case rather than failing out. + +[ghudson@mit.edu: minor code and comment edits; wrote commit message] + +ticket: 8912 (new) +(cherry picked from commit 03f122bdb22cfa53c7d855ed929c9541e56365e0) +(cherry picked from commit 086de78292b8ae89aba8a72926831124da44205d) +--- + src/lib/krb5/krb/s4u_creds.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/lib/krb5/krb/s4u_creds.c b/src/lib/krb5/krb/s4u_creds.c +index fc5c886d6..d8f486dc6 100644 +--- a/src/lib/krb5/krb/s4u_creds.c ++++ b/src/lib/krb5/krb/s4u_creds.c +@@ -534,6 +534,13 @@ krb5_get_self_cred_from_kdc(krb5_context context, + if (s4u_user.user_id.user != NULL && s4u_user.user_id.user->length) { + code = build_pa_for_user(context, tgtptr, &s4u_user.user_id, + &in_padata[1]); ++ /* ++ * If we couldn't compute the hmac-md5 checksum, send only the ++ * KRB5_PADATA_S4U_X509_USER; this will still work against modern ++ * Windows and MIT KDCs. ++ */ ++ if (code == KRB5_CRYPTO_INTERNAL) ++ code = 0; + if (code != 0) { + krb5_free_pa_data(context, in_padata); + goto cleanup; diff --git a/SOURCES/Pass-channel-bindings-through-SPNEGO.patch b/SOURCES/Pass-channel-bindings-through-SPNEGO.patch new file mode 100644 index 0000000..8ab4bbf --- /dev/null +++ b/SOURCES/Pass-channel-bindings-through-SPNEGO.patch @@ -0,0 +1,257 @@ +From d98f8867f8245b3c9dd506271897d0f03d69ae49 Mon Sep 17 00:00:00 2001 +From: Isaac Boukris +Date: Tue, 28 Apr 2020 18:15:55 +0200 +Subject: [PATCH] Pass channel bindings through SPNEGO + +ticket: 8907 (new) +(cherry picked from commit d16325a24c34ec9a5f6fb4910987f162e0d4d9cd) +(cherry picked from commit ee79bd43005245d3e5a2d3ec6d61146945e77717) +--- + src/lib/gssapi/spnego/gssapiP_negoex.h | 8 ++--- + src/lib/gssapi/spnego/negoex_ctx.c | 34 +++++++++++---------- + src/lib/gssapi/spnego/spnego_mech.c | 41 +++++++++++++------------- + 3 files changed, 43 insertions(+), 40 deletions(-) + +diff --git a/src/lib/gssapi/spnego/gssapiP_negoex.h b/src/lib/gssapi/spnego/gssapiP_negoex.h +index 44b08f523..489ab7c42 100644 +--- a/src/lib/gssapi/spnego/gssapiP_negoex.h ++++ b/src/lib/gssapi/spnego/gssapiP_negoex.h +@@ -201,10 +201,10 @@ negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx, + OM_uint32 + negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, + gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req, +- gss_buffer_t input_token, gss_buffer_t output_token, +- OM_uint32 *time_rec); ++ gss_buffer_t input_token, gss_channel_bindings_t bindings, ++ gss_buffer_t output_token, OM_uint32 *time_rec); + + OM_uint32 + negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, +- gss_buffer_t input_token, gss_buffer_t output_token, +- OM_uint32 *time_rec); ++ gss_buffer_t input_token, gss_channel_bindings_t bindings, ++ gss_buffer_t output_token, OM_uint32 *time_rec); +diff --git a/src/lib/gssapi/spnego/negoex_ctx.c b/src/lib/gssapi/spnego/negoex_ctx.c +index 18d9d4147..8848ee4db 100644 +--- a/src/lib/gssapi/spnego/negoex_ctx.c ++++ b/src/lib/gssapi/spnego/negoex_ctx.c +@@ -276,7 +276,8 @@ static OM_uint32 + mech_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, + gss_name_t target, OM_uint32 req_flags, OM_uint32 time_req, + struct negoex_message *messages, size_t nmessages, +- gss_buffer_t output_token, OM_uint32 *time_rec) ++ gss_channel_bindings_t bindings, gss_buffer_t output_token, ++ OM_uint32 *time_rec) + { + OM_uint32 major, first_major = 0, first_minor = 0; + struct negoex_auth_mech *mech = NULL; +@@ -316,10 +317,9 @@ mech_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, + mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); + + major = gss_init_sec_context(minor, cred, &mech->mech_context, target, +- mech->oid, req_flags, time_req, +- GSS_C_NO_CHANNEL_BINDINGS, input_token, +- &ctx->actual_mech, output_token, +- &ctx->ctx_flags, time_rec); ++ mech->oid, req_flags, time_req, bindings, ++ input_token, &ctx->actual_mech, ++ output_token, &ctx->ctx_flags, time_rec); + + if (major == GSS_S_COMPLETE) + mech->complete = 1; +@@ -351,7 +351,8 @@ mech_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, + static OM_uint32 + mech_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, + gss_cred_id_t cred, struct negoex_message *messages, +- size_t nmessages, gss_buffer_t output_token, OM_uint32 *time_rec) ++ size_t nmessages, gss_channel_bindings_t bindings, ++ gss_buffer_t output_token, OM_uint32 *time_rec) + { + OM_uint32 major, tmpmin; + struct negoex_auth_mech *mech; +@@ -395,10 +396,10 @@ mech_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, + gss_release_cred(&tmpmin, &ctx->deleg_cred); + + major = gss_accept_sec_context(minor, &mech->mech_context, cred, +- &msg->token, GSS_C_NO_CHANNEL_BINDINGS, +- &ctx->internal_name, &ctx->actual_mech, +- output_token, &ctx->ctx_flags, +- time_rec, &ctx->deleg_cred); ++ &msg->token, bindings, &ctx->internal_name, ++ &ctx->actual_mech, output_token, ++ &ctx->ctx_flags, time_rec, ++ &ctx->deleg_cred); + + if (major == GSS_S_COMPLETE) + mech->complete = 1; +@@ -609,8 +610,8 @@ make_output_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, + OM_uint32 + negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, + gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req, +- gss_buffer_t input_token, gss_buffer_t output_token, +- OM_uint32 *time_rec) ++ gss_buffer_t input_token, gss_channel_bindings_t bindings, ++ gss_buffer_t output_token, OM_uint32 *time_rec) + { + OM_uint32 major, tmpmin; + gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; +@@ -663,7 +664,8 @@ negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, + /* Process the input token and/or produce an output token. This may prune + * the mech list, but on success there will be at least one mech entry. */ + major = mech_init(minor, ctx, cred, target_name, req_flags, time_req, +- messages, nmessages, &mech_output_token, time_rec); ++ messages, nmessages, bindings, &mech_output_token, ++ time_rec); + if (major != GSS_S_COMPLETE) + goto cleanup; + assert(!K5_TAILQ_EMPTY(&ctx->negoex_mechs)); +@@ -701,8 +703,8 @@ cleanup: + + OM_uint32 + negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, +- gss_buffer_t input_token, gss_buffer_t output_token, +- OM_uint32 *time_rec) ++ gss_buffer_t input_token, gss_channel_bindings_t bindings, ++ gss_buffer_t output_token, OM_uint32 *time_rec) + { + OM_uint32 major, tmpmin; + gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; +@@ -754,7 +756,7 @@ negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, + * prune the list to a single mech. Continue on error if an output token + * is generated, so that we send the token to the initiator. + */ +- major = mech_accept(minor, ctx, cred, messages, nmessages, ++ major = mech_accept(minor, ctx, cred, messages, nmessages, bindings, + &mech_output_token, time_rec); + if (major != GSS_S_COMPLETE && mech_output_token.length == 0) + goto cleanup; +diff --git a/src/lib/gssapi/spnego/spnego_mech.c b/src/lib/gssapi/spnego/spnego_mech.c +index 594fc5894..4cf011143 100644 +--- a/src/lib/gssapi/spnego/spnego_mech.c ++++ b/src/lib/gssapi/spnego/spnego_mech.c +@@ -130,6 +130,7 @@ init_ctx_reselect(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32, + static OM_uint32 + init_ctx_call_init(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t, + OM_uint32, gss_name_t, OM_uint32, OM_uint32, gss_buffer_t, ++ gss_channel_bindings_t, + gss_buffer_t, OM_uint32 *, send_token_flag *); + + static OM_uint32 +@@ -144,8 +145,8 @@ acc_ctx_vfy_oid(OM_uint32 *, spnego_gss_ctx_id_t, gss_OID, + OM_uint32 *, send_token_flag *); + static OM_uint32 + acc_ctx_call_acc(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t, +- gss_buffer_t, gss_buffer_t, OM_uint32 *, OM_uint32 *, +- send_token_flag *); ++ gss_buffer_t, gss_channel_bindings_t, gss_buffer_t, ++ OM_uint32 *, OM_uint32 *, send_token_flag *); + + static gss_OID + negotiate_mech(spnego_gss_ctx_id_t, gss_OID_set, OM_uint32 *); +@@ -905,6 +906,7 @@ init_ctx_call_init(OM_uint32 *minor_status, + OM_uint32 req_flags, + OM_uint32 time_req, + gss_buffer_t mechtok_in, ++ gss_channel_bindings_t bindings, + gss_buffer_t mechtok_out, + OM_uint32 *time_rec, + send_token_flag *send_token) +@@ -921,15 +923,14 @@ init_ctx_call_init(OM_uint32 *minor_status, + if (gss_oid_equal(sc->internal_mech, &negoex_mech)) { + ret = negoex_init(minor_status, sc, mcred, target_name, + mech_req_flags, time_req, mechtok_in, +- mechtok_out, time_rec); ++ bindings, mechtok_out, time_rec); + } else { + ret = gss_init_sec_context(minor_status, mcred, + &sc->ctx_handle, target_name, + sc->internal_mech, mech_req_flags, +- time_req, GSS_C_NO_CHANNEL_BINDINGS, +- mechtok_in, &sc->actual_mech, +- mechtok_out, &sc->ctx_flags, +- time_rec); ++ time_req, bindings, mechtok_in, ++ &sc->actual_mech, mechtok_out, ++ &sc->ctx_flags, time_rec); + } + + /* Bail out if the acceptor gave us an error token but the mech didn't +@@ -981,8 +982,8 @@ init_ctx_call_init(OM_uint32 *minor_status, + gss_delete_sec_context(&tmpmin, &sc->ctx_handle, GSS_C_NO_BUFFER); + tmpret = init_ctx_call_init(&tmpmin, sc, spcred, acc_negState, + target_name, req_flags, time_req, +- mechtok_in, mechtok_out, time_rec, +- send_token); ++ mechtok_in, bindings, mechtok_out, ++ time_rec, send_token); + if (HARD_ERROR(tmpret)) + goto fail; + *minor_status = tmpmin; +@@ -1004,7 +1005,7 @@ spnego_gss_init_sec_context( + gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, +- gss_channel_bindings_t input_chan_bindings, ++ gss_channel_bindings_t bindings, + gss_buffer_t input_token, + gss_OID *actual_mech, + gss_buffer_t output_token, +@@ -1084,8 +1085,8 @@ spnego_gss_init_sec_context( + if (!spnego_ctx->mech_complete) { + ret = init_ctx_call_init(minor_status, spnego_ctx, spcred, + acc_negState, target_name, req_flags, +- time_req, mechtok_in, &mechtok_out, +- time_rec, &send_token); ++ time_req, mechtok_in, bindings, ++ &mechtok_out, time_rec, &send_token); + if (ret != GSS_S_COMPLETE) + goto cleanup; + +@@ -1542,8 +1543,9 @@ cleanup: + static OM_uint32 + acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + spnego_gss_cred_id_t spcred, gss_buffer_t mechtok_in, +- gss_buffer_t mechtok_out, OM_uint32 *time_rec, +- OM_uint32 *negState, send_token_flag *tokflag) ++ gss_channel_bindings_t bindings, gss_buffer_t mechtok_out, ++ OM_uint32 *time_rec, OM_uint32 *negState, ++ send_token_flag *tokflag) + { + OM_uint32 ret, tmpmin; + gss_OID_desc mechoid; +@@ -1568,13 +1570,12 @@ acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + if (negoex) { + ret = negoex_accept(minor_status, sc, mcred, mechtok_in, +- mechtok_out, time_rec); ++ bindings, mechtok_out, time_rec); + } else { + (void) gss_release_name(&tmpmin, &sc->internal_name); + (void) gss_release_cred(&tmpmin, &sc->deleg_cred); + ret = gss_accept_sec_context(minor_status, &sc->ctx_handle, +- mcred, mechtok_in, +- GSS_C_NO_CHANNEL_BINDINGS, ++ mcred, mechtok_in, bindings, + &sc->internal_name, + &sc->actual_mech, mechtok_out, + &sc->ctx_flags, time_rec, +@@ -1620,7 +1621,7 @@ spnego_gss_accept_sec_context( + gss_ctx_id_t *context_handle, + gss_cred_id_t verifier_cred_handle, + gss_buffer_t input_token, +- gss_channel_bindings_t input_chan_bindings, ++ gss_channel_bindings_t bindings, + gss_name_t *src_name, + gss_OID *mech_type, + gss_buffer_t output_token, +@@ -1734,8 +1735,8 @@ spnego_gss_accept_sec_context( + */ + if (negState != REQUEST_MIC && mechtok_in != GSS_C_NO_BUFFER) { + ret = acc_ctx_call_acc(minor_status, sc, spcred, mechtok_in, +- &mechtok_out, time_rec, &negState, +- &return_token); ++ bindings, &mechtok_out, time_rec, ++ &negState, &return_token); + } + + /* Step 3: process or generate the MIC, if the negotiated mech is diff --git a/SOURCES/Refactor-krb5-GSS-checksum-handling.patch b/SOURCES/Refactor-krb5-GSS-checksum-handling.patch new file mode 100644 index 0000000..8e97028 --- /dev/null +++ b/SOURCES/Refactor-krb5-GSS-checksum-handling.patch @@ -0,0 +1,480 @@ +From c4a49f5b42916fdbb34c72a11adb42ff879c50c3 Mon Sep 17 00:00:00 2001 +From: Alexander Scheel +Date: Fri, 30 Jun 2017 16:03:01 -0400 +Subject: [PATCH] Refactor krb5 GSS checksum handling + +Separate out checksum handling from kg_accept_krb5() into a new helper +process_checksum(). + +[ghudson@mit.edu: simplified checksum processing and made it use +k5-input.h instead of TREAD_ macros; moved more flag handling into +helper] + +[iboukris: adjusted helper function arguments, allowing access to the +full authenticator for subsequent changes] + +(cherry picked from commit 64d56233f9816a2a93f6e8d3030c8ed6ce397735) +[rharwood@redhat.com: problem with typo fix commit, I think] +(cherry picked from commit a34b7c50e62c19f80d39ece6a72017dac781df64) +--- + src/lib/gssapi/krb5/accept_sec_context.c | 383 +++++++++++------------ + 1 file changed, 179 insertions(+), 204 deletions(-) + +diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c +index c5bddb1e8..70dd7fc0c 100644 +--- a/src/lib/gssapi/krb5/accept_sec_context.c ++++ b/src/lib/gssapi/krb5/accept_sec_context.c +@@ -98,6 +98,7 @@ + */ + + #include "k5-int.h" ++#include "k5-input.h" + #include "gssapiP_krb5.h" + #ifdef HAVE_MEMORY_H + #include +@@ -413,6 +414,174 @@ kg_process_extension(krb5_context context, + return code; + } + ++/* The length of the MD5 channel bindings in an 0x8003 checksum */ ++#define CB_MD5_LEN 16 ++ ++/* The minimum length of an 0x8003 checksum value (4-byte channel bindings ++ * length, 16-byte channel bindings, 4-byte flags) */ ++#define MIN_8003_LEN (4 + CB_MD5_LEN + 4) ++ ++/* The flags we accept from the initiator's authenticator checksum. */ ++#define INITIATOR_FLAGS (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG | \ ++ GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ ++ GSS_C_SEQUENCE_FLAG | GSS_C_DCE_STYLE | \ ++ GSS_C_IDENTIFY_FLAG | GSS_C_EXTENDED_ERROR_FLAG) ++ ++/* ++ * The krb5 GSS mech appropriates the authenticator checksum field from RFC ++ * 4120 to store structured data instead of a checksum, indicated with checksum ++ * type 0x8003 (see RFC 4121 section 4.1.1). Some implementations instead send ++ * no checksum, or a regular checksum over empty data. ++ * ++ * Interpret the checksum. Read delegated creds into *deleg_out if it is not ++ * NULL. Set *flags_out to the allowed subset of token flags, plus ++ * GSS_C_DELEG_FLAG if a delegated credential was present. Process any ++ * extensions found using exts. On error, set *code_out to a krb5_error code ++ * for use as a minor status value. ++ */ ++static OM_uint32 ++process_checksum(OM_uint32 *minor_status, krb5_context context, ++ gss_channel_bindings_t acceptor_cb, ++ krb5_auth_context auth_context, krb5_flags ap_req_options, ++ krb5_authenticator *authenticator, krb5_gss_ctx_ext_t exts, ++ krb5_gss_cred_id_t *deleg_out, krb5_ui_4 *flags_out, ++ krb5_error_code *code_out) ++{ ++ krb5_error_code code = 0; ++ OM_uint32 status, option_id, token_flags; ++ size_t cb_len, option_len; ++ krb5_boolean valid; ++ krb5_key subkey; ++ krb5_data option, empty = empty_data(); ++ krb5_checksum cb_cksum; ++ const uint8_t *token_cb, *option_bytes; ++ struct k5input in; ++ const krb5_checksum *cksum = authenticator->checksum; ++ ++ cb_cksum.contents = NULL; ++ ++ if (cksum == NULL) { ++ /* ++ * Some SMB client implementations use handcrafted GSSAPI code that ++ * does not provide a checksum. MS-KILE documents that the Microsoft ++ * implementation considers a missing checksum acceptable; the server ++ * assumes all flags are unset in this case, and does not check channel ++ * bindings. ++ */ ++ *flags_out = 0; ++ } else if (cksum->checksum_type != CKSUMTYPE_KG_CB) { ++ /* Samba sends a regular checksum. */ ++ code = krb5_auth_con_getkey_k(context, auth_context, &subkey); ++ if (code) { ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ ++ /* Verifying the checksum ensures that this authenticator wasn't ++ * replayed from one with a checksum over actual data. */ ++ code = krb5_k_verify_checksum(context, subkey, ++ KRB5_KEYUSAGE_AP_REQ_AUTH_CKSUM, &empty, ++ cksum, &valid); ++ krb5_k_free_key(context, subkey); ++ if (code || !valid) { ++ status = GSS_S_BAD_SIG; ++ goto fail; ++ } ++ ++ /* Use ap_options from the request to guess the mutual flag. */ ++ *flags_out = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG; ++ if (ap_req_options & AP_OPTS_MUTUAL_REQUIRED) ++ *flags_out |= GSS_C_MUTUAL_FLAG; ++ } else { ++ /* The checksum must contain at least a fixed 24-byte part. */ ++ if (cksum->length < MIN_8003_LEN) { ++ status = GSS_S_BAD_BINDINGS; ++ goto fail; ++ } ++ ++ k5_input_init(&in, cksum->contents, cksum->length); ++ cb_len = k5_input_get_uint32_le(&in); ++ if (cb_len != CB_MD5_LEN) { ++ code = KG_BAD_LENGTH; ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ ++ token_cb = k5_input_get_bytes(&in, cb_len); ++ if (acceptor_cb != GSS_C_NO_CHANNEL_BINDINGS) { ++ code = kg_checksum_channel_bindings(context, acceptor_cb, ++ &cb_cksum); ++ if (code) { ++ status = GSS_S_BAD_BINDINGS; ++ goto fail; ++ } ++ assert(cb_cksum.length == cb_len); ++ if (k5_bcmp(token_cb, cb_cksum.contents, cb_len) != 0) { ++ status = GSS_S_BAD_BINDINGS; ++ goto fail; ++ } ++ } ++ ++ /* Read the token flags and accept some of them as context flags. */ ++ token_flags = k5_input_get_uint32_le(&in); ++ *flags_out = token_flags & INITIATOR_FLAGS; ++ ++ /* Read the delegated credential if present. */ ++ if (in.len >= 4 && (token_flags & GSS_C_DELEG_FLAG)) { ++ option_id = k5_input_get_uint16_le(&in); ++ option_len = k5_input_get_uint16_le(&in); ++ option_bytes = k5_input_get_bytes(&in, option_len); ++ option = make_data((uint8_t *)option_bytes, option_len); ++ if (in.status) { ++ code = KG_BAD_LENGTH; ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ if (option_id != KRB5_GSS_FOR_CREDS_OPTION) { ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ ++ /* Store the delegated credential. */ ++ code = rd_and_store_for_creds(context, auth_context, &option, ++ deleg_out); ++ if (code) { ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ *flags_out |= GSS_C_DELEG_FLAG; ++ } ++ ++ /* Process any extensions at the end of the checksum. Extensions use ++ * 4-byte big-endian tag and length instead of 2-byte little-endian. */ ++ while (in.len > 0) { ++ option_id = k5_input_get_uint32_be(&in); ++ option_len = k5_input_get_uint32_be(&in); ++ option_bytes = k5_input_get_bytes(&in, option_len); ++ option = make_data((uint8_t *)option_bytes, option_len); ++ if (in.status) { ++ code = KG_BAD_LENGTH; ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ ++ code = kg_process_extension(context, auth_context, option_id, ++ &option, exts); ++ if (code) { ++ status = GSS_S_FAILURE; ++ goto fail; ++ } ++ } ++ } ++ ++ status = GSS_S_COMPLETE; ++ ++fail: ++ free(cb_cksum.contents); ++ *code_out = code; ++ return status; ++} ++ + static OM_uint32 + kg_accept_krb5(minor_status, context_handle, + verifier_cred_handle, input_token, +@@ -433,17 +602,13 @@ kg_accept_krb5(minor_status, context_handle, + krb5_gss_ctx_ext_t exts; + { + krb5_context context; +- unsigned char *ptr, *ptr2; ++ unsigned char *ptr; + char *sptr; +- OM_uint32 tmp; +- size_t md5len; + krb5_gss_cred_id_t cred = 0; + krb5_data ap_rep, ap_req; +- unsigned int i; + krb5_error_code code; + krb5_address addr, *paddr; + krb5_authenticator *authdat = 0; +- krb5_checksum reqcksum; + krb5_gss_name_t name = NULL; + krb5_ui_4 gss_flags = 0; + krb5_gss_ctx_id_rec *ctx = NULL; +@@ -451,8 +616,6 @@ kg_accept_krb5(minor_status, context_handle, + gss_buffer_desc token; + krb5_auth_context auth_context = NULL; + krb5_ticket * ticket = NULL; +- int option_id; +- krb5_data option; + const gss_OID_desc *mech_used = NULL; + OM_uint32 major_status = GSS_S_FAILURE; + OM_uint32 tmp_minor_status; +@@ -463,7 +626,6 @@ kg_accept_krb5(minor_status, context_handle, + krb5int_access kaccess; + int cred_rcache = 0; + int no_encap = 0; +- int token_deleg_flag = 0; + krb5_flags ap_req_options = 0; + krb5_enctype negotiated_etype; + krb5_authdata_context ad_context = NULL; +@@ -489,7 +651,6 @@ kg_accept_krb5(minor_status, context_handle, + output_token->length = 0; + output_token->value = NULL; + token.value = 0; +- reqcksum.contents = 0; + ap_req.data = 0; + ap_rep.data = 0; + +@@ -654,195 +815,16 @@ kg_accept_krb5(minor_status, context_handle, + + krb5_auth_con_getauthenticator(context, auth_context, &authdat); + +- if (authdat->checksum == NULL) { +- /* +- * Some SMB client implementations use handcrafted GSSAPI code that +- * does not provide a checksum. MS-KILE documents that the Microsoft +- * implementation considers a missing checksum acceptable; the server +- * assumes all flags are unset in this case, and does not check channel +- * bindings. +- */ +- gss_flags = 0; +- } else if (authdat->checksum->checksum_type != CKSUMTYPE_KG_CB) { +- /* Samba does not send 0x8003 GSS-API checksums */ +- krb5_boolean valid; +- krb5_key subkey; +- krb5_data zero; ++ major_status = process_checksum(minor_status, context, input_chan_bindings, ++ auth_context, ap_req_options, ++ authdat, exts, ++ delegated_cred_handle ? &deleg_cred : NULL, ++ &gss_flags, &code); + +- code = krb5_auth_con_getkey_k(context, auth_context, &subkey); +- if (code) { +- major_status = GSS_S_FAILURE; +- goto fail; +- } ++ if (major_status != GSS_S_COMPLETE) ++ goto fail; + +- zero.length = 0; +- zero.data = ""; +- +- code = krb5_k_verify_checksum(context, +- subkey, +- KRB5_KEYUSAGE_AP_REQ_AUTH_CKSUM, +- &zero, +- authdat->checksum, +- &valid); +- krb5_k_free_key(context, subkey); +- if (code || !valid) { +- major_status = GSS_S_BAD_SIG; +- goto fail; +- } +- +- /* Use ap_options from the request to guess the mutual flag. */ +- gss_flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG; +- if (ap_req_options & AP_OPTS_MUTUAL_REQUIRED) +- gss_flags |= GSS_C_MUTUAL_FLAG; +- } else { +- /* gss krb5 v1 */ +- +- /* stash this now, for later. */ +- code = krb5_c_checksum_length(context, CKSUMTYPE_RSA_MD5, &md5len); +- if (code) { +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- +- /* verify that the checksum is correct */ +- +- /* +- The checksum may be either exactly 24 bytes, in which case +- no options are specified, or greater than 24 bytes, in which case +- one or more options are specified. Currently, the only valid +- option is KRB5_GSS_FOR_CREDS_OPTION ( = 1 ). +- */ +- +- if ((authdat->checksum->checksum_type != CKSUMTYPE_KG_CB) || +- (authdat->checksum->length < 24)) { +- code = 0; +- major_status = GSS_S_BAD_BINDINGS; +- goto fail; +- } +- +- ptr = (unsigned char *) authdat->checksum->contents; +- +- TREAD_INT(ptr, tmp, 0); +- +- if (tmp != md5len) { +- code = KG_BAD_LENGTH; +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- +- /* +- The following section of code attempts to implement the +- optional channel binding facility as described in RFC2743. +- +- Since this facility is optional channel binding may or may +- not have been provided by either the client or the server. +- +- If the server has specified input_chan_bindings equal to +- GSS_C_NO_CHANNEL_BINDINGS then we skip the check. If +- the server does provide channel bindings then we compute +- a checksum and compare against those provided by the +- client. */ +- +- if ((code = kg_checksum_channel_bindings(context, +- input_chan_bindings, +- &reqcksum))) { +- major_status = GSS_S_BAD_BINDINGS; +- goto fail; +- } +- +- /* Always read the clients bindings - eventhough we might ignore them */ +- TREAD_STR(ptr, ptr2, reqcksum.length); +- +- if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS ) { +- if (memcmp(ptr2, reqcksum.contents, reqcksum.length) != 0) { +- xfree(reqcksum.contents); +- reqcksum.contents = 0; +- code = 0; +- major_status = GSS_S_BAD_BINDINGS; +- goto fail; +- } +- +- } +- +- xfree(reqcksum.contents); +- reqcksum.contents = 0; +- +- /* Read the token flags. Remember if GSS_C_DELEG_FLAG was set, but +- * mask it out until we actually read a delegated credential. */ +- TREAD_INT(ptr, gss_flags, 0); +- token_deleg_flag = (gss_flags & GSS_C_DELEG_FLAG); +- gss_flags &= ~GSS_C_DELEG_FLAG; +- +- /* if the checksum length > 24, there are options to process */ +- +- i = authdat->checksum->length - 24; +- if (i && token_deleg_flag) { +- if (i >= 4) { +- TREAD_INT16(ptr, option_id, 0); +- TREAD_INT16(ptr, option.length, 0); +- i -= 4; +- +- if (i < option.length) { +- code = KG_BAD_LENGTH; +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- +- /* have to use ptr2, since option.data is wrong type and +- macro uses ptr as both lvalue and rvalue */ +- +- TREAD_STR(ptr, ptr2, option.length); +- option.data = (char *) ptr2; +- +- i -= option.length; +- +- if (option_id != KRB5_GSS_FOR_CREDS_OPTION) { +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- +- /* store the delegated credential */ +- +- code = rd_and_store_for_creds(context, auth_context, &option, +- (delegated_cred_handle) ? +- &deleg_cred : NULL); +- if (code) { +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- +- gss_flags |= GSS_C_DELEG_FLAG; +- } /* if i >= 4 */ +- /* ignore any additional trailing data, for now */ +- } +- while (i > 0) { +- /* Process Type-Length-Data options */ +- if (i < 8) { +- code = KG_BAD_LENGTH; +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- TREAD_INT(ptr, option_id, 1); +- TREAD_INT(ptr, option.length, 1); +- i -= 8; +- if (i < option.length) { +- code = KG_BAD_LENGTH; +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- TREAD_STR(ptr, ptr2, option.length); +- option.data = (char *)ptr2; +- +- i -= option.length; +- +- code = kg_process_extension(context, auth_context, +- option_id, &option, exts); +- if (code != 0) { +- major_status = GSS_S_FAILURE; +- goto fail; +- } +- } +- } ++ major_status = GSS_S_FAILURE; + + if (exts->iakerb.conv && !exts->iakerb.verified) { + major_status = GSS_S_BAD_SIG; +@@ -869,12 +851,7 @@ kg_accept_krb5(minor_status, context_handle, + ctx->mech_used = (gss_OID) mech_used; + ctx->auth_context = auth_context; + ctx->initiate = 0; +- ctx->gss_flags = (GSS_C_TRANS_FLAG | +- ((gss_flags) & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG | +- GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | +- GSS_C_SEQUENCE_FLAG | GSS_C_DELEG_FLAG | +- GSS_C_DCE_STYLE | GSS_C_IDENTIFY_FLAG | +- GSS_C_EXTENDED_ERROR_FLAG))); ++ ctx->gss_flags = gss_flags | GSS_C_TRANS_FLAG; + ctx->seed_init = 0; + ctx->cred_rcache = cred_rcache; + +@@ -1161,8 +1138,6 @@ fail: + + krb5_auth_con_free(context, auth_context); + } +- if (reqcksum.contents) +- xfree(reqcksum.contents); + if (ap_rep.data) + krb5_free_data_contents(context, &ap_rep); + if (major_status == GSS_S_COMPLETE || diff --git a/SPECS/krb5.spec b/SPECS/krb5.spec index f87f2de..3f0049c 100644 --- a/SPECS/krb5.spec +++ b/SPECS/krb5.spec @@ -18,7 +18,7 @@ Summary: The Kerberos network authentication system Name: krb5 Version: 1.18.2 # for prerelease, should be e.g., 0.% {prerelease}.1% { ?dist } (without spaces) -Release: 2%{?dist} +Release: 3%{?dist} # lookaside-cached sources; two downloads and a build artifact Source0: https://web.mit.edu/kerberos/dist/krb5/1.17/krb5-%{version}%{prerelease}.tar.gz @@ -63,6 +63,14 @@ Patch117: Do-expiration-warnings-for-all-init_creds-APIs.patch Patch118: Pass-gss_localname-through-SPNEGO.patch Patch119: Omit-KDC-indicator-check-for-S4U2Self-requests.patch Patch120: Fix-typo-in-in-in-the-ksu-man-page.patch +Patch121: Omit-PA_FOR_USER-if-we-can-t-compute-its-checksum.patch +Patch122: Improve-negoex_parse_token-code-hygiene.patch +Patch123: Refactor-krb5-GSS-checksum-handling.patch +Patch124: Implement-GSS_C_CHANNEL_BOUND_FLAG.patch +Patch125: Implement-KERB_AP_OPTIONS_CBT-server-side.patch +Patch126: Add-client_aware_channel_bindings-option.patch +Patch127: Pass-channel-bindings-through-SPNEGO.patch +Patch128: Add-channel-bindings-tests.patch License: MIT URL: http://web.mit.edu/kerberos/www/ @@ -673,6 +681,11 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog +* Mon Jun 15 2020 Robbie Harwood - 1.18.2-3 +- Match Heimdal behavior for channel bindings +- Code hygiene + test stability fix included +- Resolves: #1840518 + * Wed May 27 2020 Robbie Harwood - 1.18.2-2 - Drop DES3 from sample kdc.conf - Resolves: #1802334