]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Add GSS flag to include KERB_AP_OPTIONS_CBT 1329/head
authorStefan Metzmacher <metze@samba.org>
Fri, 1 Mar 2024 13:23:47 +0000 (14:23 +0100)
committerGreg Hudson <ghudson@mit.edu>
Mon, 6 May 2024 21:40:31 +0000 (17:40 -0400)
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
doc/appdev/refs/macros/index.rst
src/include/krb5/krb5.hin
src/lib/gssapi/krb5/init_sec_context.c
src/lib/krb5/krb/mk_req_ext.c
src/tests/gssapi/t_bindings.c
src/tests/gssapi/t_bindings.py

index 339fd6c7c155cf2a98169f384beb00ed1e1fad4a..b58f4122bca5ad1bfde7cb6d07d494380bd6d148 100644 (file)
@@ -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
 ---------------------
 
index 45fe160d7fb1e6980260eff31da2db2032f266b3..ee9fe2c61415908a982fb8820c99405916c8f9a4 100644 (file)
@@ -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
index 4e09ed345dde3a07684a18174b2ef4cd35e23b8b..74963832db0a26652ec1e6da694dd4198697361c 100644 (file)
@@ -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
index 0397fe1dfd03709857ee5fb3f34f876123fe7dbe..3cc9d4ceb0aaee7ba19d168584a81fe25a279733 100644 (file)
@@ -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;
index 08504860ca4cc07d25679c8db586322e4345b843..3eae2e751c33456c62a5fb7bf33516f2e896b90d 100644 (file)
@@ -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. */
index e8906715b4cddeffcdad3c5832ce941f44bbcfa9..3d6a70acfe51b64a1ebe7fb1085eb99d3f13ac5f 100644 (file)
  * 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));
index f377977b65787bf93fe5fd7ede384e7d72f6ff60..1bb496629b82cff1af68367f7b910ef474e7e9aa 100644 (file)
@@ -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')