From 6b74b6c18feab1f3d72d00ae412a93c6bfa4a00a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 1 Mar 2024 14:23:47 +0100 Subject: Add GSS flag to include KERB_AP_OPTIONS_CBT The Microsoft KERB_AP_OPTIONS_CBT extension (defined in [MS-KILE] 3.2.5.8) allows the client to request strict enforcement of GSS channel bindings. Client support for this extension was added in commit 225e6ef7f021cd1a8ef2a054af0ca58b7288fd81 (ticket 8900) but it requires a configuration variable to be set. The choice to include the extension should be made by the client application code, as it is a promise to include channel bindings when operating within TLS. In libkrb5, add an option AP_OPTS_CBT_FLAG to make krb5_mk_req[_extended]() include KERB_AP_OPTIONS_CBT. In the GSS initiator code, set this flag when the GSS_C_CHANNEL_BOUND flag is included in the request options. GSS_C_CHANNEL_BOUND was introduced in commit 429a31146083fac21958631c2af572b08ec91022 (ticket 8899) as an acceptor output flag. [ghudson@mit.edu: rewrote commit message; adjusted some names; simplified GSS initiator bookkeeping; added documentation] ticket: 9122 (new) --- doc/appdev/gssapi.rst | 36 ++++++++++++++++++++++++++++++++++ doc/appdev/refs/macros/index.rst | 1 + src/include/krb5/krb5.hin | 2 +- src/lib/gssapi/krb5/init_sec_context.c | 11 ++++++++--- src/lib/krb5/krb/mk_req_ext.c | 21 ++++++++++++-------- src/tests/gssapi/t_bindings.c | 31 ++++++++++++++++++++--------- src/tests/gssapi/t_bindings.py | 18 +++++++++++++++++ 7 files changed, 99 insertions(+), 21 deletions(-) diff --git a/doc/appdev/gssapi.rst b/doc/appdev/gssapi.rst index 339fd6c..b58f412 100644 --- a/doc/appdev/gssapi.rst +++ b/doc/appdev/gssapi.rst @@ -424,6 +424,42 @@ set. If the library does not support the query, gss_inquire_cred_by_oid will return **GSS_S_UNAVAILABLE**. +Channel binding behavior and GSS_C_CHANNEL_BOUND_FLAG +----------------------------------------------------- + +GSSAPI channel bindings can be used to limit the scope of a context +establishment token to a particular protected channel or endpoint, +such as a TLS channel or server certificate. Channel bindings can be +supplied via the *input_chan_bindings* parameter to either +gss_init_sec_context() or gss_accept_sec_context(). + +If both the initiator and acceptor of a GSSAPI exchange supply +matching channel bindings, **GSS_C_CHANNEL_BOUND_FLAG** will be +included in the gss_accept_sec_context() *ret_flags* result. If +either the initiator or acceptor (or both) do not supply channel +bindings, the exchange will succeed, but **GSS_C_CHANNEL_BOUND_FLAG** +will not be included in the return flags. If the acceptor and +initiator both inlude channel bindings but they do not match, the +exchange will fail. + +If **GSS_C_CHANNEL_BOUND_FLAG** is included in the *req_flags* +parameter of gss_init_sec_context(), the initiator will add the +Microsoft KERB_AP_OPTIONS_CBT extension to the Kerberos authenticator. +This extension requests that the acceptor strictly enforce channel +bindings, causing the exchange to fail if the acceptor supplies +channel bindings and the initiator does not. The KERB_AP_OPTIONS_CBT +extension will also be included if the +**client_aware_channel_bindings** variable is set to ``true`` in +:ref:`libdefaults`. + +Prior to release 1.19, **GSS_C_CHANNEL_BOUND_FLAG** is not +implemented, and the exchange will fail if the acceptor supply channel +bindings and the initiator does not (but not vice versa). Between +releases 1.19 and 1.21, **GSS_C_CHANNEL_BOUND_FLAG** is not recognized +as an initiator flag, so **client_aware_channel_bindings** is the only +way to cause KERB_AP_OPTIONS_CBT to be included. + + AEAD message wrapping --------------------- diff --git a/doc/appdev/refs/macros/index.rst b/doc/appdev/refs/macros/index.rst index 45fe160..ee9fe2c 100644 --- a/doc/appdev/refs/macros/index.rst +++ b/doc/appdev/refs/macros/index.rst @@ -22,6 +22,7 @@ Public AD_TYPE_REGISTERED.rst AD_TYPE_RESERVED.rst AP_OPTS_ETYPE_NEGOTIATION.rst + AP_OPTS_CBT_FLAG.rst AP_OPTS_MUTUAL_REQUIRED.rst AP_OPTS_RESERVED.rst AP_OPTS_USE_SESSION_KEY.rst diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 4e09ed3..7496383 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -1658,6 +1658,7 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype, #define AP_OPTS_USE_SESSION_KEY 0x40000000 /**< Use session key */ #define AP_OPTS_MUTUAL_REQUIRED 0x20000000 /**< Perform a mutual authentication exchange */ +#define AP_OPTS_CBT_FLAG 0x00000004 /* include KERB_AP_OPTIONS_CBT */ #define AP_OPTS_ETYPE_NEGOTIATION 0x00000002 #define AP_OPTS_USE_SUBKEY 0x00000001 /**< Generate a subsession key from the current session key @@ -1689,7 +1690,6 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype, /* #define AP_OPTS_RESERVED 0x00000020 */ /* #define AP_OPTS_RESERVED 0x00000010 */ /* #define AP_OPTS_RESERVED 0x00000008 */ -/* #define AP_OPTS_RESERVED 0x00000004 */ #define AP_OPTS_WIRE_MASK 0xfffffff0 diff --git a/src/lib/gssapi/krb5/init_sec_context.c b/src/lib/gssapi/krb5/init_sec_context.c index 0397fe1..3cc9d4c 100644 --- a/src/lib/gssapi/krb5/init_sec_context.c +++ b/src/lib/gssapi/krb5/init_sec_context.c @@ -365,7 +365,7 @@ make_ap_req_v1(krb5_context context, krb5_gss_ctx_id_rec *ctx, krb5_gss_cred_id_t cred, krb5_creds *k_cred, krb5_authdata_context ad_context, gss_channel_bindings_t chan_bindings, gss_OID mech_type, - gss_buffer_t token, krb5_gss_ctx_ext_t exts) + int cbt_flag, gss_buffer_t token, krb5_gss_ctx_ext_t exts) { krb5_flags mk_req_flags = 0; krb5_error_code code; @@ -400,6 +400,8 @@ make_ap_req_v1(krb5_context context, krb5_gss_ctx_id_rec *ctx, if (ctx->gss_flags & GSS_C_MUTUAL_FLAG) mk_req_flags |= AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_ETYPE_NEGOTIATION; + if (cbt_flag) + mk_req_flags |= AP_OPTS_CBT_FLAG; krb5_auth_con_set_authdata_context(context, ctx->auth_context, ad_context); code = krb5_mk_req_extended(context, &ctx->auth_context, mk_req_flags, @@ -481,6 +483,7 @@ kg_new_connection( krb5_timestamp now; gss_buffer_desc token; krb5_keyblock *keyblock; + int cbt_flag = (req_flags & GSS_C_CHANNEL_BOUND_FLAG) != 0; k5_mutex_assert_locked(&cred->lock); major_status = GSS_S_FAILURE; @@ -538,6 +541,8 @@ kg_new_connection( req_flags |= GSS_C_DELEG_POLICY_FLAG; } + /* Don't include GSS_C_CHANNEL_BOUND_FLAG here; we don't want to put it on + * the wire, and we only need to know about it for the first token. */ ctx->gss_flags = req_flags & (GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_DELEG_FLAG | @@ -595,8 +600,8 @@ kg_new_connection( krb5_int32 seq_temp; if ((code = make_ap_req_v1(context, ctx, cred, k_cred, ctx->here->ad_context, - input_chan_bindings, - mech_type, &token, exts))) { + input_chan_bindings, mech_type, cbt_flag, + &token, exts))) { if ((code == KRB5_FCC_NOFILE) || (code == KRB5_CC_NOTFOUND) || (code == KG_EMPTY_CCACHE)) major_status = GSS_S_NO_CRED; diff --git a/src/lib/krb5/krb/mk_req_ext.c b/src/lib/krb5/krb/mk_req_ext.c index 0850486..3eae2e7 100644 --- a/src/lib/krb5/krb/mk_req_ext.c +++ b/src/lib/krb5/krb/mk_req_ext.c @@ -78,7 +78,7 @@ generate_authenticator(krb5_context, krb5_checksum *, krb5_key, krb5_ui_4, krb5_authdata **, krb5_authdata_context ad_context, - krb5_enctype *desired_etypes, + krb5_enctype *desired_etypes, krb5_boolean cbt_flag, krb5_enctype tkt_enctype); krb5_error_code KRB5_CALLCONV @@ -95,6 +95,7 @@ krb5_mk_req_extended(krb5_context context, krb5_auth_context *auth_context, krb5_ap_req request; krb5_data *scratch = 0; krb5_data *toutbuf; + krb5_boolean cbt_flag = (ap_req_options & AP_OPTS_CBT_FLAG) != 0; request.ap_options = ap_req_options & AP_OPTS_WIRE_MASK; request.authenticator.ciphertext.data = NULL; @@ -201,7 +202,7 @@ krb5_mk_req_extended(krb5_context context, krb5_auth_context *auth_context, (*auth_context)->local_seq_number, in_creds->authdata, (*auth_context)->ad_context, - desired_etypes, + desired_etypes, cbt_flag, in_creds->keyblock.enctype))) goto cleanup_cksum; @@ -258,7 +259,7 @@ generate_authenticator(krb5_context context, krb5_authenticator *authent, krb5_key key, krb5_ui_4 seq_number, krb5_authdata **authorization, krb5_authdata_context ad_context, - krb5_enctype *desired_etypes, + krb5_enctype *desired_etypes, krb5_boolean cbt_flag, krb5_enctype tkt_enctype) { krb5_error_code retval; @@ -297,11 +298,15 @@ generate_authenticator(krb5_context context, krb5_authenticator *authent, krb5_free_authdata(context, ext_authdata); } - retval = profile_get_boolean(context->profile, KRB5_CONF_LIBDEFAULTS, - KRB5_CONF_CLIENT_AWARE_GSS_BINDINGS, NULL, - FALSE, &client_aware_cb); - if (retval) - return retval; + if (cbt_flag) { + client_aware_cb = TRUE; + } else { + 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. */ diff --git a/src/tests/gssapi/t_bindings.c b/src/tests/gssapi/t_bindings.c index e890671..3d6a70a 100644 --- a/src/tests/gssapi/t_bindings.c +++ b/src/tests/gssapi/t_bindings.c @@ -43,34 +43,46 @@ * 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 + * Usage: ./t_bindings [-s] [-b] targetname icb acb * - * An icb or abc value of "-" will not specify channel bindings. + * An icb or abc value of "-" will not specify channel bindings. The -s flag + * uses the SPNEGO mechanism instead of the krb5 mecanism. The -b flag + * includes GSS_C_CHANNEL_BOUND in req_flags, which requests strict enforcement + * of channel bindings by the acceptor. */ int main(int argc, char *argv[]) { + OM_uint32 client_flags = 0; 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; + gss_OID_desc *mech = GSS_C_NO_OID; argv++; argc--; + if (*argv != NULL && strcmp(*argv, "-s") == 0) { mech = &mech_spnego; argv++; argc--; - } else { - mech = &mech_krb5; } + if (*argv != NULL && strcmp(*argv, "-b") == 0) { + client_flags |= GSS_C_CHANNEL_BOUND_FLAG; + argv++; + argc--; + } + + if (mech == GSS_C_NO_OID) + mech = &mech_krb5; + if (argc != 3) { - fprintf(stderr, "Usage: t_bindings [-s] targetname icb acb\n"); + fprintf(stderr, "Usage: t_bindings [-s] [-b] targetname icb acb\n"); return 1; } @@ -89,15 +101,16 @@ main(int argc, char *argv[]) } establish_contexts_ex(mech, GSS_C_NO_CREDENTIAL, GSS_C_NO_CREDENTIAL, - target_name, 0, &ictx, &actx, icb, acb, &flags1, - NULL, NULL, NULL); + target_name, client_flags, &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); + client_flags |= GSS_C_DCE_STYLE; establish_contexts_ex(mech, GSS_C_NO_CREDENTIAL, GSS_C_NO_CREDENTIAL, - target_name, GSS_C_DCE_STYLE, &ictx, &actx, icb, acb, + target_name, client_flags, &ictx, &actx, icb, acb, &flags2, NULL, NULL, NULL); assert((flags1 & GSS_C_CHANNEL_BOUND_FLAG) == (flags2 & GSS_C_CHANNEL_BOUND_FLAG)); diff --git a/src/tests/gssapi/t_bindings.py b/src/tests/gssapi/t_bindings.py index f377977..1bb4966 100644 --- a/src/tests/gssapi/t_bindings.py +++ b/src/tests/gssapi/t_bindings.py @@ -40,4 +40,22 @@ realm.run(['./t_bindings', '-s', server, '-', 'a'], env=e, realm.run(['./t_bindings', '-s', server, 'a', 'x'], env=e, expected_code=1, expected_msg='Incorrect channel bindings') +mark('krb5 GSS_C_CHANNEL_BOUND_FLAG initiator input flag') +realm.run(['./t_bindings', '-b', server, '-', '-'], expected_msg='no') +realm.run(['./t_bindings', '-b', server, 'a', '-'], expected_msg='no') +realm.run(['./t_bindings', '-b', server, 'a', 'a'], expected_msg='yes') +realm.run(['./t_bindings', '-b', server, '-', 'a'], + expected_code=1, expected_msg='Incorrect channel bindings') +realm.run(['./t_bindings', '-b', server, 'a', 'x'], + expected_code=1, expected_msg='Incorrect channel bindings') + +mark('SPNEGO GSS_C_CHANNEL_BOUND_FLAG initiator input flag') +realm.run(['./t_bindings', '-s', '-b', server, '-', '-'], expected_msg='no') +realm.run(['./t_bindings', '-s', '-b', server, 'a', '-'], expected_msg='no') +realm.run(['./t_bindings', '-s', '-b', server, 'a', 'a'], expected_msg='yes') +realm.run(['./t_bindings', '-s', '-b', server, '-', 'a'], + expected_code=1, expected_msg='Incorrect channel bindings') +realm.run(['./t_bindings', '-s', '-b', server, 'a', 'x'], + expected_code=1, expected_msg='Incorrect channel bindings') + success('channel bindings tests') -- cgit v1.1