From 64f643a7f798c5528182dc068f15dca7b3f2d8a1 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 3a8b9cf47..315253e37 100644 --- a/doc/admin/conf_files/krb5_conf.rst +++ b/doc/admin/conf_files/krb5_conf.rst @@ -389,6 +389,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; }