krb5 commit: Add control over session key enctype negotiation

Greg Hudson ghudson at MIT.EDU
Wed Jun 6 14:56:10 EDT 2012


https://github.com/krb5/krb5/commit/0e9bf73d2b8da55aedd25061faefe6a22d9613d3
commit 0e9bf73d2b8da55aedd25061faefe6a22d9613d3
Author: Nicolas Williams <nico at cryptonector.com>
Date:   Mon Jun 4 17:17:31 2012 -0500

    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 at mit.edu: Miscellaneous edits, cleanups, and fixes; refactored
    test script; documented session_enctypes attribute]

 .../krb_admins/admin_commands/kadmin_local.rst     |   13 +++-
 doc/rst_source/krb_admins/conf_files/kdc_conf.rst  |    7 ++
 src/include/adm.h                                  |    2 +
 src/include/k5-int.h                               |    1 +
 src/include/kdb.h                                  |    3 +
 src/kdc/extern.h                                   |    2 +
 src/kdc/kdc_util.c                                 |   84 +++++++++-----------
 src/kdc/kdc_util.h                                 |   10 ---
 src/kdc/main.c                                     |    6 ++
 src/lib/kadm5/admin.h                              |    2 +
 src/lib/kadm5/alt_prof.c                           |    6 ++
 src/lib/krb5/libkrb5.exports                       |    1 +
 src/tests/Makefile.in                              |    1 +
 src/tests/t_sesskeynego.py                         |   83 +++++++++++++++++++
 14 files changed, 161 insertions(+), 60 deletions(-)

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 ec90cff..c8a08eb 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 a84c702..66f51dc 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 9c75b15..9b05f79 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 ca18baf..ee15eac 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 67c4031..291a05b 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 3866c6c..c601e57 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 2f4af73..3f3b406 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 55aafae..0cd7989 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 36753b7..b119dd5 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 4ce56c6..020962b 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 f182ce6..2198cd1 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 d294e1e..c882261 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 c55b1e8..375a60d 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 0000000..9239e12
--- /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')


More information about the cvs-krb5 mailing list