From: Nicolas Williams Date: Mon, 4 Jun 2012 22:17:31 +0000 (-0500) Subject: Add control over session key enctype negotiation X-Git-Tag: krb5-1.11-alpha1~513 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e9bf73d2b8da55aedd25061faefe6a22d9613d3;p=thirdparty%2Fkrb5.git Add control over session key enctype negotiation Adds a principal string attribute named "session_enctypes" which can specify what enctypes the principal supports for session keys. (For what it's worth, this actually allows one to list des-cbc-md5 as a supported session key enctype, though obviously this hardly matters now.) Add a [realms] section parameter for specifying whether to assume that principals (which lack the session_enctypes attribute) support des-cbc-crc for session keys. This allows those who still need to use allow_weak_crypto=true, for whatever reason, to start reducing the number of tickets issued with des-cbc-crc session keys to clients which still give des-cbc-crc preference in their default_tgs_enctypes list. [ghudson@mit.edu: Miscellaneous edits, cleanups, and fixes; refactored test script; documented session_enctypes attribute] --- diff --git a/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst b/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst index ec90cff398..c8a08eba06 100644 --- a/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst +++ b/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst @@ -584,8 +584,7 @@ get_strings **get_strings** *principal* -Displays string attributes on *principal*. String attributes are used -to supply per-principal configuration to some KDC plugin modules. +Displays string attributes on *principal*. This command requires the **inquire** privilege. @@ -600,7 +599,15 @@ set_string **set_string** *principal* *key* *value* -Sets a string attribute on *principal*. +Sets a string attribute on *principal*. String attributes are used to +supply per-principal configuration to the KDC and some KDC plugin +modules. The following string attributes are recognized by the KDC: + +**session_enctypes** + Specifies the encryption types supported for session keys when the + principal is authenticated to as a server. See + :ref:`Encryption_and_salt_types` in :ref:`kdc.conf(5)` for a list + of the accepted values. This command requires the **modify** privilege. diff --git a/doc/rst_source/krb_admins/conf_files/kdc_conf.rst b/doc/rst_source/krb_admins/conf_files/kdc_conf.rst index a84c702f06..66f51dc75f 100644 --- a/doc/rst_source/krb_admins/conf_files/kdc_conf.rst +++ b/doc/rst_source/krb_admins/conf_files/kdc_conf.rst @@ -269,6 +269,13 @@ subsection: listed in **host_based_services**. ``no_host_referral = *`` will disable referral processing altogether. +**des_crc_session_supported** + (Boolean value). If set to true, the KDC will assume that service + principals support des-cbc-crc for session key enctype negotiation + purposes. If **allow_weak_crypto** in :ref:`libdefaults` is + false, or if des-cbc-crc is not a permitted enctype, then this + variable has no effect. Defaults to true. + **reject_bad_transit** (Boolean value.) If set to true, the KDC will check the list of transited realms for cross-realm tickets against the transit path diff --git a/src/include/adm.h b/src/include/adm.h index 9c75b156a4..9b05f79d78 100644 --- a/src/include/adm.h +++ b/src/include/adm.h @@ -218,6 +218,8 @@ typedef struct __krb5_realm_params { unsigned int realm_flags_valid:1; unsigned int realm_reject_bad_transit_valid:1; unsigned int realm_restrict_anon_valid:1; + unsigned int realm_assume_des_crc_sess:1; + unsigned int realm_assume_des_crc_sess_valid:1; krb5_int32 realm_num_keysalts; } krb5_realm_params; #endif /* KRB5_ADM_H__ */ diff --git a/src/include/k5-int.h b/src/include/k5-int.h index ca18baf13c..ee15eacd8b 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -266,6 +266,7 @@ typedef INT64_TYPE krb5_int64; #define KRB5_CONF_REJECT_BAD_TRANSIT "reject_bad_transit" #define KRB5_CONF_RENEW_LIFETIME "renew_lifetime" #define KRB5_CONF_RESTRICT_ANONYMOUS_TO_TGT "restrict_anonymous_to_tgt" +#define KRB5_CONF_ASSUME_DES_CRC_SESSION "des_crc_session_supported" #define KRB5_CONF_SAFE_CHECKSUM_TYPE "safe_checksum_type" #define KRB5_CONF_SUPPORTED_ENCTYPES "supported_enctypes" #define KRB5_CONF_TICKET_LIFETIME "ticket_lifetime" diff --git a/src/include/kdb.h b/src/include/kdb.h index 67c403155c..291a05bb6c 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -131,6 +131,9 @@ #define KRB5_KDB_FLAGS_S4U ( KRB5_KDB_FLAG_PROTOCOL_TRANSITION | \ KRB5_KDB_FLAG_CONSTRAINED_DELEGATION ) +/* String attribute names recognized by krb5 */ +#define KRB5_KDB_SK_SESSION_ENCTYPES "session_enctypes" + #if !defined(_WIN32) /* diff --git a/src/kdc/extern.h b/src/kdc/extern.h index 3866c6c1fd..c601e5702b 100644 --- a/src/kdc/extern.h +++ b/src/kdc/extern.h @@ -70,6 +70,7 @@ typedef struct __kdc_realm_data { krb5_deltat realm_maxrlife; /* Maximum renewable life for realm */ krb5_boolean realm_reject_bad_transit; /* Accept unverifiable transited_realm ? */ krb5_boolean realm_restrict_anon; /* Anon to local TGT only */ + krb5_boolean realm_assume_des_crc_sess; /* Assume princs support des-cbc-crc for session keys */ } kdc_realm_t; extern kdc_realm_t **kdc_realmlist; @@ -91,6 +92,7 @@ kdc_realm_t *find_realm_data (char *, krb5_ui_4); #define tgs_server kdc_active_realm->realm_tgsprinc #define reject_bad_transit kdc_active_realm->realm_reject_bad_transit #define restrict_anon kdc_active_realm->realm_restrict_anon +#define assume_des_crc_sess kdc_active_realm->realm_assume_des_crc_sess /* various externs for KDC */ extern krb5_data empty_string; /* an empty string */ diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index 2f4af733d1..3f3b406807 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -1516,60 +1516,50 @@ validate_tgs_request(register krb5_kdc_req *request, krb5_db_entry server, return 0; } -/* - * This function returns 1 if the dbentry has a key for a specified - * keytype, and 0 if not. - */ -int -dbentry_has_key_for_enctype(krb5_context context, krb5_db_entry *client, - krb5_enctype enctype) +/* Return true if we believe server can support enctype as a session key. */ +krb5_boolean +dbentry_supports_enctype(krb5_context context, krb5_db_entry *server, + krb5_enctype enctype) { krb5_error_code retval; krb5_key_data *datap; + char *etypes_str = NULL; + krb5_enctype default_enctypes[1] = { 0 }; + krb5_enctype *etypes; + size_t i; + + /* Look up the supported session key enctypes list in the KDB. */ + retval = krb5_dbe_get_string(context, server, KRB5_KDB_SK_SESSION_ENCTYPES, + &etypes_str); + if (retval == 0 && etypes_str != NULL && *etypes_str != '\0') { + /* Pass a fake profile key for tracing of unrecognized tokens. */ + retval = krb5int_parse_enctype_list(context, "KDB-session_etypes", + etypes_str, default_enctypes, + &etypes); + free(etypes_str); + if (retval == 0 && etypes != NULL && etypes[0]) { + for (i = 0; etypes[i]; i++) + if (enctype == etypes[i]) + return TRUE; + return FALSE; + } + /* Fall through on error or empty list */ + } else { + free(etypes_str); + } - retval = krb5_dbe_find_enctype(context, client, enctype, - -1, 0, &datap); - if (retval) - return 0; - else - return 1; -} + /* If configured to, assume every server without a session_enctypes + * attribute supports DES_CBC_CRC. */ + if (assume_des_crc_sess && enctype == ENCTYPE_DES_CBC_CRC) + return TRUE; -/* - * This function returns 1 if the entity referenced by this - * structure can support the a particular encryption system, and 0 if - * not. - * - * XXX eventually this information should be looked up in the - * database. Since it isn't, we use some hueristics and attribute - * options bits for now. - */ -int -dbentry_supports_enctype(krb5_context context, krb5_db_entry *client, - krb5_enctype enctype) -{ - /* - * If it's DES_CBC_MD5, there's a bit in the attribute mask which - * checks to see if we support it. For now, treat it as always - * clear. - * - * In theory everything's supposed to support DES_CBC_MD5, but - * that's not the reality.... - */ + /* Due to an ancient interop problem, assume nothing supports des-cbc-md5 + * unless there's a session_enctypes explicitly saying that it does. */ if (enctype == ENCTYPE_DES_CBC_MD5) - return 0; + return FALSE; - /* - * XXX we assume everything can understand DES_CBC_CRC - */ - if (enctype == ENCTYPE_DES_CBC_CRC) - return 1; - - /* - * If we have a key for the encryption system, we assume it's - * supported. - */ - return dbentry_has_key_for_enctype(context, client, enctype); + /* Assume the server supports any enctype it has a long-term key for. */ + return !krb5_dbe_find_enctype(context, server, enctype, -1, 0, &datap); } /* diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h index 55aafaeb76..0cd7989fcc 100644 --- a/src/kdc/kdc_util.h +++ b/src/kdc/kdc_util.h @@ -89,16 +89,6 @@ validate_tgs_request (krb5_kdc_req *, krb5_db_entry, int fetch_asn1_field (unsigned char *, unsigned int, unsigned int, krb5_data *); -int -dbentry_has_key_for_enctype (krb5_context context, - krb5_db_entry *client, - krb5_enctype enctype); - -int -dbentry_supports_enctype (krb5_context context, - krb5_db_entry *client, - krb5_enctype enctype); - krb5_enctype select_session_keytype (krb5_context context, krb5_db_entry *server, diff --git a/src/kdc/main.c b/src/kdc/main.c index 36753b7f00..b119dd53ad 100644 --- a/src/kdc/main.c +++ b/src/kdc/main.c @@ -369,6 +369,12 @@ init_realm(kdc_realm_t *rdp, char *realm, char *def_mpname, else rdp->realm_reject_bad_transit = 1; + /* Handle assume des-cbc-crc is supported for session keys */ + if (rparams && rparams->realm_assume_des_crc_sess_valid) + rdp->realm_assume_des_crc_sess = rparams->realm_assume_des_crc_sess; + else + rdp->realm_assume_des_crc_sess = 1; + /* Handle ticket maximum life */ rdp->realm_maxlife = (rparams && rparams->realm_max_life_valid) ? rparams->realm_max_life : KRB5_KDB_MAX_LIFE; diff --git a/src/lib/kadm5/admin.h b/src/lib/kadm5/admin.h index 4ce56c6261..020962b09e 100644 --- a/src/lib/kadm5/admin.h +++ b/src/lib/kadm5/admin.h @@ -297,6 +297,8 @@ typedef struct __krb5_realm_params { unsigned int realm_flags_valid:1; unsigned int realm_reject_bad_transit_valid:1; unsigned int realm_restrict_anon_valid:1; + unsigned int realm_assume_des_crc_sess:1; + unsigned int realm_assume_des_crc_sess_valid:1; krb5_int32 realm_num_keysalts; } krb5_realm_params; diff --git a/src/lib/kadm5/alt_prof.c b/src/lib/kadm5/alt_prof.c index f182ce6439..2198cd1b18 100644 --- a/src/lib/kadm5/alt_prof.c +++ b/src/lib/kadm5/alt_prof.c @@ -1048,6 +1048,12 @@ krb5_read_realm_params(kcontext, realm, rparamp) rparams->realm_restrict_anon_valid = 1; } + hierarchy[2] = KRB5_CONF_ASSUME_DES_CRC_SESSION; + if (!krb5_aprof_get_boolean(aprofile, hierarchy, TRUE, &bvalue)) { + rparams->realm_assume_des_crc_sess = bvalue; + rparams->realm_assume_des_crc_sess_valid = 1; + } + hierarchy[2] = KRB5_CONF_NO_HOST_REFERRAL; if (!krb5_aprof_get_string_all(aprofile, hierarchy, &no_refrls)) rparams->realm_no_host_referral = no_refrls; diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index d294e1eedb..c882261811 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -590,6 +590,7 @@ krb5int_get_authdata_containee_types krb5int_init_context_kdc krb5int_init_trace krb5int_initialize_library +krb5int_parse_enctype_list krb5int_sendtokdc_debug_handler krb5int_trace profile_abandon diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in index c55b1e8229..375a60d3dc 100644 --- a/src/tests/Makefile.in +++ b/src/tests/Makefile.in @@ -73,6 +73,7 @@ check-pytests:: hist $(RUNPYTEST) $(srcdir)/t_renprinc.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_cccol.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_stringattr.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_sesskeynego.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_crossrealm.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_skew.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_keytab.py $(PYTESTFLAGS) diff --git a/src/tests/t_sesskeynego.py b/src/tests/t_sesskeynego.py new file mode 100644 index 0000000000..9239e12544 --- /dev/null +++ b/src/tests/t_sesskeynego.py @@ -0,0 +1,83 @@ +#!/usr/bin/python +from k5test import * +import re + +# Run "kvno server" with a fresh set of client tickets, then check that the +# enctypes in the service ticket match the expected values. +etypes_re = re.compile(r'server@[^\n]+\n\tEtype \(skey, tkt\): ' + '([^,]+), ([^\s]+)') +def test_kvno(realm, expected_skey, expected_tkt): + realm.kinit(realm.user_princ, password('user')) + realm.run_as_client([kvno, 'server']) + output = realm.run_as_client([klist, '-e']) + m = etypes_re.search(output) + if not m: + fail('could not parse etypes from klist -e output') + skey, tkt = m.groups() + if skey != expected_skey: + fail('got session key type %s, expected %s' % (skey, expected_skey)) + if tkt != expected_tkt: + fail('got ticket key type %s, expected %s' % (tkt, expected_tkt)) + +krb5_conf1 = {'all': {'libdefaults': { + 'default_tgs_enctypes': 'aes128-cts,aes256-cts'}}} + +krb5_conf2 = {'all': {'libdefaults': { + 'default_tgs_enctypes': 'aes256-cts,aes128-cts'}}} + +krb5_conf3 = {'all': {'libdefaults': { + 'allow_weak_crypto': 'true', + 'default_tkt_enctypes': 'aes128-cts', + 'default_tgs_enctypes': 'rc4-hmac,aes128-cts,des-cbc-crc'}}} + +krb5_conf4 = {'all' :{ + 'libdefaults': { + 'allow_weak_crypto': 'true', + 'default_tkt_enctypes': 'aes256-cts', + 'default_tgs_enctypes': 'des-cbc-crc,rc4-hmac,aes256-cts' + }, + 'realms': {'$realm': { + 'des_crc_session_supported' : 'false'}}}} + +# Test with client request and session_enctypes preferring aes128, but +# aes256 long-term key. +realm = K5Realm(krb5_conf=krb5_conf1, create_host=False, get_creds=False) +realm.run_kadminl('addprinc -randkey -e aes256-cts:normal server') +realm.run_kadminl('setstr server session_enctypes aes128-cts,aes256-cts') +test_kvno(realm, 'aes128-cts-hmac-sha1-96', 'aes256-cts-hmac-sha1-96') +realm.stop() + +# Second go, almost same as first, but resulting session key must be aes256 +# because of the difference in default_tgs_enctypes order. This tests that +# session_enctypes doesn't change the order in which we negotiate. +realm = K5Realm(krb5_conf=krb5_conf2, create_host=False, get_creds=False) +realm.run_kadminl('addprinc -randkey -e aes256-cts:normal server') +realm.run_kadminl('setstr server session_enctypes aes128-cts,aes256-cts') +test_kvno(realm, 'aes256-cts-hmac-sha1-96', 'aes256-cts-hmac-sha1-96') +realm.stop() + +# Next we use krb5_conf3 and try various things. +realm = K5Realm(krb5_conf=krb5_conf3, create_host=False, get_creds=False) +realm.run_kadminl('addprinc -randkey -e aes256-cts:normal server') + +# 3a: Negotiate aes128 session key when principal only has aes256 long-term. +realm.run_kadminl('setstr server session_enctypes aes128-cts,aes256-cts') +test_kvno(realm, 'aes128-cts-hmac-sha1-96', 'aes256-cts-hmac-sha1-96') + +# 3b: Negotiate rc4-hmac session key when principal only has aes256 long-term. +realm.run_kadminl('setstr server session_enctypes ' + 'rc4-hmac,aes128-cts,aes256-cts') +test_kvno(realm, 'arcfour-hmac', 'aes256-cts-hmac-sha1-96') + +# 3c: Test des-cbc-crc default assumption. +realm.run_kadminl('delstr server session_enctypes') +test_kvno(realm, 'des-cbc-crc', 'aes256-cts-hmac-sha1-96') +realm.stop() + +# Last go: test that we can disable the des-cbc-crc assumption +realm = K5Realm(krb5_conf=krb5_conf4, get_creds=False) +realm.run_kadminl('addprinc -randkey -e aes256-cts:normal server') +test_kvno(realm, 'aes256-cts-hmac-sha1-96', 'aes256-cts-hmac-sha1-96') +realm.stop() + +success('sesskeynego')